openclaw / src /acp /control-plane /manager.background-task.test.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
f778c12 verified
Raw History Blame Contribute Delete
9.1 kB
/** Regression coverage for ACP background-task summary truncation boundaries. */
import { afterEach, describe, expect, it } from "vitest";
import type { AdmittedRunContext } from "../../agents/admitted-run-context.js";
import { createExecutionIdentityAdmissionToken } from "../../audit/execution-identity-admission.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { tableExists } from "../../state/openclaw-state-db-schema-helpers.js";
import {
closeOpenClawStateDatabaseForTest,
openOpenClawStateDatabase,
} from "../../state/openclaw-state-db.js";
import { findTaskByRunId } from "../../tasks/task-registry.js";
import { bindTaskRunExecution } from "../../tasks/task-registry.store.sqlite.js";
import {
resetTaskRegistryForTests,
resetTaskFlowRegistryForTests,
} from "../../tasks/task-runtime.test-helpers.js";
import { withOpenClawTestState } from "../../test-utils/openclaw-test-state.js";
import { AcpRuntimeError } from "../runtime/errors.js";
import {
appendBackgroundTaskProgressSummary,
bindBackgroundTaskExecution,
createBackgroundTaskRecord,
resolveBackgroundTaskContext,
resolveBackgroundTaskFailureStatus,
} from "./manager.background-task.js";
import { ACP_TURN_TIMEOUT_DETAIL_CODE } from "./manager.turn-timeout.js";
import type { AcpSessionManagerDeps } from "./manager.types.js";
// U+1F99E (🦞) is a surrogate pair in UTF-16; a raw .slice() boundary can split it.
const LOBSTER = "🦞";
afterEach(() => {
closeOpenClawStateDatabaseForTest();
resetTaskRegistryForTests({ persist: false });
resetTaskFlowRegistryForTests({ persist: false });
});
const HIGH_SURROGATE_WITHOUT_LOW = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])/;
function fakeDeps(): AcpSessionManagerDeps {
const loadSessionEntry = (params: { sessionKey: string }) =>
params.sessionKey === "child-session"
? { entry: { spawnedBy: "requester-session" } }
: { entry: {} };
return { loadSessionEntry } as unknown as AcpSessionManagerDeps;
}
describe("appendBackgroundTaskProgressSummary", () => {
it("keeps surrogate pairs intact at the progress truncation boundary", () => {
// combined length 244 puts the pair astride the 239-char cut point.
const result = appendBackgroundTaskProgressSummary("x".repeat(238), `${LOBSTER}tail`);
expect(result).toBe(`${"x".repeat(238)}…`);
expect(HIGH_SURROGATE_WITHOUT_LOW.test(result)).toBe(false);
expect(result.length).toBeLessThanOrEqual(240);
});
it("still truncates plain ASCII exactly at the boundary", () => {
const result = appendBackgroundTaskProgressSummary("a".repeat(240), "b");
expect(result).toBe(`${"a".repeat(239)}…`);
});
it("returns short combined summaries unchanged", () => {
expect(appendBackgroundTaskProgressSummary("done: ", "ok")).toBe("done: ok");
expect(appendBackgroundTaskProgressSummary("", ` step ${LOBSTER}`)).toBe(`step ${LOBSTER}`);
});
});
describe("resolveBackgroundTaskContext", () => {
it("keeps surrogate pairs intact in the bounded task label", () => {
// normalized length 164 puts the pair astride the 159-char cut point.
const context = resolveBackgroundTaskContext({
deps: fakeDeps(),
cfg: {} as unknown as OpenClawConfig,
sessionKey: "child-session",
agentId: "qa",
requestId: "run-1",
text: `${"y".repeat(158)}${LOBSTER}tail`,
});
expect(context?.task).toBe(`${"y".repeat(158)}…`);
expect(HIGH_SURROGATE_WITHOUT_LOW.test(context?.task ?? "")).toBe(false);
});
it("passes short task text through unchanged", () => {
const context = resolveBackgroundTaskContext({
deps: fakeDeps(),
cfg: {} as unknown as OpenClawConfig,
sessionKey: "child-session",
agentId: "qa",
requestId: "run-2",
text: `summarize ${LOBSTER} feedback`,
});
expect(context?.task).toBe(`summarize ${LOBSTER} feedback`);
});
});
describe("resolveBackgroundTaskFailureStatus", () => {
it("uses the structured timeout detail instead of message text", () => {
expect(
resolveBackgroundTaskFailureStatus(
new AcpRuntimeError("ACP_TURN_FAILED", "turn deadline reached", {
detailCode: ACP_TURN_TIMEOUT_DETAIL_CODE,
}),
),
).toBe("timed_out");
expect(
resolveBackgroundTaskFailureStatus(
new AcpRuntimeError("ACP_TURN_FAILED", "backend said the request timed out"),
),
).toBe("failed");
});
});
describe("ACP background task execution binding", () => {
it("binds the exact admitted execution only at prompt submission", async () => {
await withOpenClawTestState(
{ layout: "state-only", prefix: "openclaw-acp-execution-binding-" },
async () => {
resetTaskRegistryForTests();
resetTaskFlowRegistryForTests();
const admitted: AdmittedRunContext = {
operationalRunInstance: { instanceId: "instance-acp", runId: "run-acp" },
executionIdentityToken: createExecutionIdentityAdmissionToken("run-acp", {
contextId: "context-acp",
executionId: "execution-acp",
}),
};
const record = createBackgroundTaskRecord(
{
agentId: "qa",
requesterAgentId: "main",
requesterSessionKey: "agent:main:main",
childSessionKey: "agent:qa:child",
runId: "run-acp",
task: "private",
},
100,
admitted.operationalRunInstance.instanceId,
);
const task = findTaskByRunId("run-acp");
if (!record || !task?.parentFlowId) {
throw new Error("expected ACP task and owner flow");
}
const db = openOpenClawStateDatabase().db;
expect(tableExists(db, "execution_owner_lifecycle_bindings")).toBe(false);
bindBackgroundTaskExecution(record, admitted);
expect(
db
.prepare(
`SELECT owner_kind, owner_id, context_id, execution_id
FROM execution_owner_lifecycle_bindings
ORDER BY owner_kind`,
)
.all(),
).toEqual([
{
owner_kind: "flow",
owner_id: task.parentFlowId,
context_id: "context-acp",
execution_id: "execution-acp",
},
{
owner_kind: "task",
owner_id: task.taskId,
context_id: "context-acp",
execution_id: "execution-acp",
},
]);
},
);
});
it.each(["missing", "mismatched"] as const)(
"does not bind a live parent flow when the task owner is %s",
async (taskOwnerState) => {
await withOpenClawTestState(
{ layout: "state-only", prefix: `openclaw-acp-${taskOwnerState}-task-binding-` },
async () => {
resetTaskRegistryForTests();
resetTaskFlowRegistryForTests();
const admitted: AdmittedRunContext = {
operationalRunInstance: { instanceId: "instance-acp", runId: "run-acp" },
executionIdentityToken: createExecutionIdentityAdmissionToken("run-acp", {
contextId: "context-acp",
executionId: "execution-acp",
}),
};
const record = createBackgroundTaskRecord(
{
agentId: "qa",
requesterAgentId: "main",
requesterSessionKey: "agent:main:main",
childSessionKey: "agent:qa:child",
runId: "run-acp",
task: "private",
},
100,
admitted.operationalRunInstance.instanceId,
);
const task = findTaskByRunId("run-acp");
if (!record || !task?.parentFlowId) {
throw new Error("expected ACP task and owner flow");
}
const db = openOpenClawStateDatabase().db;
if (taskOwnerState === "missing") {
db.prepare("DELETE FROM task_runs WHERE task_id = ?").run(task.taskId);
} else {
expect(
bindTaskRunExecution({
taskId: task.taskId,
admitted: {
operationalRunInstance: { instanceId: "instance-other", runId: "run-other" },
executionIdentityToken: createExecutionIdentityAdmissionToken("run-other", {
contextId: "context-other",
executionId: "execution-other",
}),
},
}),
).toBe("bound");
}
bindBackgroundTaskExecution(record, admitted);
if (taskOwnerState === "missing") {
expect(tableExists(db, "execution_owner_lifecycle_bindings")).toBe(false);
} else {
expect(
db
.prepare(
`SELECT context_id, execution_id
FROM execution_owner_lifecycle_bindings
WHERE owner_kind = 'flow' AND owner_id = ?`,
)
.get(task.parentFlowId),
).toBeUndefined();
}
},
);
},
);
});