fix(tasks): honor explicit agentId in gateway tasks.list and repair test helper signature

Two code-review findings. (1) gateway taskMatchesAgent fell through to a requester/owner/child session-key scan even when the task had an explicit agentId, so a worker subagent task owned by agent:main:main also matched agentId:main; make explicit task.agentId authoritative and keep the session-key fallback only for legacy records without an agentId, with a gateway tasks.list regression. (2) the cross-agent attribution test passed async (root) to the zero-arg withTaskRegistryTempDir helper (TS2345/TS7006); drop the unused parameter and redundant env assignment.
This commit is contained in:
Alix-007
2026-06-15 09:30:28 +08:00
committed by Peter Steinberger
parent 206ac169e6
commit 77d6ad6f65
3 changed files with 33 additions and 6 deletions

View File

@@ -136,6 +136,31 @@ describe("tasks gateway handlers", () => {
expect(listedTask?.runId).toBe("run-running");
});
it("treats explicit task agentId as authoritative over the session-key fallback", async () => {
// Cross-agent subagent task: the registry derives agentId=worker from the
// child session key, while owner/requester keys belong to main. tasks.list
// for main must not leak the worker task through the session-key fallback.
const workerTask = createTaskRecord({
runtime: "subagent",
requesterSessionKey: "agent:main:main",
ownerKey: "agent:main:main",
scopeKind: "session",
childSessionKey: "agent:worker:subagent:child",
runId: "run-worker-authoritative",
task: "Inspect worker state",
status: "running",
deliveryStatus: "pending",
});
expect(workerTask.agentId).toBe("worker");
const mainView = await runTaskHandler("tasks.list", { agentId: "main" });
expect(mainView.calls[0]?.[0]).toBe(true);
expect(mainView.payload?.tasks ?? []).toEqual([]);
const workerView = await runTaskHandler("tasks.list", { agentId: "worker" });
expect(workerView.payload?.tasks?.map((task) => task.taskId)).toEqual([workerTask.taskId]);
});
it("gets completed tasks with stable completed status", async () => {
const task = createTaskRecord({
runtime: "cli",

View File

@@ -114,15 +114,18 @@ function taskMatchesSession(task: TaskRecord, sessionKey: string | undefined): b
);
}
// Some records predate a direct `agentId`, so task listings still recover the
// owning agent from session-style keys instead of hiding those tasks.
// Explicit `task.agentId` is authoritative: a task that records its own agent
// must not also match other agents through the session-key fallback. Only
// records that predate a direct `agentId` recover the owning agent from
// session-style keys instead of being hidden.
function taskMatchesAgent(task: TaskRecord, agentId: string | undefined): boolean {
const normalized = normalizeOptionalString(agentId);
if (!normalized) {
return true;
}
if (normalizeOptionalString(task.agentId) === normalized) {
return true;
const explicitAgentId = normalizeOptionalString(task.agentId);
if (explicitAgentId) {
return explicitAgentId === normalized;
}
return [task.requesterSessionKey, task.childSessionKey, task.ownerKey].some(
(candidate) => parseAgentSessionKey(candidate)?.agentId === normalized,

View File

@@ -2093,8 +2093,7 @@ describe("task-registry", () => {
});
it("uses the child session agent for cross-agent background task attribution", async () => {
await withTaskRegistryTempDir(async (root) => {
process.env.OPENCLAW_STATE_DIR = root;
await withTaskRegistryTempDir(async () => {
resetTaskRegistryMemoryForTest({ persist: false });
const created = createTaskRecord({