@ai-agent-forge/plugin-subagent-delegate 0.85.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -0
- package/agent-forge.json +11 -0
- package/dist/delegate.d.ts +275 -0
- package/dist/delegate.d.ts.map +1 -0
- package/dist/delegate.js +1376 -0
- package/dist/delegate.js.map +1 -0
- package/dist/entry.d.ts +28 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +101 -0
- package/dist/entry.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/multi-agent-tasks.d.ts +101 -0
- package/dist/multi-agent-tasks.d.ts.map +1 -0
- package/dist/multi-agent-tasks.js +178 -0
- package/dist/multi-agent-tasks.js.map +1 -0
- package/dist/remote-adapter.d.ts +11 -0
- package/dist/remote-adapter.d.ts.map +1 -0
- package/dist/remote-adapter.js +366 -0
- package/dist/remote-adapter.js.map +1 -0
- package/dist/runner-pool.d.ts +59 -0
- package/dist/runner-pool.d.ts.map +1 -0
- package/dist/runner-pool.js +330 -0
- package/dist/runner-pool.js.map +1 -0
- package/dist/runner-spawn.d.ts +23 -0
- package/dist/runner-spawn.d.ts.map +1 -0
- package/dist/runner-spawn.js +79 -0
- package/dist/runner-spawn.js.map +1 -0
- package/dist/self-invocation.d.ts +20 -0
- package/dist/self-invocation.d.ts.map +1 -0
- package/dist/self-invocation.js +13 -0
- package/dist/self-invocation.js.map +1 -0
- package/dist/shared-adapter.d.ts +29 -0
- package/dist/shared-adapter.d.ts.map +1 -0
- package/dist/shared-adapter.js +60 -0
- package/dist/shared-adapter.js.map +1 -0
- package/dist/subagent-runtime.d.ts +162 -0
- package/dist/subagent-runtime.d.ts.map +1 -0
- package/dist/subagent-runtime.js +849 -0
- package/dist/subagent-runtime.js.map +1 -0
- package/package.json +55 -0
- package/plugin.json +27 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-Agent task coordination (6.1, 6.2a, 6.2b, 6.3) — a plugin-level task
|
|
3
|
+
* registry for parent/child agent sessions.
|
|
4
|
+
*
|
|
5
|
+
* 6.1 Tasks carry ids, parent-child session relations, and a full
|
|
6
|
+
* lifecycle event log (pending → running → terminal), so relations,
|
|
7
|
+
* states, and terminal outcomes are traceable and replayable.
|
|
8
|
+
* 6.2a Deadline, cancel, and wait: an expired deadline or an abort moves a
|
|
9
|
+
* running task to `timed_out`/`cancelled`; partial failure stays in the
|
|
10
|
+
* event log. Deadlines are checked by an explicit clock tick (no
|
|
11
|
+
* hidden timers), keeping everything deterministic.
|
|
12
|
+
* 6.2b Concurrency slots, result size caps, and per-parent quota: exceeding
|
|
13
|
+
* any limit is a structured, replayable error.
|
|
14
|
+
* 6.3 Result references ({taskId, revision}), bounded aggregation for the
|
|
15
|
+
* parent (truncation with an explicit truncated flag), duplicate
|
|
16
|
+
* completion detection, and recovery by replaying the event log.
|
|
17
|
+
* Kernel-side state machines are NOT involved — this is a plugin.
|
|
18
|
+
*
|
|
19
|
+
* Migrated from the host's `src/plugins/multi-agent-tasks.ts` (D-075 S4 third
|
|
20
|
+
* batch); the only change is the `JsonValue` import source — the plugin-sdk
|
|
21
|
+
* contracts type instead of `@agent-forge/ai` (structural equivalent).
|
|
22
|
+
*/
|
|
23
|
+
import type { JsonValue } from "@agent-forge/plugin-sdk";
|
|
24
|
+
export type AgentTaskStatusV1 = "pending" | "running" | "succeeded" | "failed" | "cancelled" | "timed_out";
|
|
25
|
+
export interface AgentTaskEventV1 {
|
|
26
|
+
readonly at: number;
|
|
27
|
+
readonly from: AgentTaskStatusV1 | null;
|
|
28
|
+
readonly to: AgentTaskStatusV1;
|
|
29
|
+
readonly reason?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface AgentTaskV1 {
|
|
32
|
+
readonly taskId: string;
|
|
33
|
+
readonly parentSessionId: string;
|
|
34
|
+
readonly childSessionId?: string;
|
|
35
|
+
readonly status: AgentTaskStatusV1;
|
|
36
|
+
readonly deadlineAt?: number;
|
|
37
|
+
readonly result?: JsonValue;
|
|
38
|
+
readonly resultBytes?: number;
|
|
39
|
+
readonly revision: number;
|
|
40
|
+
readonly events: readonly AgentTaskEventV1[];
|
|
41
|
+
}
|
|
42
|
+
export interface AgentTaskRegistryOptionsV1 {
|
|
43
|
+
readonly now?: () => number;
|
|
44
|
+
readonly maxConcurrent?: number;
|
|
45
|
+
readonly maxResultBytes?: number;
|
|
46
|
+
/** Pre-recovered tasks (6.3): replayed registries seed their settled tasks here. */
|
|
47
|
+
readonly recoveredTasks?: readonly AgentTaskV1[];
|
|
48
|
+
}
|
|
49
|
+
export type AgentTaskErrorCodesV1 = "unknown_task" | "invalid_transition" | "duplicate_terminal" | "deadline_passed" | "concurrency_limit" | "result_too_large" | "duplicate_result";
|
|
50
|
+
export declare class AgentTaskError extends Error {
|
|
51
|
+
readonly code: AgentTaskErrorCodesV1;
|
|
52
|
+
constructor(code: AgentTaskErrorCodesV1, message: string);
|
|
53
|
+
}
|
|
54
|
+
export interface AgentTaskRegistryV1 {
|
|
55
|
+
createTask(input: {
|
|
56
|
+
readonly taskId: string;
|
|
57
|
+
readonly parentSessionId: string;
|
|
58
|
+
readonly childSessionId?: string;
|
|
59
|
+
readonly deadlineAt?: number;
|
|
60
|
+
}): AgentTaskV1;
|
|
61
|
+
start(taskId: string): AgentTaskV1;
|
|
62
|
+
complete(taskId: string, result: JsonValue): AgentTaskV1;
|
|
63
|
+
fail(taskId: string, reason: string): AgentTaskV1;
|
|
64
|
+
cancel(taskId: string, reason: string): AgentTaskV1;
|
|
65
|
+
/** Explicit clock tick: moves running tasks past their deadline to timed_out. */
|
|
66
|
+
checkDeadlines(): readonly string[];
|
|
67
|
+
waitUntilSettled(taskId: string, input?: {
|
|
68
|
+
readonly until?: number;
|
|
69
|
+
}): AgentTaskV1;
|
|
70
|
+
childrenOf(parentSessionId: string): readonly AgentTaskV1[];
|
|
71
|
+
get(taskId: string): AgentTaskV1 | undefined;
|
|
72
|
+
beginResult(taskId: string): {
|
|
73
|
+
readonly status: "claimed";
|
|
74
|
+
} | {
|
|
75
|
+
readonly status: "concurrency_limit";
|
|
76
|
+
readonly maxConcurrent: number;
|
|
77
|
+
};
|
|
78
|
+
submitResult(taskId: string, result: JsonValue): AgentTaskV1;
|
|
79
|
+
aggregate(taskIds: readonly string[], input?: {
|
|
80
|
+
readonly limitBytes?: number;
|
|
81
|
+
}): {
|
|
82
|
+
readonly results: readonly {
|
|
83
|
+
readonly taskId: string;
|
|
84
|
+
readonly result?: JsonValue;
|
|
85
|
+
readonly truncated: boolean;
|
|
86
|
+
readonly missing: boolean;
|
|
87
|
+
}[];
|
|
88
|
+
readonly truncated: boolean;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
export declare function createAgentTaskRegistry(options?: AgentTaskRegistryOptionsV1): AgentTaskRegistryV1;
|
|
92
|
+
/** 6.3 recovery: replays one task's lifecycle log into its settled state. */
|
|
93
|
+
export declare function replayTaskLog(log: {
|
|
94
|
+
readonly taskId: string;
|
|
95
|
+
readonly parentSessionId: string;
|
|
96
|
+
readonly childSessionId?: string;
|
|
97
|
+
readonly deadlineAt?: number;
|
|
98
|
+
readonly result?: JsonValue;
|
|
99
|
+
readonly events: readonly AgentTaskEventV1[];
|
|
100
|
+
}): AgentTaskV1;
|
|
101
|
+
//# sourceMappingURL=multi-agent-tasks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-agent-tasks.d.ts","sourceRoot":"","sources":["../src/multi-agent-tasks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEzD,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,WAAW,GAAG,WAAW,CAAC;AAE3G,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACxC,QAAQ,CAAC,EAAE,EAAE,iBAAiB,CAAC;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;IAC5B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,SAAS,gBAAgB,EAAE,CAAC;CAC7C;AAED,MAAM,WAAW,0BAA0B;IAC1C,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,oFAAoF;IACpF,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;CACjD;AAED,MAAM,MAAM,qBAAqB,GAC9B,cAAc,GACd,oBAAoB,GACpB,oBAAoB,GACpB,iBAAiB,GACjB,mBAAmB,GACnB,kBAAkB,GAClB,kBAAkB,CAAC;AAEtB,qBAAa,cAAe,SAAQ,KAAK;IACxC,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IAErC,YAAY,IAAI,EAAE,qBAAqB,EAAE,OAAO,EAAE,MAAM,EAIvD;CACD;AAED,MAAM,WAAW,mBAAmB;IACnC,UAAU,CAAC,KAAK,EAAE;QACjB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;QACjC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;QACjC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;KAC7B,GAAG,WAAW,CAAC;IAChB,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,WAAW,CAAC;IACzD,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,CAAC;IAClD,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,CAAC;IACpD,iFAAiF;IACjF,cAAc,IAAI,SAAS,MAAM,EAAE,CAAC;IACpC,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,WAAW,CAAC;IACnF,UAAU,CAAC,eAAe,EAAE,MAAM,GAAG,SAAS,WAAW,EAAE,CAAC;IAC5D,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAAC;IAC7C,WAAW,CACV,MAAM,EAAE,MAAM,GACZ;QAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAA;KAAE,GAAG;QAAE,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;QAAC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7G,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,WAAW,CAAC;IAC7D,SAAS,CACR,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,GACtC;QACF,QAAQ,CAAC,OAAO,EAAE,SAAS;YAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;YACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;YAC5B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;YAC5B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;SAC1B,EAAE,CAAC;QACJ,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;KAC5B,CAAC;CACF;AAED,wBAAgB,uBAAuB,CAAC,OAAO,GAAE,0BAA+B,GAAG,mBAAmB,CAuJrG;AAMD,6EAA6E;AAC7E,wBAAgB,aAAa,CAAC,GAAG,EAAE;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,SAAS,gBAAgB,EAAE,CAAC;CAC7C,GAAG,WAAW,CAwBd","sourcesContent":["/**\n * Multi-Agent task coordination (6.1, 6.2a, 6.2b, 6.3) — a plugin-level task\n * registry for parent/child agent sessions.\n *\n * 6.1 Tasks carry ids, parent-child session relations, and a full\n * lifecycle event log (pending → running → terminal), so relations,\n * states, and terminal outcomes are traceable and replayable.\n * 6.2a Deadline, cancel, and wait: an expired deadline or an abort moves a\n * running task to `timed_out`/`cancelled`; partial failure stays in the\n * event log. Deadlines are checked by an explicit clock tick (no\n * hidden timers), keeping everything deterministic.\n * 6.2b Concurrency slots, result size caps, and per-parent quota: exceeding\n * any limit is a structured, replayable error.\n * 6.3 Result references ({taskId, revision}), bounded aggregation for the\n * parent (truncation with an explicit truncated flag), duplicate\n * completion detection, and recovery by replaying the event log.\n * Kernel-side state machines are NOT involved — this is a plugin.\n *\n * Migrated from the host's `src/plugins/multi-agent-tasks.ts` (D-075 S4 third\n * batch); the only change is the `JsonValue` import source — the plugin-sdk\n * contracts type instead of `@agent-forge/ai` (structural equivalent).\n */\nimport type { JsonValue } from \"@agent-forge/plugin-sdk\";\n\nexport type AgentTaskStatusV1 = \"pending\" | \"running\" | \"succeeded\" | \"failed\" | \"cancelled\" | \"timed_out\";\n\nexport interface AgentTaskEventV1 {\n\treadonly at: number;\n\treadonly from: AgentTaskStatusV1 | null;\n\treadonly to: AgentTaskStatusV1;\n\treadonly reason?: string;\n}\n\nexport interface AgentTaskV1 {\n\treadonly taskId: string;\n\treadonly parentSessionId: string;\n\treadonly childSessionId?: string;\n\treadonly status: AgentTaskStatusV1;\n\treadonly deadlineAt?: number;\n\treadonly result?: JsonValue;\n\treadonly resultBytes?: number;\n\treadonly revision: number;\n\treadonly events: readonly AgentTaskEventV1[];\n}\n\nexport interface AgentTaskRegistryOptionsV1 {\n\treadonly now?: () => number;\n\treadonly maxConcurrent?: number;\n\treadonly maxResultBytes?: number;\n\t/** Pre-recovered tasks (6.3): replayed registries seed their settled tasks here. */\n\treadonly recoveredTasks?: readonly AgentTaskV1[];\n}\n\nexport type AgentTaskErrorCodesV1 =\n\t| \"unknown_task\"\n\t| \"invalid_transition\"\n\t| \"duplicate_terminal\"\n\t| \"deadline_passed\"\n\t| \"concurrency_limit\"\n\t| \"result_too_large\"\n\t| \"duplicate_result\";\n\nexport class AgentTaskError extends Error {\n\treadonly code: AgentTaskErrorCodesV1;\n\n\tconstructor(code: AgentTaskErrorCodesV1, message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"AgentTaskError\";\n\t\tthis.code = code;\n\t}\n}\n\nexport interface AgentTaskRegistryV1 {\n\tcreateTask(input: {\n\t\treadonly taskId: string;\n\t\treadonly parentSessionId: string;\n\t\treadonly childSessionId?: string;\n\t\treadonly deadlineAt?: number;\n\t}): AgentTaskV1;\n\tstart(taskId: string): AgentTaskV1;\n\tcomplete(taskId: string, result: JsonValue): AgentTaskV1;\n\tfail(taskId: string, reason: string): AgentTaskV1;\n\tcancel(taskId: string, reason: string): AgentTaskV1;\n\t/** Explicit clock tick: moves running tasks past their deadline to timed_out. */\n\tcheckDeadlines(): readonly string[];\n\twaitUntilSettled(taskId: string, input?: { readonly until?: number }): AgentTaskV1;\n\tchildrenOf(parentSessionId: string): readonly AgentTaskV1[];\n\tget(taskId: string): AgentTaskV1 | undefined;\n\tbeginResult(\n\t\ttaskId: string,\n\t): { readonly status: \"claimed\" } | { readonly status: \"concurrency_limit\"; readonly maxConcurrent: number };\n\tsubmitResult(taskId: string, result: JsonValue): AgentTaskV1;\n\taggregate(\n\t\ttaskIds: readonly string[],\n\t\tinput?: { readonly limitBytes?: number },\n\t): {\n\t\treadonly results: readonly {\n\t\t\treadonly taskId: string;\n\t\t\treadonly result?: JsonValue;\n\t\t\treadonly truncated: boolean;\n\t\t\treadonly missing: boolean;\n\t\t}[];\n\t\treadonly truncated: boolean;\n\t};\n}\n\nexport function createAgentTaskRegistry(options: AgentTaskRegistryOptionsV1 = {}): AgentTaskRegistryV1 {\n\tconst now = options.now ?? (() => 0);\n\tconst maxConcurrent = options.maxConcurrent ?? Number.POSITIVE_INFINITY;\n\tconst maxResultBytes = options.maxResultBytes ?? Number.POSITIVE_INFINITY;\n\tconst tasks = new Map<string, AgentTaskV1>();\n\tfor (const recovered of options.recoveredTasks ?? []) {\n\t\ttasks.set(recovered.taskId, recovered);\n\t}\n\n\tconst requireTask = (taskId: string): AgentTaskV1 => {\n\t\tconst task = tasks.get(taskId);\n\t\tif (!task) throw new AgentTaskError(\"unknown_task\", `Unknown task: ${taskId}`);\n\t\treturn task;\n\t};\n\n\tconst transition = (taskId: string, to: AgentTaskStatusV1, reason?: string, result?: JsonValue): AgentTaskV1 => {\n\t\tconst task = requireTask(taskId);\n\t\tif ([\"succeeded\", \"failed\", \"cancelled\", \"timed_out\"].includes(task.status)) {\n\t\t\tthrow new AgentTaskError(\n\t\t\t\t\"duplicate_terminal\",\n\t\t\t\t`Task ${taskId} is already ${task.status}; cannot transition to ${to}`,\n\t\t\t);\n\t\t}\n\t\tconst event: AgentTaskEventV1 = { at: now(), from: task.status, to, ...(reason === undefined ? {} : { reason }) };\n\t\tconst next: AgentTaskV1 = {\n\t\t\t...task,\n\t\t\tstatus: to,\n\t\t\t...(result !== undefined ? { result, resultBytes: JSON.stringify(result).length } : {}),\n\t\t\trevision: task.revision + 1,\n\t\t\tevents: Object.freeze([...task.events, event]),\n\t\t};\n\t\ttasks.set(taskId, next);\n\t\treturn next;\n\t};\n\n\treturn {\n\t\tcreateTask(input) {\n\t\t\tif (typeof input.taskId !== \"string\" || input.taskId.trim().length === 0) {\n\t\t\t\tthrow new AgentTaskError(\"invalid_transition\", \"Task id must be a non-empty string\");\n\t\t\t}\n\t\t\tif (typeof input.parentSessionId !== \"string\" || input.parentSessionId.trim().length === 0) {\n\t\t\t\tthrow new AgentTaskError(\"invalid_transition\", \"Task parentSessionId must be a non-empty string\");\n\t\t\t}\n\t\t\tif (input.deadlineAt !== undefined && input.deadlineAt <= now()) {\n\t\t\t\tthrow new AgentTaskError(\"deadline_passed\", `Deadline ${input.deadlineAt} is in the past (now=${now()})`);\n\t\t\t}\n\t\t\tconst task: AgentTaskV1 = {\n\t\t\t\ttaskId: input.taskId,\n\t\t\t\tparentSessionId: input.parentSessionId,\n\t\t\t\t...(input.childSessionId === undefined ? {} : { childSessionId: input.childSessionId }),\n\t\t\t\tstatus: \"pending\",\n\t\t\t\t...(input.deadlineAt === undefined ? {} : { deadlineAt: input.deadlineAt }),\n\t\t\t\trevision: 0,\n\t\t\t\tevents: Object.freeze([{ at: now(), from: null, to: \"pending\" as const }]),\n\t\t\t};\n\t\t\ttasks.set(input.taskId, task);\n\t\t\treturn task;\n\t\t},\n\t\tstart(taskId) {\n\t\t\tconst task = requireTask(taskId);\n\t\t\tif (\n\t\t\t\ttask.status === \"pending\" &&\n\t\t\t\t[...tasks.values()].filter((candidate) => candidate.status === \"running\").length >= maxConcurrent\n\t\t\t) {\n\t\t\t\tthrow new AgentTaskError(\"concurrency_limit\", `At most ${maxConcurrent} task(s) may run concurrently`);\n\t\t\t}\n\t\t\treturn transition(taskId, \"running\");\n\t\t},\n\t\tcomplete(taskId, result) {\n\t\t\tconst serialized = JSON.stringify(result);\n\t\t\tif (serialized.length > maxResultBytes) {\n\t\t\t\tthrow new AgentTaskError(\n\t\t\t\t\t\"result_too_large\",\n\t\t\t\t\t`Result of ${serialized.length} bytes exceeds the ${maxResultBytes}-byte limit`,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn transition(taskId, \"succeeded\", undefined, result);\n\t\t},\n\t\tfail(taskId, reason) {\n\t\t\treturn transition(taskId, \"failed\", reason);\n\t\t},\n\t\tcancel(taskId, reason) {\n\t\t\treturn transition(taskId, \"cancelled\", reason);\n\t\t},\n\t\tcheckDeadlines() {\n\t\t\tconst currentTime = now();\n\t\t\tconst expired: string[] = [];\n\t\t\tfor (const task of tasks.values()) {\n\t\t\t\tif (task.status === \"running\" && task.deadlineAt !== undefined && task.deadlineAt < currentTime) {\n\t\t\t\t\ttransition(task.taskId, \"timed_out\", `Deadline ${task.deadlineAt} passed`);\n\t\t\t\t\texpired.push(task.taskId);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn Object.freeze(expired);\n\t\t},\n\t\twaitUntilSettled(taskId, input) {\n\t\t\tconst until = input?.until ?? now();\n\t\t\tlet task = requireTask(taskId);\n\t\t\tcheckDeadlinesPass(until);\n\t\t\ttask = requireTask(taskId);\n\t\t\tif (!isSettled(task.status)) {\n\t\t\t\t// No real time passes in a deterministic registry: an unsettleable\n\t\t\t\t// task is reported as still running at the requested time.\n\t\t\t\treturn task;\n\t\t\t}\n\t\t\treturn task;\n\t\t},\n\t\tchildrenOf(parentSessionId) {\n\t\t\treturn Object.freeze([...tasks.values()].filter((task) => task.parentSessionId === parentSessionId));\n\t\t},\n\t\tget(taskId) {\n\t\t\treturn tasks.get(taskId);\n\t\t},\n\t\tbeginResult(taskId) {\n\t\t\trequireTask(taskId);\n\t\t\tconst running = [...tasks.values()].filter((task) => task.status === \"running\").length;\n\t\t\tif (running >= maxConcurrent) {\n\t\t\t\treturn { status: \"concurrency_limit\", maxConcurrent };\n\t\t\t}\n\t\t\treturn { status: \"claimed\" };\n\t\t},\n\t\tsubmitResult(taskId, result) {\n\t\t\treturn this.complete(taskId, result);\n\t\t},\n\t\taggregate(taskIds, input) {\n\t\t\tconst limitBytes = input?.limitBytes ?? Number.POSITIVE_INFINITY;\n\t\t\tlet used = 0;\n\t\t\tlet truncated = false;\n\t\t\tconst results = taskIds.map((taskId) => {\n\t\t\t\tconst task = tasks.get(taskId);\n\t\t\t\tif (!task) return { taskId, truncated: false, missing: true };\n\t\t\t\tif (task.result === undefined) return { taskId, truncated: false, missing: true };\n\t\t\t\tconst size = task.resultBytes ?? 0;\n\t\t\t\tif (used + size > limitBytes) {\n\t\t\t\t\ttruncated = true;\n\t\t\t\t\treturn { taskId, truncated: true, missing: false };\n\t\t\t\t}\n\t\t\t\tused += size;\n\t\t\t\treturn { taskId, result: task.result, truncated: false, missing: false };\n\t\t\t});\n\t\t\treturn { results: Object.freeze(results), truncated };\n\t\t},\n\t};\n\n\tfunction checkDeadlinesPass(until: number): void {\n\t\tfor (const task of tasks.values()) {\n\t\t\tif (task.status === \"running\" && task.deadlineAt !== undefined && task.deadlineAt < until) {\n\t\t\t\ttransition(task.taskId, \"timed_out\", `Deadline ${task.deadlineAt} passed`);\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction isSettled(status: AgentTaskStatusV1): boolean {\n\treturn status === \"succeeded\" || status === \"failed\" || status === \"cancelled\" || status === \"timed_out\";\n}\n\n/** 6.3 recovery: replays one task's lifecycle log into its settled state. */\nexport function replayTaskLog(log: {\n\treadonly taskId: string;\n\treadonly parentSessionId: string;\n\treadonly childSessionId?: string;\n\treadonly deadlineAt?: number;\n\treadonly result?: JsonValue;\n\treadonly events: readonly AgentTaskEventV1[];\n}): AgentTaskV1 {\n\tlet status: AgentTaskStatusV1 = \"pending\";\n\t// The registry counts the initial pending creation as revision 0 and each\n\t// subsequent transition as +1, so a replay mirrors that: events - 1.\n\tconst revision = Math.max(log.events.length - 1, 0);\n\tfor (const event of log.events) {\n\t\tif (event.from !== null && event.from !== status) {\n\t\t\tthrow new AgentTaskError(\n\t\t\t\t\"invalid_transition\",\n\t\t\t\t`Replay mismatch for ${log.taskId}: event.from=${event.from} but the task was ${status}`,\n\t\t\t);\n\t\t}\n\t\tstatus = event.to;\n\t}\n\treturn {\n\t\ttaskId: log.taskId,\n\t\tparentSessionId: log.parentSessionId,\n\t\t...(log.childSessionId === undefined ? {} : { childSessionId: log.childSessionId }),\n\t\tstatus,\n\t\t...(log.deadlineAt === undefined ? {} : { deadlineAt: log.deadlineAt }),\n\t\t...(log.result === undefined ? {} : { result: log.result }),\n\t\trevision,\n\t\tevents: Object.freeze([...log.events]),\n\t};\n}\n"]}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
export class AgentTaskError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
constructor(code, message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "AgentTaskError";
|
|
6
|
+
this.code = code;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export function createAgentTaskRegistry(options = {}) {
|
|
10
|
+
const now = options.now ?? (() => 0);
|
|
11
|
+
const maxConcurrent = options.maxConcurrent ?? Number.POSITIVE_INFINITY;
|
|
12
|
+
const maxResultBytes = options.maxResultBytes ?? Number.POSITIVE_INFINITY;
|
|
13
|
+
const tasks = new Map();
|
|
14
|
+
for (const recovered of options.recoveredTasks ?? []) {
|
|
15
|
+
tasks.set(recovered.taskId, recovered);
|
|
16
|
+
}
|
|
17
|
+
const requireTask = (taskId) => {
|
|
18
|
+
const task = tasks.get(taskId);
|
|
19
|
+
if (!task)
|
|
20
|
+
throw new AgentTaskError("unknown_task", `Unknown task: ${taskId}`);
|
|
21
|
+
return task;
|
|
22
|
+
};
|
|
23
|
+
const transition = (taskId, to, reason, result) => {
|
|
24
|
+
const task = requireTask(taskId);
|
|
25
|
+
if (["succeeded", "failed", "cancelled", "timed_out"].includes(task.status)) {
|
|
26
|
+
throw new AgentTaskError("duplicate_terminal", `Task ${taskId} is already ${task.status}; cannot transition to ${to}`);
|
|
27
|
+
}
|
|
28
|
+
const event = { at: now(), from: task.status, to, ...(reason === undefined ? {} : { reason }) };
|
|
29
|
+
const next = {
|
|
30
|
+
...task,
|
|
31
|
+
status: to,
|
|
32
|
+
...(result !== undefined ? { result, resultBytes: JSON.stringify(result).length } : {}),
|
|
33
|
+
revision: task.revision + 1,
|
|
34
|
+
events: Object.freeze([...task.events, event]),
|
|
35
|
+
};
|
|
36
|
+
tasks.set(taskId, next);
|
|
37
|
+
return next;
|
|
38
|
+
};
|
|
39
|
+
return {
|
|
40
|
+
createTask(input) {
|
|
41
|
+
if (typeof input.taskId !== "string" || input.taskId.trim().length === 0) {
|
|
42
|
+
throw new AgentTaskError("invalid_transition", "Task id must be a non-empty string");
|
|
43
|
+
}
|
|
44
|
+
if (typeof input.parentSessionId !== "string" || input.parentSessionId.trim().length === 0) {
|
|
45
|
+
throw new AgentTaskError("invalid_transition", "Task parentSessionId must be a non-empty string");
|
|
46
|
+
}
|
|
47
|
+
if (input.deadlineAt !== undefined && input.deadlineAt <= now()) {
|
|
48
|
+
throw new AgentTaskError("deadline_passed", `Deadline ${input.deadlineAt} is in the past (now=${now()})`);
|
|
49
|
+
}
|
|
50
|
+
const task = {
|
|
51
|
+
taskId: input.taskId,
|
|
52
|
+
parentSessionId: input.parentSessionId,
|
|
53
|
+
...(input.childSessionId === undefined ? {} : { childSessionId: input.childSessionId }),
|
|
54
|
+
status: "pending",
|
|
55
|
+
...(input.deadlineAt === undefined ? {} : { deadlineAt: input.deadlineAt }),
|
|
56
|
+
revision: 0,
|
|
57
|
+
events: Object.freeze([{ at: now(), from: null, to: "pending" }]),
|
|
58
|
+
};
|
|
59
|
+
tasks.set(input.taskId, task);
|
|
60
|
+
return task;
|
|
61
|
+
},
|
|
62
|
+
start(taskId) {
|
|
63
|
+
const task = requireTask(taskId);
|
|
64
|
+
if (task.status === "pending" &&
|
|
65
|
+
[...tasks.values()].filter((candidate) => candidate.status === "running").length >= maxConcurrent) {
|
|
66
|
+
throw new AgentTaskError("concurrency_limit", `At most ${maxConcurrent} task(s) may run concurrently`);
|
|
67
|
+
}
|
|
68
|
+
return transition(taskId, "running");
|
|
69
|
+
},
|
|
70
|
+
complete(taskId, result) {
|
|
71
|
+
const serialized = JSON.stringify(result);
|
|
72
|
+
if (serialized.length > maxResultBytes) {
|
|
73
|
+
throw new AgentTaskError("result_too_large", `Result of ${serialized.length} bytes exceeds the ${maxResultBytes}-byte limit`);
|
|
74
|
+
}
|
|
75
|
+
return transition(taskId, "succeeded", undefined, result);
|
|
76
|
+
},
|
|
77
|
+
fail(taskId, reason) {
|
|
78
|
+
return transition(taskId, "failed", reason);
|
|
79
|
+
},
|
|
80
|
+
cancel(taskId, reason) {
|
|
81
|
+
return transition(taskId, "cancelled", reason);
|
|
82
|
+
},
|
|
83
|
+
checkDeadlines() {
|
|
84
|
+
const currentTime = now();
|
|
85
|
+
const expired = [];
|
|
86
|
+
for (const task of tasks.values()) {
|
|
87
|
+
if (task.status === "running" && task.deadlineAt !== undefined && task.deadlineAt < currentTime) {
|
|
88
|
+
transition(task.taskId, "timed_out", `Deadline ${task.deadlineAt} passed`);
|
|
89
|
+
expired.push(task.taskId);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return Object.freeze(expired);
|
|
93
|
+
},
|
|
94
|
+
waitUntilSettled(taskId, input) {
|
|
95
|
+
const until = input?.until ?? now();
|
|
96
|
+
let task = requireTask(taskId);
|
|
97
|
+
checkDeadlinesPass(until);
|
|
98
|
+
task = requireTask(taskId);
|
|
99
|
+
if (!isSettled(task.status)) {
|
|
100
|
+
// No real time passes in a deterministic registry: an unsettleable
|
|
101
|
+
// task is reported as still running at the requested time.
|
|
102
|
+
return task;
|
|
103
|
+
}
|
|
104
|
+
return task;
|
|
105
|
+
},
|
|
106
|
+
childrenOf(parentSessionId) {
|
|
107
|
+
return Object.freeze([...tasks.values()].filter((task) => task.parentSessionId === parentSessionId));
|
|
108
|
+
},
|
|
109
|
+
get(taskId) {
|
|
110
|
+
return tasks.get(taskId);
|
|
111
|
+
},
|
|
112
|
+
beginResult(taskId) {
|
|
113
|
+
requireTask(taskId);
|
|
114
|
+
const running = [...tasks.values()].filter((task) => task.status === "running").length;
|
|
115
|
+
if (running >= maxConcurrent) {
|
|
116
|
+
return { status: "concurrency_limit", maxConcurrent };
|
|
117
|
+
}
|
|
118
|
+
return { status: "claimed" };
|
|
119
|
+
},
|
|
120
|
+
submitResult(taskId, result) {
|
|
121
|
+
return this.complete(taskId, result);
|
|
122
|
+
},
|
|
123
|
+
aggregate(taskIds, input) {
|
|
124
|
+
const limitBytes = input?.limitBytes ?? Number.POSITIVE_INFINITY;
|
|
125
|
+
let used = 0;
|
|
126
|
+
let truncated = false;
|
|
127
|
+
const results = taskIds.map((taskId) => {
|
|
128
|
+
const task = tasks.get(taskId);
|
|
129
|
+
if (!task)
|
|
130
|
+
return { taskId, truncated: false, missing: true };
|
|
131
|
+
if (task.result === undefined)
|
|
132
|
+
return { taskId, truncated: false, missing: true };
|
|
133
|
+
const size = task.resultBytes ?? 0;
|
|
134
|
+
if (used + size > limitBytes) {
|
|
135
|
+
truncated = true;
|
|
136
|
+
return { taskId, truncated: true, missing: false };
|
|
137
|
+
}
|
|
138
|
+
used += size;
|
|
139
|
+
return { taskId, result: task.result, truncated: false, missing: false };
|
|
140
|
+
});
|
|
141
|
+
return { results: Object.freeze(results), truncated };
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
function checkDeadlinesPass(until) {
|
|
145
|
+
for (const task of tasks.values()) {
|
|
146
|
+
if (task.status === "running" && task.deadlineAt !== undefined && task.deadlineAt < until) {
|
|
147
|
+
transition(task.taskId, "timed_out", `Deadline ${task.deadlineAt} passed`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function isSettled(status) {
|
|
153
|
+
return status === "succeeded" || status === "failed" || status === "cancelled" || status === "timed_out";
|
|
154
|
+
}
|
|
155
|
+
/** 6.3 recovery: replays one task's lifecycle log into its settled state. */
|
|
156
|
+
export function replayTaskLog(log) {
|
|
157
|
+
let status = "pending";
|
|
158
|
+
// The registry counts the initial pending creation as revision 0 and each
|
|
159
|
+
// subsequent transition as +1, so a replay mirrors that: events - 1.
|
|
160
|
+
const revision = Math.max(log.events.length - 1, 0);
|
|
161
|
+
for (const event of log.events) {
|
|
162
|
+
if (event.from !== null && event.from !== status) {
|
|
163
|
+
throw new AgentTaskError("invalid_transition", `Replay mismatch for ${log.taskId}: event.from=${event.from} but the task was ${status}`);
|
|
164
|
+
}
|
|
165
|
+
status = event.to;
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
taskId: log.taskId,
|
|
169
|
+
parentSessionId: log.parentSessionId,
|
|
170
|
+
...(log.childSessionId === undefined ? {} : { childSessionId: log.childSessionId }),
|
|
171
|
+
status,
|
|
172
|
+
...(log.deadlineAt === undefined ? {} : { deadlineAt: log.deadlineAt }),
|
|
173
|
+
...(log.result === undefined ? {} : { result: log.result }),
|
|
174
|
+
revision,
|
|
175
|
+
events: Object.freeze([...log.events]),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=multi-agent-tasks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-agent-tasks.js","sourceRoot":"","sources":["../src/multi-agent-tasks.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Multi-Agent task coordination (6.1, 6.2a, 6.2b, 6.3) — a plugin-level task\n * registry for parent/child agent sessions.\n *\n * 6.1 Tasks carry ids, parent-child session relations, and a full\n * lifecycle event log (pending → running → terminal), so relations,\n * states, and terminal outcomes are traceable and replayable.\n * 6.2a Deadline, cancel, and wait: an expired deadline or an abort moves a\n * running task to `timed_out`/`cancelled`; partial failure stays in the\n * event log. Deadlines are checked by an explicit clock tick (no\n * hidden timers), keeping everything deterministic.\n * 6.2b Concurrency slots, result size caps, and per-parent quota: exceeding\n * any limit is a structured, replayable error.\n * 6.3 Result references ({taskId, revision}), bounded aggregation for the\n * parent (truncation with an explicit truncated flag), duplicate\n * completion detection, and recovery by replaying the event log.\n * Kernel-side state machines are NOT involved — this is a plugin.\n *\n * Migrated from the host's `src/plugins/multi-agent-tasks.ts` (D-075 S4 third\n * batch); the only change is the `JsonValue` import source — the plugin-sdk\n * contracts type instead of `@agent-forge/ai` (structural equivalent).\n */\nimport type { JsonValue } from \"@agent-forge/plugin-sdk\";\n\nexport type AgentTaskStatusV1 = \"pending\" | \"running\" | \"succeeded\" | \"failed\" | \"cancelled\" | \"timed_out\";\n\nexport interface AgentTaskEventV1 {\n\treadonly at: number;\n\treadonly from: AgentTaskStatusV1 | null;\n\treadonly to: AgentTaskStatusV1;\n\treadonly reason?: string;\n}\n\nexport interface AgentTaskV1 {\n\treadonly taskId: string;\n\treadonly parentSessionId: string;\n\treadonly childSessionId?: string;\n\treadonly status: AgentTaskStatusV1;\n\treadonly deadlineAt?: number;\n\treadonly result?: JsonValue;\n\treadonly resultBytes?: number;\n\treadonly revision: number;\n\treadonly events: readonly AgentTaskEventV1[];\n}\n\nexport interface AgentTaskRegistryOptionsV1 {\n\treadonly now?: () => number;\n\treadonly maxConcurrent?: number;\n\treadonly maxResultBytes?: number;\n\t/** Pre-recovered tasks (6.3): replayed registries seed their settled tasks here. */\n\treadonly recoveredTasks?: readonly AgentTaskV1[];\n}\n\nexport type AgentTaskErrorCodesV1 =\n\t| \"unknown_task\"\n\t| \"invalid_transition\"\n\t| \"duplicate_terminal\"\n\t| \"deadline_passed\"\n\t| \"concurrency_limit\"\n\t| \"result_too_large\"\n\t| \"duplicate_result\";\n\nexport class AgentTaskError extends Error {\n\treadonly code: AgentTaskErrorCodesV1;\n\n\tconstructor(code: AgentTaskErrorCodesV1, message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"AgentTaskError\";\n\t\tthis.code = code;\n\t}\n}\n\nexport interface AgentTaskRegistryV1 {\n\tcreateTask(input: {\n\t\treadonly taskId: string;\n\t\treadonly parentSessionId: string;\n\t\treadonly childSessionId?: string;\n\t\treadonly deadlineAt?: number;\n\t}): AgentTaskV1;\n\tstart(taskId: string): AgentTaskV1;\n\tcomplete(taskId: string, result: JsonValue): AgentTaskV1;\n\tfail(taskId: string, reason: string): AgentTaskV1;\n\tcancel(taskId: string, reason: string): AgentTaskV1;\n\t/** Explicit clock tick: moves running tasks past their deadline to timed_out. */\n\tcheckDeadlines(): readonly string[];\n\twaitUntilSettled(taskId: string, input?: { readonly until?: number }): AgentTaskV1;\n\tchildrenOf(parentSessionId: string): readonly AgentTaskV1[];\n\tget(taskId: string): AgentTaskV1 | undefined;\n\tbeginResult(\n\t\ttaskId: string,\n\t): { readonly status: \"claimed\" } | { readonly status: \"concurrency_limit\"; readonly maxConcurrent: number };\n\tsubmitResult(taskId: string, result: JsonValue): AgentTaskV1;\n\taggregate(\n\t\ttaskIds: readonly string[],\n\t\tinput?: { readonly limitBytes?: number },\n\t): {\n\t\treadonly results: readonly {\n\t\t\treadonly taskId: string;\n\t\t\treadonly result?: JsonValue;\n\t\t\treadonly truncated: boolean;\n\t\t\treadonly missing: boolean;\n\t\t}[];\n\t\treadonly truncated: boolean;\n\t};\n}\n\nexport function createAgentTaskRegistry(options: AgentTaskRegistryOptionsV1 = {}): AgentTaskRegistryV1 {\n\tconst now = options.now ?? (() => 0);\n\tconst maxConcurrent = options.maxConcurrent ?? Number.POSITIVE_INFINITY;\n\tconst maxResultBytes = options.maxResultBytes ?? Number.POSITIVE_INFINITY;\n\tconst tasks = new Map<string, AgentTaskV1>();\n\tfor (const recovered of options.recoveredTasks ?? []) {\n\t\ttasks.set(recovered.taskId, recovered);\n\t}\n\n\tconst requireTask = (taskId: string): AgentTaskV1 => {\n\t\tconst task = tasks.get(taskId);\n\t\tif (!task) throw new AgentTaskError(\"unknown_task\", `Unknown task: ${taskId}`);\n\t\treturn task;\n\t};\n\n\tconst transition = (taskId: string, to: AgentTaskStatusV1, reason?: string, result?: JsonValue): AgentTaskV1 => {\n\t\tconst task = requireTask(taskId);\n\t\tif ([\"succeeded\", \"failed\", \"cancelled\", \"timed_out\"].includes(task.status)) {\n\t\t\tthrow new AgentTaskError(\n\t\t\t\t\"duplicate_terminal\",\n\t\t\t\t`Task ${taskId} is already ${task.status}; cannot transition to ${to}`,\n\t\t\t);\n\t\t}\n\t\tconst event: AgentTaskEventV1 = { at: now(), from: task.status, to, ...(reason === undefined ? {} : { reason }) };\n\t\tconst next: AgentTaskV1 = {\n\t\t\t...task,\n\t\t\tstatus: to,\n\t\t\t...(result !== undefined ? { result, resultBytes: JSON.stringify(result).length } : {}),\n\t\t\trevision: task.revision + 1,\n\t\t\tevents: Object.freeze([...task.events, event]),\n\t\t};\n\t\ttasks.set(taskId, next);\n\t\treturn next;\n\t};\n\n\treturn {\n\t\tcreateTask(input) {\n\t\t\tif (typeof input.taskId !== \"string\" || input.taskId.trim().length === 0) {\n\t\t\t\tthrow new AgentTaskError(\"invalid_transition\", \"Task id must be a non-empty string\");\n\t\t\t}\n\t\t\tif (typeof input.parentSessionId !== \"string\" || input.parentSessionId.trim().length === 0) {\n\t\t\t\tthrow new AgentTaskError(\"invalid_transition\", \"Task parentSessionId must be a non-empty string\");\n\t\t\t}\n\t\t\tif (input.deadlineAt !== undefined && input.deadlineAt <= now()) {\n\t\t\t\tthrow new AgentTaskError(\"deadline_passed\", `Deadline ${input.deadlineAt} is in the past (now=${now()})`);\n\t\t\t}\n\t\t\tconst task: AgentTaskV1 = {\n\t\t\t\ttaskId: input.taskId,\n\t\t\t\tparentSessionId: input.parentSessionId,\n\t\t\t\t...(input.childSessionId === undefined ? {} : { childSessionId: input.childSessionId }),\n\t\t\t\tstatus: \"pending\",\n\t\t\t\t...(input.deadlineAt === undefined ? {} : { deadlineAt: input.deadlineAt }),\n\t\t\t\trevision: 0,\n\t\t\t\tevents: Object.freeze([{ at: now(), from: null, to: \"pending\" as const }]),\n\t\t\t};\n\t\t\ttasks.set(input.taskId, task);\n\t\t\treturn task;\n\t\t},\n\t\tstart(taskId) {\n\t\t\tconst task = requireTask(taskId);\n\t\t\tif (\n\t\t\t\ttask.status === \"pending\" &&\n\t\t\t\t[...tasks.values()].filter((candidate) => candidate.status === \"running\").length >= maxConcurrent\n\t\t\t) {\n\t\t\t\tthrow new AgentTaskError(\"concurrency_limit\", `At most ${maxConcurrent} task(s) may run concurrently`);\n\t\t\t}\n\t\t\treturn transition(taskId, \"running\");\n\t\t},\n\t\tcomplete(taskId, result) {\n\t\t\tconst serialized = JSON.stringify(result);\n\t\t\tif (serialized.length > maxResultBytes) {\n\t\t\t\tthrow new AgentTaskError(\n\t\t\t\t\t\"result_too_large\",\n\t\t\t\t\t`Result of ${serialized.length} bytes exceeds the ${maxResultBytes}-byte limit`,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn transition(taskId, \"succeeded\", undefined, result);\n\t\t},\n\t\tfail(taskId, reason) {\n\t\t\treturn transition(taskId, \"failed\", reason);\n\t\t},\n\t\tcancel(taskId, reason) {\n\t\t\treturn transition(taskId, \"cancelled\", reason);\n\t\t},\n\t\tcheckDeadlines() {\n\t\t\tconst currentTime = now();\n\t\t\tconst expired: string[] = [];\n\t\t\tfor (const task of tasks.values()) {\n\t\t\t\tif (task.status === \"running\" && task.deadlineAt !== undefined && task.deadlineAt < currentTime) {\n\t\t\t\t\ttransition(task.taskId, \"timed_out\", `Deadline ${task.deadlineAt} passed`);\n\t\t\t\t\texpired.push(task.taskId);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn Object.freeze(expired);\n\t\t},\n\t\twaitUntilSettled(taskId, input) {\n\t\t\tconst until = input?.until ?? now();\n\t\t\tlet task = requireTask(taskId);\n\t\t\tcheckDeadlinesPass(until);\n\t\t\ttask = requireTask(taskId);\n\t\t\tif (!isSettled(task.status)) {\n\t\t\t\t// No real time passes in a deterministic registry: an unsettleable\n\t\t\t\t// task is reported as still running at the requested time.\n\t\t\t\treturn task;\n\t\t\t}\n\t\t\treturn task;\n\t\t},\n\t\tchildrenOf(parentSessionId) {\n\t\t\treturn Object.freeze([...tasks.values()].filter((task) => task.parentSessionId === parentSessionId));\n\t\t},\n\t\tget(taskId) {\n\t\t\treturn tasks.get(taskId);\n\t\t},\n\t\tbeginResult(taskId) {\n\t\t\trequireTask(taskId);\n\t\t\tconst running = [...tasks.values()].filter((task) => task.status === \"running\").length;\n\t\t\tif (running >= maxConcurrent) {\n\t\t\t\treturn { status: \"concurrency_limit\", maxConcurrent };\n\t\t\t}\n\t\t\treturn { status: \"claimed\" };\n\t\t},\n\t\tsubmitResult(taskId, result) {\n\t\t\treturn this.complete(taskId, result);\n\t\t},\n\t\taggregate(taskIds, input) {\n\t\t\tconst limitBytes = input?.limitBytes ?? Number.POSITIVE_INFINITY;\n\t\t\tlet used = 0;\n\t\t\tlet truncated = false;\n\t\t\tconst results = taskIds.map((taskId) => {\n\t\t\t\tconst task = tasks.get(taskId);\n\t\t\t\tif (!task) return { taskId, truncated: false, missing: true };\n\t\t\t\tif (task.result === undefined) return { taskId, truncated: false, missing: true };\n\t\t\t\tconst size = task.resultBytes ?? 0;\n\t\t\t\tif (used + size > limitBytes) {\n\t\t\t\t\ttruncated = true;\n\t\t\t\t\treturn { taskId, truncated: true, missing: false };\n\t\t\t\t}\n\t\t\t\tused += size;\n\t\t\t\treturn { taskId, result: task.result, truncated: false, missing: false };\n\t\t\t});\n\t\t\treturn { results: Object.freeze(results), truncated };\n\t\t},\n\t};\n\n\tfunction checkDeadlinesPass(until: number): void {\n\t\tfor (const task of tasks.values()) {\n\t\t\tif (task.status === \"running\" && task.deadlineAt !== undefined && task.deadlineAt < until) {\n\t\t\t\ttransition(task.taskId, \"timed_out\", `Deadline ${task.deadlineAt} passed`);\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction isSettled(status: AgentTaskStatusV1): boolean {\n\treturn status === \"succeeded\" || status === \"failed\" || status === \"cancelled\" || status === \"timed_out\";\n}\n\n/** 6.3 recovery: replays one task's lifecycle log into its settled state. */\nexport function replayTaskLog(log: {\n\treadonly taskId: string;\n\treadonly parentSessionId: string;\n\treadonly childSessionId?: string;\n\treadonly deadlineAt?: number;\n\treadonly result?: JsonValue;\n\treadonly events: readonly AgentTaskEventV1[];\n}): AgentTaskV1 {\n\tlet status: AgentTaskStatusV1 = \"pending\";\n\t// The registry counts the initial pending creation as revision 0 and each\n\t// subsequent transition as +1, so a replay mirrors that: events - 1.\n\tconst revision = Math.max(log.events.length - 1, 0);\n\tfor (const event of log.events) {\n\t\tif (event.from !== null && event.from !== status) {\n\t\t\tthrow new AgentTaskError(\n\t\t\t\t\"invalid_transition\",\n\t\t\t\t`Replay mismatch for ${log.taskId}: event.from=${event.from} but the task was ${status}`,\n\t\t\t);\n\t\t}\n\t\tstatus = event.to;\n\t}\n\treturn {\n\t\ttaskId: log.taskId,\n\t\tparentSessionId: log.parentSessionId,\n\t\t...(log.childSessionId === undefined ? {} : { childSessionId: log.childSessionId }),\n\t\tstatus,\n\t\t...(log.deadlineAt === undefined ? {} : { deadlineAt: log.deadlineAt }),\n\t\t...(log.result === undefined ? {} : { result: log.result }),\n\t\trevision,\n\t\tevents: Object.freeze([...log.events]),\n\t};\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type AgentTaskAdapter } from "@agent-forge/plugin-sdk";
|
|
2
|
+
import { type SubagentTaskChannelV1, type SubagentTaskProgressAdapterV1 } from "@agent-forge/protocol/subagent";
|
|
3
|
+
/**
|
|
4
|
+
* 远程 `AgentTaskAdapter`:与本地 SDK adapter 语义对齐——create 分配 runner 侧
|
|
5
|
+
* 会话;run resolve = prompt 完成,`aborted` 终局抛 `AbortError` 类取消错误
|
|
6
|
+
* (与 sdk.ts 的 aborted PromptOutcome 处理一致,由 guards 归到 cancelled,
|
|
7
|
+
* 不得落成 succeeded);cancel 幂等;dispose 释放。channel 关闭或读失败时
|
|
8
|
+
* 全部在途请求 reject(保留错误码),关闭后的新请求立即 reject 不悬挂。
|
|
9
|
+
*/
|
|
10
|
+
export declare function createRemoteAgentTaskAdapter(channel: SubagentTaskChannelV1): AgentTaskAdapter & SubagentTaskProgressAdapterV1;
|
|
11
|
+
//# sourceMappingURL=remote-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-adapter.d.ts","sourceRoot":"","sources":["../src/remote-adapter.ts"],"names":[],"mappings":"AAeA,OAAO,EAKN,KAAK,gBAAgB,EAKrB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAGN,KAAK,qBAAqB,EAG1B,KAAK,6BAA6B,EAElC,MAAM,gCAAgC,CAAC;AAuRxC;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAC3C,OAAO,EAAE,qBAAqB,GAC5B,gBAAgB,GAAG,6BAA6B,CAgGlD","sourcesContent":["/**\n * subagent-task 远程 adapter(D-045 / pooled 切片第 1 增量)。\n *\n * `AgentTaskAdapter` 的跨进程实现:经 `SubagentTaskChannelV1` 与 runner 侧\n * server 对话(create/run/cancel/dispose/result)。run 语义与本地一致——\n * `host.run()` resolve = 该次 prompt 完成(aborted 是正常结果);signal 中止\n * 时发 cancel 并等待 run 终态。`session.getMessages()` 为同步签名,消息在\n * run 终态时经 result op 预取缓存。本文件不做进程 spawn(runner 生命周期属\n * pooled 切片第 2 增量)。\n *\n * 迁自宿主 `src/plugins/subagent-task/remote-adapter.ts`(D-075 S4 第三批);公共契约\n * 改自 plugin-sdk,wire 协议/进度/server 事实类型改自 @agent-forge/protocol(宿主侧\n * 同名模块已下沉该包)。\n */\nimport { randomUUID } from \"node:crypto\";\nimport {\n\tAGENT_TASK_CHILD_PROFILE_KEY,\n\tAGENT_TASK_PARENT_SESSION_KEY,\n\tAGENT_TASK_PARENT_TRACE_KEY,\n\tAGENT_TASK_RESUME_SESSION_KEY,\n\ttype AgentTaskAdapter,\n\ttype AgentTaskCreateRequest,\n\ttype AgentTaskHost,\n\ttype ModelFactsViewV1,\n\ttype SessionAPI,\n} from \"@agent-forge/plugin-sdk\";\nimport {\n\tSUBAGENT_TASK_PROTOCOL_VERSION,\n\ttype SubagentTaskAppliedProfileV1,\n\ttype SubagentTaskChannelV1,\n\ttype SubagentTaskEnvelopeV1,\n\ttype SubagentTaskOkResponseEnvelopeV1,\n\ttype SubagentTaskProgressAdapterV1,\n\tSubagentTaskProtocolError,\n} from \"@agent-forge/protocol/subagent\";\n\ninterface PendingRequest {\n\tresolve(env: SubagentTaskOkResponseEnvelopeV1): void;\n\treject(error: SubagentTaskProtocolError): void;\n}\n\n/** 与本地 SDK adapter 相同的取消原因规范化:非字符串 reason 记为 \"cancelled\"。 */\nfunction abortReasonText(signal: AbortSignal): string {\n\treturn typeof signal.reason === \"string\" ? signal.reason : \"cancelled\";\n}\n\n/** 身份违规(跨任务应答):一律硬失败,绝不降级成\"拿不到结果\"。 */\nfunction isCrossTaskViolation(error: unknown): boolean {\n\treturn typeof error === \"object\" && error !== null && (error as { code?: unknown }).code === \"cross_task_response\";\n}\n\n/** 与 sdk.ts abortError 同形的取消类错误:name=\"AbortError\",由 guards 归到 cancelled。 */\nfunction abortLikeError(message: string): Error {\n\tconst error = new Error(message);\n\terror.name = \"AbortError\";\n\treturn error;\n}\n\n/**\n * 远程 host.session 只实现调度路径实际消费的面:getSessionId/getMessages\n * (run 终态时预取的有界快照)/isIdle/abort(映射为 cancel)/waitForIdle。\n * prompt 为桩——远程路径经 host.run 走 run op,不支持对 remote session 直接\n * prompt。桩边界以 docs/design/子代理pooled切片与Agent服务协议边界设计.md §4 为准。\n */\n\nfunction createRemoteSessionProxy(taskId: string, client: RemoteTaskClient, agentSessionId?: string): SessionAPI {\n\t// 远程调度路径不使用的 SessionAPI 成员:显式桩(抛协议错误),不静默返回假数据。\n\tconst unsupportedAsync = (name: string): (() => Promise<never>) => {\n\t\treturn async () => {\n\t\t\tthrow new SubagentTaskProtocolError(`remote session does not support ${name}`);\n\t\t};\n\t};\n\tconst unsupportedSync = (name: string): (() => never) => {\n\t\treturn () => {\n\t\t\tthrow new SubagentTaskProtocolError(`remote session does not support ${name}`);\n\t\t};\n\t};\n\treturn {\n\t\tgetSessionId: () => taskId,\n\t\t// 叶子会话自身身份:父侧记录与回读用它(而不是 wire 任务 id)。\n\t\t...(agentSessionId === undefined ? {} : { getAgentSessionId: () => agentSessionId }),\n\t\tgetMessages: () => client.cachedMessages(taskId),\n\t\tisIdle: () => !client.hasActiveRun(taskId),\n\t\tprompt: unsupportedAsync(\"direct prompt; use AgentTaskHost.run\"),\n\t\tabort: () => client.cancelTask(taskId, \"session abort\"),\n\t\twaitForIdle: () => client.waitForIdle(taskId),\n\t\tappendEntry: unsupportedSync(\"appendEntry\"),\n\t\tgetBranchEntries: unsupportedSync(\"getBranchEntries\"),\n\t\t// 事实面(runner 自报):档案核验需要它们。runner 未回事实时**不声明**这些成员——\n\t\t// 上层因此看到\"宿主不暴露事实\"并按无法自证处理,而不是被空数组糊弄成通过。\n\t\t...(client.appliedProfile(taskId)?.activeTools === undefined\n\t\t\t? {}\n\t\t\t: { getActiveTools: (): string[] => [...(client.appliedProfile(taskId)?.activeTools ?? [])] }),\n\t\t...(client.appliedProfile(taskId)?.model === undefined\n\t\t\t? {}\n\t\t\t: { getModel: (): ModelFactsViewV1 => ({ ...(client.appliedProfile(taskId)?.model as ModelFactsViewV1) }) }),\n\t\t// 父→子引导(D-060 S5 pooled parity):经 wire steer op 到叶子会话自身 steer。\n\t\tsteer: (text: string) => client.steerTask(taskId, text),\n\t};\n}\n\nclass RemoteTaskClient {\n\tprivate readonly channel: SubagentTaskChannelV1;\n\tprivate requestSequence = 0;\n\tprivate readonly pending = new Map<string, PendingRequest>();\n\tprivate readonly progressListeners = new Set<(env: SubagentTaskEnvelopeV1) => void>();\n\tprivate readonly taskProgressListeners = new Map<string, Set<(message: string | undefined, seq: number) => void>>();\n\tprivate readonly activeRuns = new Map<string, number>();\n\t/** 每个任务当前 run 的 runId:进度事件按此校验,陈旧或串台 run 的进度不得投递。 */\n\tprivate readonly activeRunIds = new Map<string, string>();\n\tprivate readonly idleWaiters = new Map<string, Array<() => void>>();\n\tprivate readonly messageCache = new Map<string, readonly unknown[]>();\n\t/** create 应答里 runner 自报的收窄事实(工具面/模型/模式):代理据此暴露可核验面。 */\n\tprivate readonly appliedProfiles = new Map<string, SubagentTaskAppliedProfileV1>();\n\tprivate reader: Promise<void> | undefined;\n\tprivate closed = false;\n\n\tconstructor(channel: SubagentTaskChannelV1) {\n\t\tthis.channel = channel;\n\t}\n\n\tstart(): void {\n\t\tif (this.reader !== undefined) return;\n\t\tthis.reader = (async () => {\n\t\t\ttry {\n\t\t\t\tfor await (const env of this.channel) {\n\t\t\t\t\tthis.dispatch(env);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tthis.failAllPending(error instanceof Error ? error : new SubagentTaskProtocolError(\"channel read failed\"));\n\t\t\t}\n\t\t\t// 通道关闭 = 对端(runner 进程)已消失:在途任务按 runner_lost 结算,\n\t\t\t// 由调度器投影为 failed + reasonCode subagent_runner_lost(D-044 §4.3)。\n\t\t\tthis.failAllPending(new SubagentTaskProtocolError(\"subagent runner lost: channel closed\", \"runner_lost\"));\n\t\t})();\n\t\tvoid this.reader.catch(() => {});\n\t}\n\n\tprivate dispatch(env: SubagentTaskEnvelopeV1): void {\n\t\tif (env.kind === \"response\") {\n\t\t\tconst pending = this.pending.get(env.id);\n\t\t\tif (pending === undefined) return; // 未知响应:丢弃(迟到回执)\n\t\t\tthis.pending.delete(env.id);\n\t\t\tif (env.ok) pending.resolve(env);\n\t\t\telse pending.reject(new SubagentTaskProtocolError(env.message, env.code));\n\t\t\treturn;\n\t\t}\n\t\tif (env.kind === \"event\") {\n\t\t\t// 隔离校验:只有\"该任务当前 run\"的进度才投递。runner 侧陈旧批次、\n\t\t\t// 或身份错标(B 任务的进度挂在 A 的 taskId 上)一律丢弃,宁可少一条\n\t\t\t// 提示也不让父侧把别的子会话的进度算到当前任务上。\n\t\t\tconst expectedRunId = this.activeRunIds.get(env.taskId);\n\t\t\tif (expectedRunId === undefined || env.runId !== expectedRunId) return;\n\t\t\tfor (const listener of this.progressListeners) listener(env);\n\t\t\tconst taskListeners = this.taskProgressListeners.get(env.taskId);\n\t\t\tif (taskListeners !== undefined) {\n\t\t\t\tfor (const listener of taskListeners) listener(env.message, env.seq);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate failAllPending(error: SubagentTaskProtocolError): void {\n\t\tthis.closed = true;\n\t\tfor (const pending of this.pending.values()) pending.reject(error);\n\t\tthis.pending.clear();\n\t}\n\n\tcall(\n\t\trequest: Omit<Extract<SubagentTaskEnvelopeV1, { kind: \"request\" }>, \"v\" | \"kind\" | \"id\">,\n\t): Promise<SubagentTaskOkResponseEnvelopeV1> {\n\t\t// 通道已关闭后不再入队:立即 reject,避免调用方永久悬挂。\n\t\tif (this.closed) {\n\t\t\treturn Promise.reject(new SubagentTaskProtocolError(\"subagent runner lost: channel closed\", \"runner_lost\"));\n\t\t}\n\t\tthis.start();\n\t\tconst id = `req:${++this.requestSequence}:${randomUUID().slice(0, 8)}`;\n\t\treturn new Promise<SubagentTaskOkResponseEnvelopeV1>((resolve, reject) => {\n\t\t\tthis.pending.set(id, { resolve, reject });\n\t\t\tvoid this.channel.write({ v: SUBAGENT_TASK_PROTOCOL_VERSION, kind: \"request\", id, ...request });\n\t\t});\n\t}\n\n\tonProgress(listener: (env: SubagentTaskEnvelopeV1) => void): () => void {\n\t\tthis.progressListeners.add(listener);\n\t\treturn () => this.progressListeners.delete(listener);\n\t}\n\n\t/**\n\t * 任务作用域调用:请求与应答必须是同一个任务。runner 若就 A 的请求回 B 的\n\t * taskId/runId(身份错标、状态错位),这里按 `cross_task_response` 硬失败,\n\t * 绝不把别的子会话的消息缓存成 A 的结果。\n\t */\n\tasync callForTask(\n\t\ttaskId: string,\n\t\trequest: Omit<Extract<SubagentTaskEnvelopeV1, { kind: \"request\" }>, \"v\" | \"kind\" | \"id\" | \"taskId\">,\n\t\trunId?: string,\n\t): Promise<SubagentTaskOkResponseEnvelopeV1> {\n\t\tconst response = await this.call({ ...request, taskId, ...(runId === undefined ? {} : { runId }) });\n\t\tif (response.taskId !== taskId) {\n\t\t\tthrow new SubagentTaskProtocolError(\n\t\t\t\t`runner answered task ${String(response.taskId)} for a task ${taskId} request`,\n\t\t\t\t\"cross_task_response\",\n\t\t\t);\n\t\t}\n\t\tif (runId !== undefined && response.runId !== runId) {\n\t\t\tthrow new SubagentTaskProtocolError(\n\t\t\t\t`runner answered run ${String(response.runId)} for a run ${runId} request`,\n\t\t\t\t\"cross_task_response\",\n\t\t\t);\n\t\t}\n\t\treturn response;\n\t}\n\n\t/** 订阅某个任务(wire taskId)的进度事件;返回退订函数。 */\n\tonTaskProgress(taskId: string, listener: (message: string | undefined, seq: number) => void): () => void {\n\t\tconst listeners = this.taskProgressListeners.get(taskId);\n\t\tif (listeners === undefined) this.taskProgressListeners.set(taskId, new Set([listener]));\n\t\telse listeners.add(listener);\n\t\treturn () => {\n\t\t\tconst current = this.taskProgressListeners.get(taskId);\n\t\t\tif (current === undefined) return;\n\t\t\tcurrent.delete(listener);\n\t\t\tif (current.size === 0) this.taskProgressListeners.delete(taskId);\n\t\t};\n\t}\n\n\t/** 登记 in-flight run 并接管 signal→cancel 级联;释放必须经 releaseRun。 */\n\ttrackRun(taskId: string, runId: string, signal: AbortSignal): void {\n\t\tthis.activeRuns.set(taskId, (this.activeRuns.get(taskId) ?? 0) + 1);\n\t\tthis.activeRunIds.set(taskId, runId);\n\t\t// 一次 run 至多挂一个 abort 监听;server 侧 cancel 幂等,重复触发无害。\n\t\tsignal.addEventListener(\n\t\t\t\"abort\",\n\t\t\t() => {\n\t\t\t\tvoid this.cancelTask(taskId, abortReasonText(signal)).catch(() => {});\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\t/** run 终态(resolve 或 reject)后调用:递减活跃计数并唤醒 waitForIdle。 */\n\treleaseRun(taskId: string, runId?: string): void {\n\t\tconst current = this.activeRuns.get(taskId) ?? 0;\n\t\tif (current <= 1) {\n\t\t\tthis.activeRuns.delete(taskId);\n\t\t\t// 仅释放属于该 run 的 runId 槽:并发的同名释放不会顶掉新 run 的登记。\n\t\t\tif (runId === undefined || this.activeRunIds.get(taskId) === runId) {\n\t\t\t\tthis.activeRunIds.delete(taskId);\n\t\t\t}\n\t\t\tthis.wakeIdle(taskId);\n\t\t\treturn;\n\t\t}\n\t\tthis.activeRuns.set(taskId, current - 1);\n\t}\n\n\thasActiveRun(taskId: string): boolean {\n\t\treturn (this.activeRuns.get(taskId) ?? 0) > 0;\n\t}\n\n\twaitForIdle(taskId: string): Promise<void> {\n\t\tif (!this.hasActiveRun(taskId)) return Promise.resolve();\n\t\treturn new Promise<void>((resolve) => {\n\t\t\tconst waiters = this.idleWaiters.get(taskId);\n\t\t\tif (waiters === undefined) this.idleWaiters.set(taskId, [resolve]);\n\t\t\telse waiters.push(resolve);\n\t\t});\n\t}\n\n\tprivate wakeIdle(taskId: string): void {\n\t\tfor (const wake of this.idleWaiters.get(taskId)?.splice(0) ?? []) wake();\n\t}\n\n\tasync refreshMessages(taskId: string): Promise<void> {\n\t\tconst response = await this.callForTask(taskId, { op: \"result\" });\n\t\tthis.messageCache.set(taskId, response.messages ?? []);\n\t}\n\n\tcachedMessages(taskId: string): readonly unknown[] {\n\t\treturn this.messageCache.get(taskId) ?? [];\n\t}\n\n\t/**\n\t * 释放某任务的缓存:终态后消息快照与进度订阅都不再需要,长寿命 runner 连接\n\t * 不能按任务数无界增长(每条快照上限是 settle 预算)。释放顺序由调度器保证:\n\t * settle 先读 getMessages,再调用 host.dispose。\n\t */\n\tdropTask(taskId: string): void {\n\t\tthis.messageCache.delete(taskId);\n\t\tthis.taskProgressListeners.delete(taskId);\n\t\tthis.activeRunIds.delete(taskId);\n\t\tthis.appliedProfiles.delete(taskId);\n\t}\n\n\t/** 记录 runner 自报的档案事实(create 应答)。 */\n\trememberAppliedProfile(taskId: string, applied: SubagentTaskAppliedProfileV1 | undefined): void {\n\t\tif (applied === undefined) return;\n\t\tthis.appliedProfiles.set(taskId, applied);\n\t}\n\n\tappliedProfile(taskId: string): SubagentTaskAppliedProfileV1 | undefined {\n\t\treturn this.appliedProfiles.get(taskId);\n\t}\n\n\tasync cancelTask(taskId: string, reason?: string): Promise<void> {\n\t\tawait this.callForTask(taskId, { op: \"cancel\", ...(reason === undefined ? {} : { reason }) });\n\t}\n\n\t/** 父→子引导(D-060 S5 pooled parity):steer op 到 runner 叶子会话。 */\n\tasync steerTask(taskId: string, text: string): Promise<void> {\n\t\tawait this.callForTask(taskId, { op: \"steer\", text });\n\t}\n}\n\n/**\n * 远程 `AgentTaskAdapter`:与本地 SDK adapter 语义对齐——create 分配 runner 侧\n * 会话;run resolve = prompt 完成,`aborted` 终局抛 `AbortError` 类取消错误\n * (与 sdk.ts 的 aborted PromptOutcome 处理一致,由 guards 归到 cancelled,\n * 不得落成 succeeded);cancel 幂等;dispose 释放。channel 关闭或读失败时\n * 全部在途请求 reject(保留错误码),关闭后的新请求立即 reject 不悬挂。\n */\nexport function createRemoteAgentTaskAdapter(\n\tchannel: SubagentTaskChannelV1,\n): AgentTaskAdapter & SubagentTaskProgressAdapterV1 {\n\tconst client = new RemoteTaskClient(channel);\n\tconst liveTaskIds = new Set<string>();\n\treturn {\n\t\t/** D-044 §5.6:把 runner 侧的 progress 事件按 wire taskId 转给上层订阅者。 */\n\t\tonTaskProgress(taskId: string, listener: (message: string | undefined, seq: number) => void): () => void {\n\t\t\treturn client.onTaskProgress(taskId, listener);\n\t\t},\n\t\tcreate: async (request: AgentTaskCreateRequest): Promise<AgentTaskHost> => {\n\t\t\t// 子任务档案随 create 过线:runner 侧叶子会话按它收窄,并在应答里回实际事实。\n\t\t\tconst profile = request.metadata?.[AGENT_TASK_CHILD_PROFILE_KEY];\n\t\t\tconst parentSessionId = request.metadata?.[AGENT_TASK_PARENT_SESSION_KEY];\n\t\t\t// resume 目标(D-060 S5 pooled parity):runner 打开既有会话文件续写,不新建。\n\t\t\tconst resumeSessionId = request.metadata?.[AGENT_TASK_RESUME_SESSION_KEY];\n\t\t\t// 回合级血缘(第 3 期第二批):创建子会话的父回合 correlationId,仅 create 新建消费。\n\t\t\tconst parentTraceId = request.metadata?.[AGENT_TASK_PARENT_TRACE_KEY];\n\t\t\tconst created = await client.call({\n\t\t\t\top: \"create\",\n\t\t\t\tprompt: request.prompt,\n\t\t\t\t...(profile === undefined ? {} : { profile }),\n\t\t\t\t...(typeof parentSessionId === \"string\" ? { parentSessionId } : {}),\n\t\t\t\t...(typeof resumeSessionId === \"string\" ? { resumeSessionId } : {}),\n\t\t\t\t...(typeof parentTraceId === \"string\" ? { parentTraceId } : {}),\n\t\t\t});\n\t\t\tconst taskId = created.taskId;\n\t\t\tif (taskId === undefined) {\n\t\t\t\tthrow new SubagentTaskProtocolError(\"create response missing taskId\", \"invalid_request\");\n\t\t\t}\n\t\t\t// 任务 id 必须唯一:runner 若把已存活任务的 id 再发一次,就是身份复用,\n\t\t\t// 之后两个任务的应答将无法区分——直接硬失败而不是继续跑。\n\t\t\tif (liveTaskIds.has(taskId)) {\n\t\t\t\tthrow new SubagentTaskProtocolError(\n\t\t\t\t\t`runner reused the id of a live task: ${taskId}`,\n\t\t\t\t\t\"cross_task_response\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tliveTaskIds.add(taskId);\n\t\t\tclient.rememberAppliedProfile(taskId, created.appliedProfile);\n\t\t\t// 叶子会话自身的 sessionId(D-060 S1):宿主可见的 getSessionId() 是 wire 任务 id,\n\t\t\t// 真正的 agent 会话 id 走 getAgentSessionId 暴露。\n\t\t\tconst agentSessionId = created.sessionId;\n\t\t\tconst host: AgentTaskHost = {\n\t\t\t\tsession: createRemoteSessionProxy(taskId, client, agentSessionId),\n\t\t\t\trun: (prompt: string, signal: AbortSignal): Promise<void> => {\n\t\t\t\t\tconst runId = `run:${randomUUID().slice(0, 8)}`;\n\t\t\t\t\tif (signal.aborted) {\n\t\t\t\t\t\t// 与本地 SDK adapter 一致:入口即中止只本地拒绝,不向 runner 发取消——\n\t\t\t\t\t\t// 什么都没有启动,任务保持可用(发取消会把任务标记为终态)。\n\t\t\t\t\t\treturn Promise.reject(abortLikeError(`run aborted before start: ${abortReasonText(signal)}`));\n\t\t\t\t\t}\n\t\t\t\t\tclient.trackRun(taskId, runId, signal);\n\t\t\t\t\treturn client\n\t\t\t\t\t\t.callForTask(taskId, { op: \"run\", prompt }, runId)\n\t\t\t\t\t\t.then(async (response) => {\n\t\t\t\t\t\t\t// run 终态后预取有界消息,供调度器 settle 同步读取。\n\t\t\t\t\t\t\t// 预取失败本身可容忍(拿不到消息不等于任务失败),但身份违规不可\n\t\t\t\t\t\t\t// 容忍:那是 runner 把别的任务的消息算到了这个任务上。\n\t\t\t\t\t\t\tawait client.refreshMessages(taskId).catch((error: unknown) => {\n\t\t\t\t\t\t\t\tif (isCrossTaskViolation(error)) throw error;\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tif (response.status === \"cancelled\") {\n\t\t\t\t\t\t\t\tthrow abortLikeError(\"agent task run was cancelled\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.catch((error: unknown) => {\n\t\t\t\t\t\t\t// 取消先于 run 到达:runner 以 task_terminal 拒绝启动。父侧确实请求过取消,\n\t\t\t\t\t\t\t// 因此仍按取消类错误结算(与\"aborted 终局\"同一分类),不落成 failed。\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tsignal.aborted &&\n\t\t\t\t\t\t\t\ttypeof error === \"object\" &&\n\t\t\t\t\t\t\t\terror !== null &&\n\t\t\t\t\t\t\t\t(error as { code?: unknown }).code === \"task_terminal\"\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tthrow abortLikeError(`agent task run was cancelled before start: ${abortReasonText(signal)}`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tthrow error;\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.finally(() => client.releaseRun(taskId, runId));\n\t\t\t\t},\n\t\t\t\tcancel: (reason?: string): Promise<void> => client.cancelTask(taskId, reason),\n\t\t\t\tdispose: async (): Promise<void> => {\n\t\t\t\t\tliveTaskIds.delete(taskId);\n\t\t\t\t\tawait client.callForTask(taskId, { op: \"dispose\" });\n\t\t\t\t\tclient.dropTask(taskId);\n\t\t\t\t},\n\t\t\t};\n\t\t\treturn host;\n\t\t},\n\t\tdispose: async (): Promise<void> => {\n\t\t\t// 宿主会话销毁时释放全部未 dispose 的任务(逐个容错,不阻断其余回收)。\n\t\t\tfor (const taskId of [...liveTaskIds]) {\n\t\t\t\tliveTaskIds.delete(taskId);\n\t\t\t\tawait client.callForTask(taskId, { op: \"dispose\" }).catch(() => {});\n\t\t\t}\n\t\t},\n\t};\n}\n"]}
|