@caupulican/pi-adaptative 0.81.41 → 0.81.42
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/CHANGELOG.md +16 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/commands/text.py +5 -1
- package/dist/bundled-resources/runtimes/pi-shell-engine/context.py +3 -3
- package/dist/bundled-resources/runtimes/pi-shell-engine/errors.py +8 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/exec.py +26 -3
- package/dist/bundled-resources/runtimes/pi-shell-engine/expand.py +3 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/main.py +4 -1
- package/dist/bundled-resources/runtimes/pi-shell-engine/state.py +2 -1
- package/dist/bundled-resources/runtimes/pi-shell-engine/tokens.py +2 -0
- package/dist/core/agent-paths.d.ts +32 -0
- package/dist/core/agent-paths.d.ts.map +1 -1
- package/dist/core/agent-paths.js +65 -0
- package/dist/core/agent-paths.js.map +1 -1
- package/dist/core/agent-session-contracts.d.ts +19 -1
- package/dist/core/agent-session-contracts.d.ts.map +1 -1
- package/dist/core/agent-session-contracts.js.map +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +4 -9
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/autonomy/contracts.d.ts +2 -0
- package/dist/core/autonomy/contracts.d.ts.map +1 -1
- package/dist/core/autonomy/contracts.js.map +1 -1
- package/dist/core/autonomy/envelope-enforcement.d.ts.map +1 -1
- package/dist/core/autonomy/envelope-enforcement.js +4 -9
- package/dist/core/autonomy/envelope-enforcement.js.map +1 -1
- package/dist/core/autonomy/lane-tool-surface.d.ts +3 -1
- package/dist/core/autonomy/lane-tool-surface.d.ts.map +1 -1
- package/dist/core/autonomy/lane-tool-surface.js +17 -4
- package/dist/core/autonomy/lane-tool-surface.js.map +1 -1
- package/dist/core/autonomy/path-scope.d.ts +14 -0
- package/dist/core/autonomy/path-scope.d.ts.map +1 -1
- package/dist/core/autonomy/path-scope.js +37 -6
- package/dist/core/autonomy/path-scope.js.map +1 -1
- package/dist/core/background-lane-controller.d.ts +27 -1
- package/dist/core/background-lane-controller.d.ts.map +1 -1
- package/dist/core/background-lane-controller.js +32 -1
- package/dist/core/background-lane-controller.js.map +1 -1
- package/dist/core/delegation/managed-lane-controller.d.ts +11 -1
- package/dist/core/delegation/managed-lane-controller.d.ts.map +1 -1
- package/dist/core/delegation/managed-lane-controller.js +84 -41
- package/dist/core/delegation/managed-lane-controller.js.map +1 -1
- package/dist/core/delegation/session-worker-claim.d.ts.map +1 -1
- package/dist/core/delegation/session-worker-claim.js +4 -3
- package/dist/core/delegation/session-worker-claim.js.map +1 -1
- package/dist/core/delegation/worker-action-journal.d.ts +73 -0
- package/dist/core/delegation/worker-action-journal.d.ts.map +1 -0
- package/dist/core/delegation/worker-action-journal.js +297 -0
- package/dist/core/delegation/worker-action-journal.js.map +1 -0
- package/dist/core/delegation/worker-actions.d.ts +36 -1
- package/dist/core/delegation/worker-actions.d.ts.map +1 -1
- package/dist/core/delegation/worker-actions.js +171 -46
- package/dist/core/delegation/worker-actions.js.map +1 -1
- package/dist/core/delegation/worker-agent-control-coordinator.d.ts +75 -0
- package/dist/core/delegation/worker-agent-control-coordinator.d.ts.map +1 -0
- package/dist/core/delegation/worker-agent-control-coordinator.js +218 -0
- package/dist/core/delegation/worker-agent-control-coordinator.js.map +1 -0
- package/dist/core/delegation/worker-agent-control.d.ts +66 -0
- package/dist/core/delegation/worker-agent-control.d.ts.map +1 -0
- package/dist/core/delegation/worker-agent-control.js +163 -0
- package/dist/core/delegation/worker-agent-control.js.map +1 -0
- package/dist/core/delegation/worker-attempt-executor.d.ts +80 -0
- package/dist/core/delegation/worker-attempt-executor.d.ts.map +1 -0
- package/dist/core/delegation/worker-attempt-executor.js +343 -0
- package/dist/core/delegation/worker-attempt-executor.js.map +1 -0
- package/dist/core/delegation/worker-claim.d.ts +30 -0
- package/dist/core/delegation/worker-claim.d.ts.map +1 -1
- package/dist/core/delegation/worker-claim.js +205 -53
- package/dist/core/delegation/worker-claim.js.map +1 -1
- package/dist/core/delegation/worker-conversation-store.d.ts +116 -0
- package/dist/core/delegation/worker-conversation-store.d.ts.map +1 -0
- package/dist/core/delegation/worker-conversation-store.js +442 -0
- package/dist/core/delegation/worker-conversation-store.js.map +1 -0
- package/dist/core/delegation/worker-delegation-controller.d.ts +21 -5
- package/dist/core/delegation/worker-delegation-controller.d.ts.map +1 -1
- package/dist/core/delegation/worker-delegation-controller.js +386 -251
- package/dist/core/delegation/worker-delegation-controller.js.map +1 -1
- package/dist/core/delegation/worker-delegation-request.d.ts +14 -0
- package/dist/core/delegation/worker-delegation-request.d.ts.map +1 -1
- package/dist/core/delegation/worker-delegation-request.js.map +1 -1
- package/dist/core/delegation/worker-dispatch-scheduler.d.ts +4 -2
- package/dist/core/delegation/worker-dispatch-scheduler.d.ts.map +1 -1
- package/dist/core/delegation/worker-dispatch-scheduler.js +16 -6
- package/dist/core/delegation/worker-dispatch-scheduler.js.map +1 -1
- package/dist/core/delegation/worker-execution-policy.d.ts +2 -1
- package/dist/core/delegation/worker-execution-policy.d.ts.map +1 -1
- package/dist/core/delegation/worker-execution-policy.js +5 -7
- package/dist/core/delegation/worker-execution-policy.js.map +1 -1
- package/dist/core/delegation/worker-lifecycle.d.ts +44 -1
- package/dist/core/delegation/worker-lifecycle.d.ts.map +1 -1
- package/dist/core/delegation/worker-lifecycle.js +120 -0
- package/dist/core/delegation/worker-lifecycle.js.map +1 -1
- package/dist/core/delegation/worker-process-owner.d.ts +18 -0
- package/dist/core/delegation/worker-process-owner.d.ts.map +1 -0
- package/dist/core/delegation/worker-process-owner.js +54 -0
- package/dist/core/delegation/worker-process-owner.js.map +1 -0
- package/dist/core/delegation/worker-profile-resolver.d.ts +5 -1
- package/dist/core/delegation/worker-profile-resolver.d.ts.map +1 -1
- package/dist/core/delegation/worker-profile-resolver.js +8 -0
- package/dist/core/delegation/worker-profile-resolver.js.map +1 -1
- package/dist/core/delegation/worker-recovery-coordinator.d.ts +54 -0
- package/dist/core/delegation/worker-recovery-coordinator.d.ts.map +1 -0
- package/dist/core/delegation/worker-recovery-coordinator.js +181 -0
- package/dist/core/delegation/worker-recovery-coordinator.js.map +1 -0
- package/dist/core/delegation/worker-resource-catalog.d.ts +32 -0
- package/dist/core/delegation/worker-resource-catalog.d.ts.map +1 -0
- package/dist/core/delegation/worker-resource-catalog.js +116 -0
- package/dist/core/delegation/worker-resource-catalog.js.map +1 -0
- package/dist/core/delegation/worker-resource-materializer.d.ts +47 -0
- package/dist/core/delegation/worker-resource-materializer.d.ts.map +1 -0
- package/dist/core/delegation/worker-resource-materializer.js +137 -0
- package/dist/core/delegation/worker-resource-materializer.js.map +1 -0
- package/dist/core/delegation/worker-runner.d.ts +3 -1
- package/dist/core/delegation/worker-runner.d.ts.map +1 -1
- package/dist/core/delegation/worker-runner.js +189 -54
- package/dist/core/delegation/worker-runner.js.map +1 -1
- package/dist/core/delegation/worker-terminal-finalizer.d.ts +23 -0
- package/dist/core/delegation/worker-terminal-finalizer.d.ts.map +1 -0
- package/dist/core/delegation/worker-terminal-finalizer.js +12 -0
- package/dist/core/delegation/worker-terminal-finalizer.js.map +1 -0
- package/dist/core/delegation/worker-write-reservation-coordinator.d.ts +38 -0
- package/dist/core/delegation/worker-write-reservation-coordinator.d.ts.map +1 -0
- package/dist/core/delegation/worker-write-reservation-coordinator.js +107 -0
- package/dist/core/delegation/worker-write-reservation-coordinator.js.map +1 -0
- package/dist/core/delegation/worker-write-reservation.d.ts +90 -0
- package/dist/core/delegation/worker-write-reservation.d.ts.map +1 -0
- package/dist/core/delegation/worker-write-reservation.js +348 -0
- package/dist/core/delegation/worker-write-reservation.js.map +1 -0
- package/dist/core/models/model-endpoint.d.ts +3 -0
- package/dist/core/models/model-endpoint.d.ts.map +1 -0
- package/dist/core/models/model-endpoint.js +11 -0
- package/dist/core/models/model-endpoint.js.map +1 -0
- package/dist/core/orchestration/agent-resume.d.ts +2 -0
- package/dist/core/orchestration/agent-resume.d.ts.map +1 -1
- package/dist/core/orchestration/agent-resume.js +7 -0
- package/dist/core/orchestration/agent-resume.js.map +1 -1
- package/dist/core/orchestration/attempt-usage.d.ts +28 -0
- package/dist/core/orchestration/attempt-usage.d.ts.map +1 -0
- package/dist/core/orchestration/attempt-usage.js +168 -0
- package/dist/core/orchestration/attempt-usage.js.map +1 -0
- package/dist/core/orchestration/capability-gateway.d.ts +19 -1
- package/dist/core/orchestration/capability-gateway.d.ts.map +1 -1
- package/dist/core/orchestration/capability-gateway.js +128 -28
- package/dist/core/orchestration/capability-gateway.js.map +1 -1
- package/dist/core/orchestration/contracts.d.ts +35 -0
- package/dist/core/orchestration/contracts.d.ts.map +1 -1
- package/dist/core/orchestration/contracts.js +17 -0
- package/dist/core/orchestration/contracts.js.map +1 -1
- package/dist/core/orchestration/delegation-ledger.d.ts +17 -2
- package/dist/core/orchestration/delegation-ledger.d.ts.map +1 -1
- package/dist/core/orchestration/delegation-ledger.js +78 -3
- package/dist/core/orchestration/delegation-ledger.js.map +1 -1
- package/dist/core/orchestration/event-store.d.ts +1 -0
- package/dist/core/orchestration/event-store.d.ts.map +1 -1
- package/dist/core/orchestration/event-store.js +78 -38
- package/dist/core/orchestration/event-store.js.map +1 -1
- package/dist/core/orchestration/profile-registry.d.ts.map +1 -1
- package/dist/core/orchestration/profile-registry.js +81 -41
- package/dist/core/orchestration/profile-registry.js.map +1 -1
- package/dist/core/orchestration/profile-store.d.ts.map +1 -1
- package/dist/core/orchestration/profile-store.js +18 -4
- package/dist/core/orchestration/profile-store.js.map +1 -1
- package/dist/core/orchestration/risk-budget.d.ts +2 -0
- package/dist/core/orchestration/risk-budget.d.ts.map +1 -1
- package/dist/core/orchestration/risk-budget.js +26 -0
- package/dist/core/orchestration/risk-budget.js.map +1 -1
- package/dist/core/orchestration/task-runtime.d.ts +13 -1
- package/dist/core/orchestration/task-runtime.d.ts.map +1 -1
- package/dist/core/orchestration/task-runtime.js +127 -16
- package/dist/core/orchestration/task-runtime.js.map +1 -1
- package/dist/core/orchestration/worker-execution-contract.d.ts +3 -1
- package/dist/core/orchestration/worker-execution-contract.d.ts.map +1 -1
- package/dist/core/orchestration/worker-execution-contract.js +75 -23
- package/dist/core/orchestration/worker-execution-contract.js.map +1 -1
- package/dist/core/orchestration/worker-result-adapter.d.ts +9 -3
- package/dist/core/orchestration/worker-result-adapter.d.ts.map +1 -1
- package/dist/core/orchestration/worker-result-adapter.js +167 -14
- package/dist/core/orchestration/worker-result-adapter.js.map +1 -1
- package/dist/core/reflection-controller.d.ts +1 -1
- package/dist/core/reflection-controller.d.ts.map +1 -1
- package/dist/core/reflection-controller.js +90 -54
- package/dist/core/reflection-controller.js.map +1 -1
- package/dist/core/research/evidence-bundle.d.ts +20 -0
- package/dist/core/research/evidence-bundle.d.ts.map +1 -1
- package/dist/core/research/evidence-bundle.js +173 -69
- package/dist/core/research/evidence-bundle.js.map +1 -1
- package/dist/core/runtime-builder.d.ts +7 -0
- package/dist/core/runtime-builder.d.ts.map +1 -1
- package/dist/core/runtime-builder.js +15 -1
- package/dist/core/runtime-builder.js.map +1 -1
- package/dist/core/session-artifact-bundle.d.ts +35 -0
- package/dist/core/session-artifact-bundle.d.ts.map +1 -0
- package/dist/core/session-artifact-bundle.js +61 -0
- package/dist/core/session-artifact-bundle.js.map +1 -0
- package/dist/core/tools/delegate-status.d.ts +6 -1
- package/dist/core/tools/delegate-status.d.ts.map +1 -1
- package/dist/core/tools/delegate-status.js +100 -18
- package/dist/core/tools/delegate-status.js.map +1 -1
- package/dist/core/tools/delegate.d.ts +9 -1
- package/dist/core/tools/delegate.d.ts.map +1 -1
- package/dist/core/tools/delegate.js +323 -61
- package/dist/core/tools/delegate.js.map +1 -1
- package/dist/core/tools/run-process.d.ts +1 -1
- package/dist/core/tools/run-process.d.ts.map +1 -1
- package/dist/core/tools/run-process.js +69 -9
- package/dist/core/tools/run-process.js.map +1 -1
- package/dist/core/tools/shell-contract-router.d.ts.map +1 -1
- package/dist/core/tools/shell-contract-router.js +12 -5
- package/dist/core/tools/shell-contract-router.js.map +1 -1
- package/dist/core/util/bounded-file.d.ts +15 -0
- package/dist/core/util/bounded-file.d.ts.map +1 -0
- package/dist/core/util/bounded-file.js +120 -0
- package/dist/core/util/bounded-file.js.map +1 -0
- package/dist/core/util/bounded-jsonl.d.ts.map +1 -1
- package/dist/core/util/bounded-jsonl.js +119 -9
- package/dist/core/util/bounded-jsonl.js.map +1 -1
- package/dist/core/util/bounded-value.d.ts +3 -0
- package/dist/core/util/bounded-value.d.ts.map +1 -0
- package/dist/core/util/bounded-value.js +12 -0
- package/dist/core/util/bounded-value.js.map +1 -0
- package/dist/core/util/value-guards.d.ts +2 -0
- package/dist/core/util/value-guards.d.ts.map +1 -1
- package/dist/core/util/value-guards.js +5 -0
- package/dist/core/util/value-guards.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +27 -44
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/docs/tool-repair.md +3 -0
- package/docs/windows.md +4 -2
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +4 -4
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { AgentMessage } from "@caupulican/pi-agent-core";
|
|
2
|
+
import type { WorkerDelegationRunOutcome } from "../agent-session-contracts.ts";
|
|
3
|
+
import type { LaneRecord } from "../autonomy/lane-tracker.ts";
|
|
4
|
+
import type { AttemptRuntimeState } from "../orchestration/task-runtime.ts";
|
|
5
|
+
import { type WorkerAgentControlPort } from "./worker-agent-control.ts";
|
|
6
|
+
import type { WorkerConversation } from "./worker-conversation-store.ts";
|
|
7
|
+
import type { WorkerDelegationRequest } from "./worker-delegation-request.ts";
|
|
8
|
+
import type { WorkerDispatchScheduler } from "./worker-dispatch-scheduler.ts";
|
|
9
|
+
import type { WorkerLifecycle } from "./worker-lifecycle.ts";
|
|
10
|
+
export interface WorkerAgentControlCoordinatorOptions {
|
|
11
|
+
agentDir: string;
|
|
12
|
+
parentSessionId: string;
|
|
13
|
+
processOwnerId: string;
|
|
14
|
+
isControlAvailable(): boolean;
|
|
15
|
+
getLifecycle(): WorkerLifecycle;
|
|
16
|
+
recoveredRequest(attempt: AttemptRuntimeState): WorkerDelegationRequest;
|
|
17
|
+
run(request: WorkerDelegationRequest, record: LaneRecord): Promise<WorkerDelegationRunOutcome>;
|
|
18
|
+
scheduler: Pick<WorkerDispatchScheduler, "enqueue" | "track" | "drain">;
|
|
19
|
+
statusChanged(): void;
|
|
20
|
+
abortLane(laneId: string, reasonCode: string): void;
|
|
21
|
+
cancelLane(laneId: string, reasonCode: string): LaneRecord | undefined;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Sole owner of model-facing logical-agent controls and their durable inboxes.
|
|
25
|
+
*
|
|
26
|
+
* It deliberately owns no worker execution policy, provider loop, or terminal persistence. Those
|
|
27
|
+
* controller-owned callbacks keep this narrow port from becoming a second lifecycle authority.
|
|
28
|
+
*/
|
|
29
|
+
export declare class WorkerAgentControlCoordinator implements WorkerAgentControlPort {
|
|
30
|
+
private readonly options;
|
|
31
|
+
private readonly mailboxes;
|
|
32
|
+
private readonly stateListeners;
|
|
33
|
+
constructor(options: WorkerAgentControlCoordinatorOptions);
|
|
34
|
+
getProcessOwnerId(): string;
|
|
35
|
+
sendWorkerAgentMessage(agentId: string, message: string): {
|
|
36
|
+
messageId: string;
|
|
37
|
+
queued: true;
|
|
38
|
+
};
|
|
39
|
+
followUpWorkerAgent(agentId: string, message: string): {
|
|
40
|
+
started: boolean;
|
|
41
|
+
steering: boolean;
|
|
42
|
+
messageId: string;
|
|
43
|
+
record?: LaneRecord;
|
|
44
|
+
skipReason?: string;
|
|
45
|
+
};
|
|
46
|
+
interruptWorkerAgent(agentId: string): {
|
|
47
|
+
interrupted: boolean;
|
|
48
|
+
reason?: string;
|
|
49
|
+
};
|
|
50
|
+
resumeWorkerAgent(agentId: string): {
|
|
51
|
+
started: boolean;
|
|
52
|
+
record?: LaneRecord;
|
|
53
|
+
skipReason?: string;
|
|
54
|
+
};
|
|
55
|
+
cancelWorkerAgent(agentId: string, reasonCode?: string): LaneRecord | undefined;
|
|
56
|
+
/** Event-driven wait: durable projection plus state/mailbox notifications, never output polling. */
|
|
57
|
+
waitForWorkerAgent(agentId: string, timeoutMs?: number): Promise<{
|
|
58
|
+
status: "active" | "suspended" | "idle" | "unknown";
|
|
59
|
+
}>;
|
|
60
|
+
/** Read mailbox items at a safe boundary and reconcile the narrow append-before-ack crash window. */
|
|
61
|
+
mailboxMessagesForConversation(agentId: string, conversation: WorkerConversation, includeFollowUp: boolean): AgentMessage[];
|
|
62
|
+
/** Acknowledge only after the exact child transcript message has been durably appended. */
|
|
63
|
+
acknowledgeMailboxMessage(agentId: string, message: {
|
|
64
|
+
role: string;
|
|
65
|
+
content: unknown;
|
|
66
|
+
}): void;
|
|
67
|
+
/** Called by controller-owned execution transitions after lifecycle state changed. */
|
|
68
|
+
signalStateChanged(): void;
|
|
69
|
+
private requireControl;
|
|
70
|
+
private requireKnownAgent;
|
|
71
|
+
private getMailbox;
|
|
72
|
+
private subscribeStateChanges;
|
|
73
|
+
private mailboxMessage;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=worker-agent-control-coordinator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-agent-control-coordinator.d.ts","sourceRoot":"","sources":["../../../src/core/delegation/worker-agent-control-coordinator.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentMessage } from \"@caupulican/pi-agent-core\";\nimport type { WorkerDelegationRunOutcome } from \"../agent-session-contracts.ts\";\nimport type { LaneRecord } from \"../autonomy/lane-tracker.ts\";\nimport type { AgentBindingContract } from \"../orchestration/contracts.ts\";\nimport type { AttemptRuntimeState } from \"../orchestration/task-runtime.ts\";\nimport { type WorkerAgentControlPort, WorkerAgentMailbox, type WorkerAgentMessage } from \"./worker-agent-control.ts\";\nimport type { WorkerConversation } from \"./worker-conversation-store.ts\";\nimport type { WorkerDelegationRequest } from \"./worker-delegation-request.ts\";\nimport type { WorkerDispatchScheduler } from \"./worker-dispatch-scheduler.ts\";\nimport type { WorkerLifecycle } from \"./worker-lifecycle.ts\";\n\nexport interface WorkerAgentControlCoordinatorOptions {\n\tagentDir: string;\n\tparentSessionId: string;\n\tprocessOwnerId: string;\n\tisControlAvailable(): boolean;\n\tgetLifecycle(): WorkerLifecycle;\n\trecoveredRequest(attempt: AttemptRuntimeState): WorkerDelegationRequest;\n\trun(request: WorkerDelegationRequest, record: LaneRecord): Promise<WorkerDelegationRunOutcome>;\n\tscheduler: Pick<WorkerDispatchScheduler, \"enqueue\" | \"track\" | \"drain\">;\n\tstatusChanged(): void;\n\tabortLane(laneId: string, reasonCode: string): void;\n\tcancelLane(laneId: string, reasonCode: string): LaneRecord | undefined;\n}\n\n/**\n * Sole owner of model-facing logical-agent controls and their durable inboxes.\n *\n * It deliberately owns no worker execution policy, provider loop, or terminal persistence. Those\n * controller-owned callbacks keep this narrow port from becoming a second lifecycle authority.\n */\nexport class WorkerAgentControlCoordinator implements WorkerAgentControlPort {\n\tprivate readonly options: WorkerAgentControlCoordinatorOptions;\n\tprivate readonly mailboxes = new Map<string, WorkerAgentMailbox>();\n\tprivate readonly stateListeners = new Set<() => void>();\n\n\tconstructor(options: WorkerAgentControlCoordinatorOptions) {\n\t\tthis.options = options;\n\t}\n\n\tgetProcessOwnerId(): string {\n\t\treturn this.options.processOwnerId;\n\t}\n\n\tsendWorkerAgentMessage(agentId: string, message: string): { messageId: string; queued: true } {\n\t\tthis.requireControl();\n\t\tconst agent = this.requireKnownAgent(agentId);\n\t\tconst queued = this.getMailbox(agent.agentId).enqueue({ kind: \"follow_up\", content: message });\n\t\tthis.signalStateChanged();\n\t\treturn { messageId: queued.messageId, queued: true };\n\t}\n\n\tfollowUpWorkerAgent(\n\t\tagentId: string,\n\t\tmessage: string,\n\t): { started: boolean; steering: boolean; messageId: string; record?: LaneRecord; skipReason?: string } {\n\t\tthis.requireControl();\n\t\tconst agent = this.requireKnownAgent(agentId);\n\t\tconst canonicalAgentId = agent.agentId;\n\t\tconst active = agent.activeAttemptId\n\t\t\t? this.options.getLifecycle().getTaskRuntimeSnapshot().attempts[agent.activeAttemptId]\n\t\t\t: undefined;\n\t\tif (active?.status === \"running\" || active?.status === \"leased\") {\n\t\t\tconst queued = this.getMailbox(canonicalAgentId).enqueue({ kind: \"steer\", content: message });\n\t\t\tthis.signalStateChanged();\n\t\t\treturn { started: false, steering: true, messageId: queued.messageId };\n\t\t}\n\t\tif (agent.status !== \"registered\") {\n\t\t\treturn { started: false, steering: false, messageId: \"\", skipReason: `agent_${agent.status}` };\n\t\t}\n\t\tconst queued = this.getMailbox(canonicalAgentId).enqueue({ kind: \"follow_up\", content: message });\n\t\ttry {\n\t\t\tconst prepared = this.options\n\t\t\t\t.getLifecycle()\n\t\t\t\t.prepareAgentTurn({ agentId: canonicalAgentId, instructions: message });\n\t\t\tthis.options.scheduler.enqueue(prepared.record, this.options.recoveredRequest(prepared.attempt));\n\t\t\tthis.options.statusChanged();\n\t\t\tthis.options.scheduler.drain();\n\t\t\tthis.signalStateChanged();\n\t\t\treturn { started: true, steering: false, messageId: queued.messageId, record: prepared.record };\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tstarted: false,\n\t\t\t\tsteering: false,\n\t\t\t\tmessageId: queued.messageId,\n\t\t\t\tskipReason: error instanceof Error ? error.message : String(error),\n\t\t\t};\n\t\t}\n\t}\n\n\tinterruptWorkerAgent(agentId: string): { interrupted: boolean; reason?: string } {\n\t\tthis.requireControl();\n\t\tconst agent = this.requireKnownAgent(agentId);\n\t\tconst attemptId = agent.activeAttemptId;\n\t\tconst attempt = attemptId ? this.options.getLifecycle().getTaskRuntimeSnapshot().attempts[attemptId] : undefined;\n\t\tif (!attempt || (attempt.status !== \"running\" && attempt.status !== \"leased\")) {\n\t\t\treturn { interrupted: false, reason: \"agent_not_running\" };\n\t\t}\n\t\ttry {\n\t\t\tthis.options.getLifecycle().suspendAgent(attempt.taskId, agent.agentId, this.options.processOwnerId);\n\t\t\tthis.options.abortLane(attempt.taskId, \"agent_interrupted\");\n\t\t\tthis.signalStateChanged();\n\t\t\treturn { interrupted: true };\n\t\t} catch (error) {\n\t\t\treturn { interrupted: false, reason: error instanceof Error ? error.message : String(error) };\n\t\t}\n\t}\n\n\tresumeWorkerAgent(agentId: string): { started: boolean; record?: LaneRecord; skipReason?: string } {\n\t\tthis.requireControl();\n\t\tconst agent = this.requireKnownAgent(agentId);\n\t\tconst attemptId = agent.activeAttemptId;\n\t\tconst attempt = attemptId ? this.options.getLifecycle().getTaskRuntimeSnapshot().attempts[attemptId] : undefined;\n\t\tif (!attempt || attempt.status !== \"suspended\") return { started: false, skipReason: \"agent_not_suspended\" };\n\t\tconst record = this.options.getLifecycle().getRecord(attempt.taskId);\n\t\tif (!record) return { started: false, skipReason: \"orchestration_projection_missing\" };\n\t\tconst promise = this.options.run(this.options.recoveredRequest(attempt), record);\n\t\tthis.options.scheduler.track(record.laneId, promise);\n\t\tthis.signalStateChanged();\n\t\treturn { started: true, record };\n\t}\n\n\tcancelWorkerAgent(agentId: string, reasonCode = \"agent_cancelled\"): LaneRecord | undefined {\n\t\tthis.requireControl();\n\t\tconst agent = this.requireKnownAgent(agentId);\n\t\tconst attemptId = agent.activeAttemptId;\n\t\tconst attempt = attemptId ? this.options.getLifecycle().getTaskRuntimeSnapshot().attempts[attemptId] : undefined;\n\t\tif (!attempt) return undefined;\n\t\tthis.options.abortLane(attempt.taskId, reasonCode);\n\t\tconst record = this.options.cancelLane(attempt.taskId, reasonCode);\n\t\tthis.signalStateChanged();\n\t\treturn record;\n\t}\n\n\t/** Event-driven wait: durable projection plus state/mailbox notifications, never output polling. */\n\twaitForWorkerAgent(\n\t\tagentId: string,\n\t\ttimeoutMs = 30_000,\n\t): Promise<{ status: \"active\" | \"suspended\" | \"idle\" | \"unknown\" }> {\n\t\tthis.requireControl();\n\t\tconst canonicalAgentId = agentId.trim();\n\t\tconst boundedTimeoutMs = Number.isFinite(timeoutMs)\n\t\t\t? Math.max(1, Math.min(Math.floor(timeoutMs), 300_000))\n\t\t\t: 30_000;\n\t\tconst currentStatus = (): \"active\" | \"suspended\" | \"idle\" | \"unknown\" => {\n\t\t\tconst agent = this.options.getLifecycle().getAgent(canonicalAgentId);\n\t\t\tif (!agent) return \"unknown\";\n\t\t\tif (agent.status === \"active\" || agent.status === \"resuming\") return \"active\";\n\t\t\tif (agent.status === \"suspended\") return \"suspended\";\n\t\t\treturn \"idle\";\n\t\t};\n\t\tconst immediate = currentStatus();\n\t\tif (immediate !== \"active\") return Promise.resolve({ status: immediate });\n\t\treturn new Promise((resolve) => {\n\t\t\tlet settled = false;\n\t\t\tconst settle = () => {\n\t\t\t\tif (settled) return;\n\t\t\t\tconst next = currentStatus();\n\t\t\t\tif (next === \"active\") return;\n\t\t\t\tsettled = true;\n\t\t\t\tunsubscribeMailbox();\n\t\t\t\tunsubscribeState();\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tresolve({ status: next });\n\t\t\t};\n\t\t\tconst unsubscribeMailbox = this.getMailbox(canonicalAgentId).subscribe(settle);\n\t\t\tconst unsubscribeState = this.subscribeStateChanges(settle);\n\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tunsubscribeMailbox();\n\t\t\t\tunsubscribeState();\n\t\t\t\tresolve({ status: currentStatus() });\n\t\t\t}, boundedTimeoutMs);\n\t\t\tif (typeof timeout === \"object\" && \"unref\" in timeout) timeout.unref();\n\t\t});\n\t}\n\n\t/** Read mailbox items at a safe boundary and reconcile the narrow append-before-ack crash window. */\n\tmailboxMessagesForConversation(\n\t\tagentId: string,\n\t\tconversation: WorkerConversation,\n\t\tincludeFollowUp: boolean,\n\t): AgentMessage[] {\n\t\tconst mailbox = this.getMailbox(agentId);\n\t\tconst pending = mailbox.pending();\n\t\tif (pending.length === 0) return [];\n\t\tconst delivered = conversation.findDeliveredWorkerControlMessageIds(pending.map((message) => message.messageId));\n\t\tfor (const messageId of delivered) mailbox.acknowledgeDelivered(messageId);\n\t\treturn mailbox\n\t\t\t.pending()\n\t\t\t.filter((message) => message.kind === \"steer\" || includeFollowUp)\n\t\t\t.map((message) => this.mailboxMessage(message));\n\t}\n\n\t/** Acknowledge only after the exact child transcript message has been durably appended. */\n\tacknowledgeMailboxMessage(agentId: string, message: { role: string; content: unknown }): void {\n\t\tif (message.role !== \"user\" || typeof message.content !== \"string\") return;\n\t\tconst messageId = /^\\[Worker control (worker-message-[^\\]]+)\\]\\n/.exec(message.content)?.[1];\n\t\tif (messageId) this.getMailbox(agentId).acknowledgeDelivered(messageId);\n\t}\n\n\t/** Called by controller-owned execution transitions after lifecycle state changed. */\n\tsignalStateChanged(): void {\n\t\tfor (const listener of this.stateListeners) listener();\n\t}\n\n\tprivate requireControl(): void {\n\t\tif (!this.options.isControlAvailable()) {\n\t\t\tthrow new Error(\"Worker delegation control is unavailable in this UAC surface.\");\n\t\t}\n\t}\n\n\tprivate requireKnownAgent(agentId: string): AgentBindingContract {\n\t\tconst normalized = agentId.trim();\n\t\tif (!normalized) throw new Error(\"Logical worker agent id is required.\");\n\t\tconst agent = this.options.getLifecycle().getAgent(normalized);\n\t\tif (!agent) throw new Error(`Unknown logical worker agent '${normalized}'.`);\n\t\treturn agent;\n\t}\n\n\tprivate getMailbox(agentId: string): WorkerAgentMailbox {\n\t\tlet mailbox = this.mailboxes.get(agentId);\n\t\tif (!mailbox) {\n\t\t\tmailbox = new WorkerAgentMailbox({\n\t\t\t\tagentDir: this.options.agentDir,\n\t\t\t\tparentSessionId: this.options.parentSessionId,\n\t\t\t\tagentId,\n\t\t\t});\n\t\t\tthis.mailboxes.set(agentId, mailbox);\n\t\t}\n\t\treturn mailbox;\n\t}\n\n\tprivate subscribeStateChanges(listener: () => void): () => void {\n\t\tthis.stateListeners.add(listener);\n\t\treturn () => this.stateListeners.delete(listener);\n\t}\n\n\tprivate mailboxMessage(message: WorkerAgentMessage): AgentMessage {\n\t\treturn {\n\t\t\trole: \"user\",\n\t\t\tcontent: `[Worker control ${message.messageId}]\\n${message.content}`,\n\t\t\ttimestamp: Date.now(),\n\t\t};\n\t}\n}\n"]}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { WorkerAgentMailbox } from "./worker-agent-control.js";
|
|
2
|
+
/**
|
|
3
|
+
* Sole owner of model-facing logical-agent controls and their durable inboxes.
|
|
4
|
+
*
|
|
5
|
+
* It deliberately owns no worker execution policy, provider loop, or terminal persistence. Those
|
|
6
|
+
* controller-owned callbacks keep this narrow port from becoming a second lifecycle authority.
|
|
7
|
+
*/
|
|
8
|
+
export class WorkerAgentControlCoordinator {
|
|
9
|
+
options;
|
|
10
|
+
mailboxes = new Map();
|
|
11
|
+
stateListeners = new Set();
|
|
12
|
+
constructor(options) {
|
|
13
|
+
this.options = options;
|
|
14
|
+
}
|
|
15
|
+
getProcessOwnerId() {
|
|
16
|
+
return this.options.processOwnerId;
|
|
17
|
+
}
|
|
18
|
+
sendWorkerAgentMessage(agentId, message) {
|
|
19
|
+
this.requireControl();
|
|
20
|
+
const agent = this.requireKnownAgent(agentId);
|
|
21
|
+
const queued = this.getMailbox(agent.agentId).enqueue({ kind: "follow_up", content: message });
|
|
22
|
+
this.signalStateChanged();
|
|
23
|
+
return { messageId: queued.messageId, queued: true };
|
|
24
|
+
}
|
|
25
|
+
followUpWorkerAgent(agentId, message) {
|
|
26
|
+
this.requireControl();
|
|
27
|
+
const agent = this.requireKnownAgent(agentId);
|
|
28
|
+
const canonicalAgentId = agent.agentId;
|
|
29
|
+
const active = agent.activeAttemptId
|
|
30
|
+
? this.options.getLifecycle().getTaskRuntimeSnapshot().attempts[agent.activeAttemptId]
|
|
31
|
+
: undefined;
|
|
32
|
+
if (active?.status === "running" || active?.status === "leased") {
|
|
33
|
+
const queued = this.getMailbox(canonicalAgentId).enqueue({ kind: "steer", content: message });
|
|
34
|
+
this.signalStateChanged();
|
|
35
|
+
return { started: false, steering: true, messageId: queued.messageId };
|
|
36
|
+
}
|
|
37
|
+
if (agent.status !== "registered") {
|
|
38
|
+
return { started: false, steering: false, messageId: "", skipReason: `agent_${agent.status}` };
|
|
39
|
+
}
|
|
40
|
+
const queued = this.getMailbox(canonicalAgentId).enqueue({ kind: "follow_up", content: message });
|
|
41
|
+
try {
|
|
42
|
+
const prepared = this.options
|
|
43
|
+
.getLifecycle()
|
|
44
|
+
.prepareAgentTurn({ agentId: canonicalAgentId, instructions: message });
|
|
45
|
+
this.options.scheduler.enqueue(prepared.record, this.options.recoveredRequest(prepared.attempt));
|
|
46
|
+
this.options.statusChanged();
|
|
47
|
+
this.options.scheduler.drain();
|
|
48
|
+
this.signalStateChanged();
|
|
49
|
+
return { started: true, steering: false, messageId: queued.messageId, record: prepared.record };
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
return {
|
|
53
|
+
started: false,
|
|
54
|
+
steering: false,
|
|
55
|
+
messageId: queued.messageId,
|
|
56
|
+
skipReason: error instanceof Error ? error.message : String(error),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
interruptWorkerAgent(agentId) {
|
|
61
|
+
this.requireControl();
|
|
62
|
+
const agent = this.requireKnownAgent(agentId);
|
|
63
|
+
const attemptId = agent.activeAttemptId;
|
|
64
|
+
const attempt = attemptId ? this.options.getLifecycle().getTaskRuntimeSnapshot().attempts[attemptId] : undefined;
|
|
65
|
+
if (!attempt || (attempt.status !== "running" && attempt.status !== "leased")) {
|
|
66
|
+
return { interrupted: false, reason: "agent_not_running" };
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
this.options.getLifecycle().suspendAgent(attempt.taskId, agent.agentId, this.options.processOwnerId);
|
|
70
|
+
this.options.abortLane(attempt.taskId, "agent_interrupted");
|
|
71
|
+
this.signalStateChanged();
|
|
72
|
+
return { interrupted: true };
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
return { interrupted: false, reason: error instanceof Error ? error.message : String(error) };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
resumeWorkerAgent(agentId) {
|
|
79
|
+
this.requireControl();
|
|
80
|
+
const agent = this.requireKnownAgent(agentId);
|
|
81
|
+
const attemptId = agent.activeAttemptId;
|
|
82
|
+
const attempt = attemptId ? this.options.getLifecycle().getTaskRuntimeSnapshot().attempts[attemptId] : undefined;
|
|
83
|
+
if (!attempt || attempt.status !== "suspended")
|
|
84
|
+
return { started: false, skipReason: "agent_not_suspended" };
|
|
85
|
+
const record = this.options.getLifecycle().getRecord(attempt.taskId);
|
|
86
|
+
if (!record)
|
|
87
|
+
return { started: false, skipReason: "orchestration_projection_missing" };
|
|
88
|
+
const promise = this.options.run(this.options.recoveredRequest(attempt), record);
|
|
89
|
+
this.options.scheduler.track(record.laneId, promise);
|
|
90
|
+
this.signalStateChanged();
|
|
91
|
+
return { started: true, record };
|
|
92
|
+
}
|
|
93
|
+
cancelWorkerAgent(agentId, reasonCode = "agent_cancelled") {
|
|
94
|
+
this.requireControl();
|
|
95
|
+
const agent = this.requireKnownAgent(agentId);
|
|
96
|
+
const attemptId = agent.activeAttemptId;
|
|
97
|
+
const attempt = attemptId ? this.options.getLifecycle().getTaskRuntimeSnapshot().attempts[attemptId] : undefined;
|
|
98
|
+
if (!attempt)
|
|
99
|
+
return undefined;
|
|
100
|
+
this.options.abortLane(attempt.taskId, reasonCode);
|
|
101
|
+
const record = this.options.cancelLane(attempt.taskId, reasonCode);
|
|
102
|
+
this.signalStateChanged();
|
|
103
|
+
return record;
|
|
104
|
+
}
|
|
105
|
+
/** Event-driven wait: durable projection plus state/mailbox notifications, never output polling. */
|
|
106
|
+
waitForWorkerAgent(agentId, timeoutMs = 30_000) {
|
|
107
|
+
this.requireControl();
|
|
108
|
+
const canonicalAgentId = agentId.trim();
|
|
109
|
+
const boundedTimeoutMs = Number.isFinite(timeoutMs)
|
|
110
|
+
? Math.max(1, Math.min(Math.floor(timeoutMs), 300_000))
|
|
111
|
+
: 30_000;
|
|
112
|
+
const currentStatus = () => {
|
|
113
|
+
const agent = this.options.getLifecycle().getAgent(canonicalAgentId);
|
|
114
|
+
if (!agent)
|
|
115
|
+
return "unknown";
|
|
116
|
+
if (agent.status === "active" || agent.status === "resuming")
|
|
117
|
+
return "active";
|
|
118
|
+
if (agent.status === "suspended")
|
|
119
|
+
return "suspended";
|
|
120
|
+
return "idle";
|
|
121
|
+
};
|
|
122
|
+
const immediate = currentStatus();
|
|
123
|
+
if (immediate !== "active")
|
|
124
|
+
return Promise.resolve({ status: immediate });
|
|
125
|
+
return new Promise((resolve) => {
|
|
126
|
+
let settled = false;
|
|
127
|
+
const settle = () => {
|
|
128
|
+
if (settled)
|
|
129
|
+
return;
|
|
130
|
+
const next = currentStatus();
|
|
131
|
+
if (next === "active")
|
|
132
|
+
return;
|
|
133
|
+
settled = true;
|
|
134
|
+
unsubscribeMailbox();
|
|
135
|
+
unsubscribeState();
|
|
136
|
+
clearTimeout(timeout);
|
|
137
|
+
resolve({ status: next });
|
|
138
|
+
};
|
|
139
|
+
const unsubscribeMailbox = this.getMailbox(canonicalAgentId).subscribe(settle);
|
|
140
|
+
const unsubscribeState = this.subscribeStateChanges(settle);
|
|
141
|
+
const timeout = setTimeout(() => {
|
|
142
|
+
if (settled)
|
|
143
|
+
return;
|
|
144
|
+
settled = true;
|
|
145
|
+
unsubscribeMailbox();
|
|
146
|
+
unsubscribeState();
|
|
147
|
+
resolve({ status: currentStatus() });
|
|
148
|
+
}, boundedTimeoutMs);
|
|
149
|
+
if (typeof timeout === "object" && "unref" in timeout)
|
|
150
|
+
timeout.unref();
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
/** Read mailbox items at a safe boundary and reconcile the narrow append-before-ack crash window. */
|
|
154
|
+
mailboxMessagesForConversation(agentId, conversation, includeFollowUp) {
|
|
155
|
+
const mailbox = this.getMailbox(agentId);
|
|
156
|
+
const pending = mailbox.pending();
|
|
157
|
+
if (pending.length === 0)
|
|
158
|
+
return [];
|
|
159
|
+
const delivered = conversation.findDeliveredWorkerControlMessageIds(pending.map((message) => message.messageId));
|
|
160
|
+
for (const messageId of delivered)
|
|
161
|
+
mailbox.acknowledgeDelivered(messageId);
|
|
162
|
+
return mailbox
|
|
163
|
+
.pending()
|
|
164
|
+
.filter((message) => message.kind === "steer" || includeFollowUp)
|
|
165
|
+
.map((message) => this.mailboxMessage(message));
|
|
166
|
+
}
|
|
167
|
+
/** Acknowledge only after the exact child transcript message has been durably appended. */
|
|
168
|
+
acknowledgeMailboxMessage(agentId, message) {
|
|
169
|
+
if (message.role !== "user" || typeof message.content !== "string")
|
|
170
|
+
return;
|
|
171
|
+
const messageId = /^\[Worker control (worker-message-[^\]]+)\]\n/.exec(message.content)?.[1];
|
|
172
|
+
if (messageId)
|
|
173
|
+
this.getMailbox(agentId).acknowledgeDelivered(messageId);
|
|
174
|
+
}
|
|
175
|
+
/** Called by controller-owned execution transitions after lifecycle state changed. */
|
|
176
|
+
signalStateChanged() {
|
|
177
|
+
for (const listener of this.stateListeners)
|
|
178
|
+
listener();
|
|
179
|
+
}
|
|
180
|
+
requireControl() {
|
|
181
|
+
if (!this.options.isControlAvailable()) {
|
|
182
|
+
throw new Error("Worker delegation control is unavailable in this UAC surface.");
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
requireKnownAgent(agentId) {
|
|
186
|
+
const normalized = agentId.trim();
|
|
187
|
+
if (!normalized)
|
|
188
|
+
throw new Error("Logical worker agent id is required.");
|
|
189
|
+
const agent = this.options.getLifecycle().getAgent(normalized);
|
|
190
|
+
if (!agent)
|
|
191
|
+
throw new Error(`Unknown logical worker agent '${normalized}'.`);
|
|
192
|
+
return agent;
|
|
193
|
+
}
|
|
194
|
+
getMailbox(agentId) {
|
|
195
|
+
let mailbox = this.mailboxes.get(agentId);
|
|
196
|
+
if (!mailbox) {
|
|
197
|
+
mailbox = new WorkerAgentMailbox({
|
|
198
|
+
agentDir: this.options.agentDir,
|
|
199
|
+
parentSessionId: this.options.parentSessionId,
|
|
200
|
+
agentId,
|
|
201
|
+
});
|
|
202
|
+
this.mailboxes.set(agentId, mailbox);
|
|
203
|
+
}
|
|
204
|
+
return mailbox;
|
|
205
|
+
}
|
|
206
|
+
subscribeStateChanges(listener) {
|
|
207
|
+
this.stateListeners.add(listener);
|
|
208
|
+
return () => this.stateListeners.delete(listener);
|
|
209
|
+
}
|
|
210
|
+
mailboxMessage(message) {
|
|
211
|
+
return {
|
|
212
|
+
role: "user",
|
|
213
|
+
content: `[Worker control ${message.messageId}]\n${message.content}`,
|
|
214
|
+
timestamp: Date.now(),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=worker-agent-control-coordinator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-agent-control-coordinator.js","sourceRoot":"","sources":["../../../src/core/delegation/worker-agent-control-coordinator.ts"],"names":[],"mappings":"AAKA,OAAO,EAA+B,kBAAkB,EAA2B,MAAM,2BAA2B,CAAC;AAoBrH;;;;;GAKG;AACH,MAAM,OAAO,6BAA6B;IACxB,OAAO,CAAuC;IAC9C,SAAS,GAAG,IAAI,GAAG,EAA8B,CAAC;IAClD,cAAc,GAAG,IAAI,GAAG,EAAc,CAAC;IAExD,YAAY,OAA6C,EAAE;QAC1D,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IAAA,CACvB;IAED,iBAAiB,GAAW;QAC3B,OAAO,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC;IAAA,CACnC;IAED,sBAAsB,CAAC,OAAe,EAAE,OAAe,EAAuC;QAC7F,IAAI,CAAC,cAAc,EAAE,CAAC;QACtB,MAAM,KAAK,GAAG,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAC9C,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;QAC/F,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC1B,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAAA,CACrD;IAED,mBAAmB,CAClB,OAAe,EACf,OAAe,EACwF;QACvG,IAAI,CAAC,cAAc,EAAE,CAAC;QACtB,MAAM,KAAK,GAAG,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAC9C,MAAM,gBAAgB,GAAG,KAAK,CAAC,OAAO,CAAC;QACvC,MAAM,MAAM,GAAG,KAAK,CAAC,eAAe;YACnC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,sBAAsB,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,eAAe,CAAC;YACtF,CAAC,CAAC,SAAS,CAAC;QACb,IAAI,MAAM,EAAE,MAAM,KAAK,SAAS,IAAI,MAAM,EAAE,MAAM,KAAK,QAAQ,EAAE,CAAC;YACjE,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;YAC9F,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC1B,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC;QACxE,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,KAAK,YAAY,EAAE,CAAC;YACnC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,UAAU,EAAE,SAAS,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;QAChG,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;QAClG,IAAI,CAAC;YACJ,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO;iBAC3B,YAAY,EAAE;iBACd,gBAAgB,CAAC,EAAE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,CAAC;YACzE,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;YACjG,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC;YAC7B,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;YAC/B,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC1B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;QACjG,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,OAAO;gBACN,OAAO,EAAE,KAAK;gBACd,QAAQ,EAAE,KAAK;gBACf,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,UAAU,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;aAClE,CAAC;QACH,CAAC;IAAA,CACD;IAED,oBAAoB,CAAC,OAAe,EAA6C;QAChF,IAAI,CAAC,cAAc,EAAE,CAAC;QACtB,MAAM,KAAK,GAAG,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAC9C,MAAM,SAAS,GAAG,KAAK,CAAC,eAAe,CAAC;QACxC,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,sBAAsB,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACjH,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC,EAAE,CAAC;YAC/E,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;QAC5D,CAAC;QACD,IAAI,CAAC;YACJ,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;YACrG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;YAC5D,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC1B,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;QAC9B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/F,CAAC;IAAA,CACD;IAED,iBAAiB,CAAC,OAAe,EAAkE;QAClG,IAAI,CAAC,cAAc,EAAE,CAAC;QACtB,MAAM,KAAK,GAAG,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAC9C,MAAM,SAAS,GAAG,KAAK,CAAC,eAAe,CAAC;QACxC,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,sBAAsB,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACjH,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,WAAW;YAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,qBAAqB,EAAE,CAAC;QAC7G,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACrE,IAAI,CAAC,MAAM;YAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,kCAAkC,EAAE,CAAC;QACvF,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;QACjF,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrD,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC1B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAAA,CACjC;IAED,iBAAiB,CAAC,OAAe,EAAE,UAAU,GAAG,iBAAiB,EAA0B;QAC1F,IAAI,CAAC,cAAc,EAAE,CAAC;QACtB,MAAM,KAAK,GAAG,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAC9C,MAAM,SAAS,GAAG,KAAK,CAAC,eAAe,CAAC;QACxC,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,sBAAsB,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACjH,IAAI,CAAC,OAAO;YAAE,OAAO,SAAS,CAAC;QAC/B,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QACnE,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC1B,OAAO,MAAM,CAAC;IAAA,CACd;IAED,oGAAoG;IACpG,kBAAkB,CACjB,OAAe,EACf,SAAS,GAAG,MAAM,EACiD;QACnE,IAAI,CAAC,cAAc,EAAE,CAAC;QACtB,MAAM,gBAAgB,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;QACxC,MAAM,gBAAgB,GAAG,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC;YAClD,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,CAAC;YACvD,CAAC,CAAC,MAAM,CAAC;QACV,MAAM,aAAa,GAAG,GAAgD,EAAE,CAAC;YACxE,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC;YACrE,IAAI,CAAC,KAAK;gBAAE,OAAO,SAAS,CAAC;YAC7B,IAAI,KAAK,CAAC,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,UAAU;gBAAE,OAAO,QAAQ,CAAC;YAC9E,IAAI,KAAK,CAAC,MAAM,KAAK,WAAW;gBAAE,OAAO,WAAW,CAAC;YACrD,OAAO,MAAM,CAAC;QAAA,CACd,CAAC;QACF,MAAM,SAAS,GAAG,aAAa,EAAE,CAAC;QAClC,IAAI,SAAS,KAAK,QAAQ;YAAE,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;QAC1E,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;YAC/B,IAAI,OAAO,GAAG,KAAK,CAAC;YACpB,MAAM,MAAM,GAAG,GAAG,EAAE,CAAC;gBACpB,IAAI,OAAO;oBAAE,OAAO;gBACpB,MAAM,IAAI,GAAG,aAAa,EAAE,CAAC;gBAC7B,IAAI,IAAI,KAAK,QAAQ;oBAAE,OAAO;gBAC9B,OAAO,GAAG,IAAI,CAAC;gBACf,kBAAkB,EAAE,CAAC;gBACrB,gBAAgB,EAAE,CAAC;gBACnB,YAAY,CAAC,OAAO,CAAC,CAAC;gBACtB,OAAO,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YAAA,CAC1B,CAAC;YACF,MAAM,kBAAkB,GAAG,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;YAC/E,MAAM,gBAAgB,GAAG,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAC;YAC5D,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC;gBAChC,IAAI,OAAO;oBAAE,OAAO;gBACpB,OAAO,GAAG,IAAI,CAAC;gBACf,kBAAkB,EAAE,CAAC;gBACrB,gBAAgB,EAAE,CAAC;gBACnB,OAAO,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,EAAE,CAAC,CAAC;YAAA,CACrC,EAAE,gBAAgB,CAAC,CAAC;YACrB,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,IAAI,OAAO;gBAAE,OAAO,CAAC,KAAK,EAAE,CAAC;QAAA,CACvE,CAAC,CAAC;IAAA,CACH;IAED,qGAAqG;IACrG,8BAA8B,CAC7B,OAAe,EACf,YAAgC,EAChC,eAAwB,EACP;QACjB,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QACzC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;QAClC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QACpC,MAAM,SAAS,GAAG,YAAY,CAAC,oCAAoC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;QACjH,KAAK,MAAM,SAAS,IAAI,SAAS;YAAE,OAAO,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAC;QAC3E,OAAO,OAAO;aACZ,OAAO,EAAE;aACT,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,OAAO,IAAI,eAAe,CAAC;aAChE,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAAA,CACjD;IAED,2FAA2F;IAC3F,yBAAyB,CAAC,OAAe,EAAE,OAA2C,EAAQ;QAC7F,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ;YAAE,OAAO;QAC3E,MAAM,SAAS,GAAG,+CAA+C,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7F,IAAI,SAAS;YAAE,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAC;IAAA,CACxE;IAED,sFAAsF;IACtF,kBAAkB,GAAS;QAC1B,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,cAAc;YAAE,QAAQ,EAAE,CAAC;IAAA,CACvD;IAEO,cAAc,GAAS;QAC9B,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC;YACxC,MAAM,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAAC;QAClF,CAAC;IAAA,CACD;IAEO,iBAAiB,CAAC,OAAe,EAAwB;QAChE,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;QAClC,IAAI,CAAC,UAAU;YAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;QACzE,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;QAC/D,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,UAAU,IAAI,CAAC,CAAC;QAC7E,OAAO,KAAK,CAAC;IAAA,CACb;IAEO,UAAU,CAAC,OAAe,EAAsB;QACvD,IAAI,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC1C,IAAI,CAAC,OAAO,EAAE,CAAC;YACd,OAAO,GAAG,IAAI,kBAAkB,CAAC;gBAChC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ;gBAC/B,eAAe,EAAE,IAAI,CAAC,OAAO,CAAC,eAAe;gBAC7C,OAAO;aACP,CAAC,CAAC;YACH,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACtC,CAAC;QACD,OAAO,OAAO,CAAC;IAAA,CACf;IAEO,qBAAqB,CAAC,QAAoB,EAAc;QAC/D,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAClC,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAAA,CAClD;IAEO,cAAc,CAAC,OAA2B,EAAgB;QACjE,OAAO;YACN,IAAI,EAAE,MAAM;YACZ,OAAO,EAAE,mBAAmB,OAAO,CAAC,SAAS,MAAM,OAAO,CAAC,OAAO,EAAE;YACpE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACrB,CAAC;IAAA,CACF;CACD","sourcesContent":["import type { AgentMessage } from \"@caupulican/pi-agent-core\";\nimport type { WorkerDelegationRunOutcome } from \"../agent-session-contracts.ts\";\nimport type { LaneRecord } from \"../autonomy/lane-tracker.ts\";\nimport type { AgentBindingContract } from \"../orchestration/contracts.ts\";\nimport type { AttemptRuntimeState } from \"../orchestration/task-runtime.ts\";\nimport { type WorkerAgentControlPort, WorkerAgentMailbox, type WorkerAgentMessage } from \"./worker-agent-control.ts\";\nimport type { WorkerConversation } from \"./worker-conversation-store.ts\";\nimport type { WorkerDelegationRequest } from \"./worker-delegation-request.ts\";\nimport type { WorkerDispatchScheduler } from \"./worker-dispatch-scheduler.ts\";\nimport type { WorkerLifecycle } from \"./worker-lifecycle.ts\";\n\nexport interface WorkerAgentControlCoordinatorOptions {\n\tagentDir: string;\n\tparentSessionId: string;\n\tprocessOwnerId: string;\n\tisControlAvailable(): boolean;\n\tgetLifecycle(): WorkerLifecycle;\n\trecoveredRequest(attempt: AttemptRuntimeState): WorkerDelegationRequest;\n\trun(request: WorkerDelegationRequest, record: LaneRecord): Promise<WorkerDelegationRunOutcome>;\n\tscheduler: Pick<WorkerDispatchScheduler, \"enqueue\" | \"track\" | \"drain\">;\n\tstatusChanged(): void;\n\tabortLane(laneId: string, reasonCode: string): void;\n\tcancelLane(laneId: string, reasonCode: string): LaneRecord | undefined;\n}\n\n/**\n * Sole owner of model-facing logical-agent controls and their durable inboxes.\n *\n * It deliberately owns no worker execution policy, provider loop, or terminal persistence. Those\n * controller-owned callbacks keep this narrow port from becoming a second lifecycle authority.\n */\nexport class WorkerAgentControlCoordinator implements WorkerAgentControlPort {\n\tprivate readonly options: WorkerAgentControlCoordinatorOptions;\n\tprivate readonly mailboxes = new Map<string, WorkerAgentMailbox>();\n\tprivate readonly stateListeners = new Set<() => void>();\n\n\tconstructor(options: WorkerAgentControlCoordinatorOptions) {\n\t\tthis.options = options;\n\t}\n\n\tgetProcessOwnerId(): string {\n\t\treturn this.options.processOwnerId;\n\t}\n\n\tsendWorkerAgentMessage(agentId: string, message: string): { messageId: string; queued: true } {\n\t\tthis.requireControl();\n\t\tconst agent = this.requireKnownAgent(agentId);\n\t\tconst queued = this.getMailbox(agent.agentId).enqueue({ kind: \"follow_up\", content: message });\n\t\tthis.signalStateChanged();\n\t\treturn { messageId: queued.messageId, queued: true };\n\t}\n\n\tfollowUpWorkerAgent(\n\t\tagentId: string,\n\t\tmessage: string,\n\t): { started: boolean; steering: boolean; messageId: string; record?: LaneRecord; skipReason?: string } {\n\t\tthis.requireControl();\n\t\tconst agent = this.requireKnownAgent(agentId);\n\t\tconst canonicalAgentId = agent.agentId;\n\t\tconst active = agent.activeAttemptId\n\t\t\t? this.options.getLifecycle().getTaskRuntimeSnapshot().attempts[agent.activeAttemptId]\n\t\t\t: undefined;\n\t\tif (active?.status === \"running\" || active?.status === \"leased\") {\n\t\t\tconst queued = this.getMailbox(canonicalAgentId).enqueue({ kind: \"steer\", content: message });\n\t\t\tthis.signalStateChanged();\n\t\t\treturn { started: false, steering: true, messageId: queued.messageId };\n\t\t}\n\t\tif (agent.status !== \"registered\") {\n\t\t\treturn { started: false, steering: false, messageId: \"\", skipReason: `agent_${agent.status}` };\n\t\t}\n\t\tconst queued = this.getMailbox(canonicalAgentId).enqueue({ kind: \"follow_up\", content: message });\n\t\ttry {\n\t\t\tconst prepared = this.options\n\t\t\t\t.getLifecycle()\n\t\t\t\t.prepareAgentTurn({ agentId: canonicalAgentId, instructions: message });\n\t\t\tthis.options.scheduler.enqueue(prepared.record, this.options.recoveredRequest(prepared.attempt));\n\t\t\tthis.options.statusChanged();\n\t\t\tthis.options.scheduler.drain();\n\t\t\tthis.signalStateChanged();\n\t\t\treturn { started: true, steering: false, messageId: queued.messageId, record: prepared.record };\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tstarted: false,\n\t\t\t\tsteering: false,\n\t\t\t\tmessageId: queued.messageId,\n\t\t\t\tskipReason: error instanceof Error ? error.message : String(error),\n\t\t\t};\n\t\t}\n\t}\n\n\tinterruptWorkerAgent(agentId: string): { interrupted: boolean; reason?: string } {\n\t\tthis.requireControl();\n\t\tconst agent = this.requireKnownAgent(agentId);\n\t\tconst attemptId = agent.activeAttemptId;\n\t\tconst attempt = attemptId ? this.options.getLifecycle().getTaskRuntimeSnapshot().attempts[attemptId] : undefined;\n\t\tif (!attempt || (attempt.status !== \"running\" && attempt.status !== \"leased\")) {\n\t\t\treturn { interrupted: false, reason: \"agent_not_running\" };\n\t\t}\n\t\ttry {\n\t\t\tthis.options.getLifecycle().suspendAgent(attempt.taskId, agent.agentId, this.options.processOwnerId);\n\t\t\tthis.options.abortLane(attempt.taskId, \"agent_interrupted\");\n\t\t\tthis.signalStateChanged();\n\t\t\treturn { interrupted: true };\n\t\t} catch (error) {\n\t\t\treturn { interrupted: false, reason: error instanceof Error ? error.message : String(error) };\n\t\t}\n\t}\n\n\tresumeWorkerAgent(agentId: string): { started: boolean; record?: LaneRecord; skipReason?: string } {\n\t\tthis.requireControl();\n\t\tconst agent = this.requireKnownAgent(agentId);\n\t\tconst attemptId = agent.activeAttemptId;\n\t\tconst attempt = attemptId ? this.options.getLifecycle().getTaskRuntimeSnapshot().attempts[attemptId] : undefined;\n\t\tif (!attempt || attempt.status !== \"suspended\") return { started: false, skipReason: \"agent_not_suspended\" };\n\t\tconst record = this.options.getLifecycle().getRecord(attempt.taskId);\n\t\tif (!record) return { started: false, skipReason: \"orchestration_projection_missing\" };\n\t\tconst promise = this.options.run(this.options.recoveredRequest(attempt), record);\n\t\tthis.options.scheduler.track(record.laneId, promise);\n\t\tthis.signalStateChanged();\n\t\treturn { started: true, record };\n\t}\n\n\tcancelWorkerAgent(agentId: string, reasonCode = \"agent_cancelled\"): LaneRecord | undefined {\n\t\tthis.requireControl();\n\t\tconst agent = this.requireKnownAgent(agentId);\n\t\tconst attemptId = agent.activeAttemptId;\n\t\tconst attempt = attemptId ? this.options.getLifecycle().getTaskRuntimeSnapshot().attempts[attemptId] : undefined;\n\t\tif (!attempt) return undefined;\n\t\tthis.options.abortLane(attempt.taskId, reasonCode);\n\t\tconst record = this.options.cancelLane(attempt.taskId, reasonCode);\n\t\tthis.signalStateChanged();\n\t\treturn record;\n\t}\n\n\t/** Event-driven wait: durable projection plus state/mailbox notifications, never output polling. */\n\twaitForWorkerAgent(\n\t\tagentId: string,\n\t\ttimeoutMs = 30_000,\n\t): Promise<{ status: \"active\" | \"suspended\" | \"idle\" | \"unknown\" }> {\n\t\tthis.requireControl();\n\t\tconst canonicalAgentId = agentId.trim();\n\t\tconst boundedTimeoutMs = Number.isFinite(timeoutMs)\n\t\t\t? Math.max(1, Math.min(Math.floor(timeoutMs), 300_000))\n\t\t\t: 30_000;\n\t\tconst currentStatus = (): \"active\" | \"suspended\" | \"idle\" | \"unknown\" => {\n\t\t\tconst agent = this.options.getLifecycle().getAgent(canonicalAgentId);\n\t\t\tif (!agent) return \"unknown\";\n\t\t\tif (agent.status === \"active\" || agent.status === \"resuming\") return \"active\";\n\t\t\tif (agent.status === \"suspended\") return \"suspended\";\n\t\t\treturn \"idle\";\n\t\t};\n\t\tconst immediate = currentStatus();\n\t\tif (immediate !== \"active\") return Promise.resolve({ status: immediate });\n\t\treturn new Promise((resolve) => {\n\t\t\tlet settled = false;\n\t\t\tconst settle = () => {\n\t\t\t\tif (settled) return;\n\t\t\t\tconst next = currentStatus();\n\t\t\t\tif (next === \"active\") return;\n\t\t\t\tsettled = true;\n\t\t\t\tunsubscribeMailbox();\n\t\t\t\tunsubscribeState();\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tresolve({ status: next });\n\t\t\t};\n\t\t\tconst unsubscribeMailbox = this.getMailbox(canonicalAgentId).subscribe(settle);\n\t\t\tconst unsubscribeState = this.subscribeStateChanges(settle);\n\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tunsubscribeMailbox();\n\t\t\t\tunsubscribeState();\n\t\t\t\tresolve({ status: currentStatus() });\n\t\t\t}, boundedTimeoutMs);\n\t\t\tif (typeof timeout === \"object\" && \"unref\" in timeout) timeout.unref();\n\t\t});\n\t}\n\n\t/** Read mailbox items at a safe boundary and reconcile the narrow append-before-ack crash window. */\n\tmailboxMessagesForConversation(\n\t\tagentId: string,\n\t\tconversation: WorkerConversation,\n\t\tincludeFollowUp: boolean,\n\t): AgentMessage[] {\n\t\tconst mailbox = this.getMailbox(agentId);\n\t\tconst pending = mailbox.pending();\n\t\tif (pending.length === 0) return [];\n\t\tconst delivered = conversation.findDeliveredWorkerControlMessageIds(pending.map((message) => message.messageId));\n\t\tfor (const messageId of delivered) mailbox.acknowledgeDelivered(messageId);\n\t\treturn mailbox\n\t\t\t.pending()\n\t\t\t.filter((message) => message.kind === \"steer\" || includeFollowUp)\n\t\t\t.map((message) => this.mailboxMessage(message));\n\t}\n\n\t/** Acknowledge only after the exact child transcript message has been durably appended. */\n\tacknowledgeMailboxMessage(agentId: string, message: { role: string; content: unknown }): void {\n\t\tif (message.role !== \"user\" || typeof message.content !== \"string\") return;\n\t\tconst messageId = /^\\[Worker control (worker-message-[^\\]]+)\\]\\n/.exec(message.content)?.[1];\n\t\tif (messageId) this.getMailbox(agentId).acknowledgeDelivered(messageId);\n\t}\n\n\t/** Called by controller-owned execution transitions after lifecycle state changed. */\n\tsignalStateChanged(): void {\n\t\tfor (const listener of this.stateListeners) listener();\n\t}\n\n\tprivate requireControl(): void {\n\t\tif (!this.options.isControlAvailable()) {\n\t\t\tthrow new Error(\"Worker delegation control is unavailable in this UAC surface.\");\n\t\t}\n\t}\n\n\tprivate requireKnownAgent(agentId: string): AgentBindingContract {\n\t\tconst normalized = agentId.trim();\n\t\tif (!normalized) throw new Error(\"Logical worker agent id is required.\");\n\t\tconst agent = this.options.getLifecycle().getAgent(normalized);\n\t\tif (!agent) throw new Error(`Unknown logical worker agent '${normalized}'.`);\n\t\treturn agent;\n\t}\n\n\tprivate getMailbox(agentId: string): WorkerAgentMailbox {\n\t\tlet mailbox = this.mailboxes.get(agentId);\n\t\tif (!mailbox) {\n\t\t\tmailbox = new WorkerAgentMailbox({\n\t\t\t\tagentDir: this.options.agentDir,\n\t\t\t\tparentSessionId: this.options.parentSessionId,\n\t\t\t\tagentId,\n\t\t\t});\n\t\t\tthis.mailboxes.set(agentId, mailbox);\n\t\t}\n\t\treturn mailbox;\n\t}\n\n\tprivate subscribeStateChanges(listener: () => void): () => void {\n\t\tthis.stateListeners.add(listener);\n\t\treturn () => this.stateListeners.delete(listener);\n\t}\n\n\tprivate mailboxMessage(message: WorkerAgentMessage): AgentMessage {\n\t\treturn {\n\t\t\trole: \"user\",\n\t\t\tcontent: `[Worker control ${message.messageId}]\\n${message.content}`,\n\t\t\ttimestamp: Date.now(),\n\t\t};\n\t}\n}\n"]}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { LaneRecord } from "../autonomy/lane-tracker.ts";
|
|
2
|
+
export type WorkerAgentMessageKind = "steer" | "follow_up";
|
|
3
|
+
export interface WorkerAgentMessage {
|
|
4
|
+
messageId: string;
|
|
5
|
+
kind: WorkerAgentMessageKind;
|
|
6
|
+
content: string;
|
|
7
|
+
createdAt: string;
|
|
8
|
+
deliveredAt?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface WorkerAgentMailboxOptions {
|
|
11
|
+
agentDir: string;
|
|
12
|
+
parentSessionId: string;
|
|
13
|
+
agentId: string;
|
|
14
|
+
}
|
|
15
|
+
/** One canonical host port for model-facing logical-agent controls. */
|
|
16
|
+
export interface WorkerAgentControlPort {
|
|
17
|
+
sendWorkerAgentMessage(agentId: string, message: string): {
|
|
18
|
+
messageId: string;
|
|
19
|
+
queued: true;
|
|
20
|
+
};
|
|
21
|
+
followUpWorkerAgent(agentId: string, message: string): {
|
|
22
|
+
started: boolean;
|
|
23
|
+
steering: boolean;
|
|
24
|
+
messageId: string;
|
|
25
|
+
record?: LaneRecord;
|
|
26
|
+
skipReason?: string;
|
|
27
|
+
};
|
|
28
|
+
interruptWorkerAgent(agentId: string): {
|
|
29
|
+
interrupted: boolean;
|
|
30
|
+
reason?: string;
|
|
31
|
+
};
|
|
32
|
+
resumeWorkerAgent(agentId: string): {
|
|
33
|
+
started: boolean;
|
|
34
|
+
record?: LaneRecord;
|
|
35
|
+
skipReason?: string;
|
|
36
|
+
};
|
|
37
|
+
cancelWorkerAgent(agentId: string, reasonCode?: string): LaneRecord | undefined;
|
|
38
|
+
waitForWorkerAgent(agentId: string, timeoutMs?: number): Promise<{
|
|
39
|
+
status: "active" | "suspended" | "idle" | "unknown";
|
|
40
|
+
}>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Bounded durable inbox for a single logical worker agent.
|
|
44
|
+
*
|
|
45
|
+
* A message is never considered delivered merely because a controller read it. Its caller must
|
|
46
|
+
* acknowledge it only after the exact corresponding user message has been appended to the child
|
|
47
|
+
* WorkerConversation. The in-process subscription is a notification edge, not a polling loop.
|
|
48
|
+
*/
|
|
49
|
+
export declare class WorkerAgentMailbox {
|
|
50
|
+
private readonly parentSessionId;
|
|
51
|
+
private readonly agentId;
|
|
52
|
+
private readonly file;
|
|
53
|
+
private readonly listeners;
|
|
54
|
+
constructor(options: WorkerAgentMailboxOptions);
|
|
55
|
+
enqueue(input: {
|
|
56
|
+
kind: WorkerAgentMessageKind;
|
|
57
|
+
content: string;
|
|
58
|
+
}): WorkerAgentMessage;
|
|
59
|
+
pending(kind?: WorkerAgentMessageKind): WorkerAgentMessage[];
|
|
60
|
+
acknowledgeDelivered(messageId: string): void;
|
|
61
|
+
subscribe(listener: () => void): () => void;
|
|
62
|
+
private notify;
|
|
63
|
+
private read;
|
|
64
|
+
private update;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=worker-agent-control.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-agent-control.d.ts","sourceRoot":"","sources":["../../../src/core/delegation/worker-agent-control.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAU9D,MAAM,MAAM,sBAAsB,GAAG,OAAO,GAAG,WAAW,CAAC;AAE3D,MAAM,WAAW,kBAAkB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,sBAAsB,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AASD,MAAM,WAAW,yBAAyB;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,uEAAuE;AACvE,MAAM,WAAW,sBAAsB;IACtC,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,IAAI,CAAA;KAAE,CAAC;IAC9F,mBAAmB,CAClB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACb;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,UAAU,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACxG,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG;QAAE,WAAW,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACjF,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,UAAU,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACnG,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;IAChF,kBAAkB,CACjB,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,MAAM,EAAE,QAAQ,GAAG,WAAW,GAAG,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC,CAAC;CACpE;AAwED;;;;;;GAMG;AACH,qBAAa,kBAAkB;IAC9B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyB;IAEnD,YAAY,OAAO,EAAE,yBAAyB,EAQ7C;IAED,OAAO,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,sBAAsB,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,kBAAkB,CAsBpF;IAED,OAAO,CAAC,IAAI,CAAC,EAAE,sBAAsB,GAAG,kBAAkB,EAAE,CAM3D;IAED,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAa5C;IAED,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAG1C;IAED,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,IAAI;IAWZ,OAAO,CAAC,MAAM;CAWd","sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport { existsSync } from \"node:fs\";\nimport { workerAgentMailboxFile } from \"../agent-paths.ts\";\nimport type { LaneRecord } from \"../autonomy/lane-tracker.ts\";\nimport { withFileLockSync, writeFileAtomicSync } from \"../util/atomic-file.ts\";\nimport { readBoundedTextFileSync } from \"../util/bounded-file.ts\";\n\nconst MAX_MAILBOX_MESSAGES = 64;\nconst MAX_MAILBOX_MESSAGE_CHARS = 4_096;\nconst MAX_MAILBOX_BYTES = 128 * 1024;\nconst MAX_MAILBOX_MESSAGE_ID_CHARS = 512;\nconst MAX_MAILBOX_TIMESTAMP_CHARS = 128;\n\nexport type WorkerAgentMessageKind = \"steer\" | \"follow_up\";\n\nexport interface WorkerAgentMessage {\n\tmessageId: string;\n\tkind: WorkerAgentMessageKind;\n\tcontent: string;\n\tcreatedAt: string;\n\tdeliveredAt?: string;\n}\n\ninterface WorkerAgentMailboxState {\n\tversion: 1;\n\tparentSessionId: string;\n\tagentId: string;\n\tmessages: WorkerAgentMessage[];\n}\n\nexport interface WorkerAgentMailboxOptions {\n\tagentDir: string;\n\tparentSessionId: string;\n\tagentId: string;\n}\n\n/** One canonical host port for model-facing logical-agent controls. */\nexport interface WorkerAgentControlPort {\n\tsendWorkerAgentMessage(agentId: string, message: string): { messageId: string; queued: true };\n\tfollowUpWorkerAgent(\n\t\tagentId: string,\n\t\tmessage: string,\n\t): { started: boolean; steering: boolean; messageId: string; record?: LaneRecord; skipReason?: string };\n\tinterruptWorkerAgent(agentId: string): { interrupted: boolean; reason?: string };\n\tresumeWorkerAgent(agentId: string): { started: boolean; record?: LaneRecord; skipReason?: string };\n\tcancelWorkerAgent(agentId: string, reasonCode?: string): LaneRecord | undefined;\n\twaitForWorkerAgent(\n\t\tagentId: string,\n\t\ttimeoutMs?: number,\n\t): Promise<{ status: \"active\" | \"suspended\" | \"idle\" | \"unknown\" }>;\n}\n\nfunction mailboxDigest(parentSessionId: string, agentId: string): string {\n\treturn createHash(\"sha256\")\n\t\t.update(\"pi-worker-agent-mailbox-v1\")\n\t\t.update(\"\\0\")\n\t\t.update(parentSessionId)\n\t\t.update(\"\\0\")\n\t\t.update(agentId)\n\t\t.digest(\"hex\");\n}\n\nfunction assertIdentity(value: string, label: string): string {\n\tconst normalized = value.trim();\n\tif (!normalized) throw new TypeError(`A worker ${label} is required.`);\n\tif (normalized.length > 512) throw new TypeError(`Worker ${label} exceeds 512 characters.`);\n\treturn normalized;\n}\n\nfunction parseState(raw: string, parentSessionId: string, agentId: string): WorkerAgentMailboxState {\n\tlet candidate: unknown;\n\ttry {\n\t\tcandidate = JSON.parse(raw);\n\t} catch {\n\t\tthrow new Error(\"Worker agent mailbox is invalid JSON.\");\n\t}\n\tif (!candidate || typeof candidate !== \"object\" || Array.isArray(candidate)) {\n\t\tthrow new Error(\"Worker agent mailbox is invalid.\");\n\t}\n\tconst state = candidate as Partial<WorkerAgentMailboxState>;\n\tif (\n\t\tstate.version !== 1 ||\n\t\tstate.parentSessionId !== parentSessionId ||\n\t\tstate.agentId !== agentId ||\n\t\t!Array.isArray(state.messages)\n\t) {\n\t\tthrow new Error(\"Worker agent mailbox identity conflicts with the requested logical worker.\");\n\t}\n\tconst messages = state.messages.map((message): WorkerAgentMessage => {\n\t\tif (\n\t\t\t!message ||\n\t\t\ttypeof message !== \"object\" ||\n\t\t\tArray.isArray(message) ||\n\t\t\ttypeof message.messageId !== \"string\" ||\n\t\t\tmessage.messageId.length === 0 ||\n\t\t\tmessage.messageId.length > MAX_MAILBOX_MESSAGE_ID_CHARS ||\n\t\t\t(message.kind !== \"steer\" && message.kind !== \"follow_up\") ||\n\t\t\ttypeof message.content !== \"string\" ||\n\t\t\tmessage.content.length === 0 ||\n\t\t\tmessage.content.length > MAX_MAILBOX_MESSAGE_CHARS ||\n\t\t\ttypeof message.createdAt !== \"string\" ||\n\t\t\tmessage.createdAt.length === 0 ||\n\t\t\tmessage.createdAt.length > MAX_MAILBOX_TIMESTAMP_CHARS ||\n\t\t\t(message.deliveredAt !== undefined &&\n\t\t\t\t(typeof message.deliveredAt !== \"string\" ||\n\t\t\t\t\tmessage.deliveredAt.length === 0 ||\n\t\t\t\t\tmessage.deliveredAt.length > MAX_MAILBOX_TIMESTAMP_CHARS))\n\t\t) {\n\t\t\tthrow new Error(\"Worker agent mailbox contains an invalid message.\");\n\t\t}\n\t\treturn { ...message };\n\t});\n\tconst pendingMessages = messages.filter((message) => message.deliveredAt === undefined);\n\tif (pendingMessages.length > MAX_MAILBOX_MESSAGES)\n\t\tthrow new Error(\"Worker agent mailbox exceeds its pending-message bound.\");\n\treturn { version: 1, parentSessionId, agentId, messages: pendingMessages };\n}\n\nfunction encodedStateBytes(state: WorkerAgentMailboxState): number {\n\treturn Buffer.byteLength(JSON.stringify(state), \"utf-8\");\n}\n\n/**\n * Bounded durable inbox for a single logical worker agent.\n *\n * A message is never considered delivered merely because a controller read it. Its caller must\n * acknowledge it only after the exact corresponding user message has been appended to the child\n * WorkerConversation. The in-process subscription is a notification edge, not a polling loop.\n */\nexport class WorkerAgentMailbox {\n\tprivate readonly parentSessionId: string;\n\tprivate readonly agentId: string;\n\tprivate readonly file: string;\n\tprivate readonly listeners = new Set<() => void>();\n\n\tconstructor(options: WorkerAgentMailboxOptions) {\n\t\tthis.parentSessionId = assertIdentity(options.parentSessionId, \"parent session id\");\n\t\tthis.agentId = assertIdentity(options.agentId, \"agent id\");\n\t\tthis.file = workerAgentMailboxFile(\n\t\t\toptions.agentDir,\n\t\t\tthis.parentSessionId,\n\t\t\tmailboxDigest(this.parentSessionId, this.agentId),\n\t\t);\n\t}\n\n\tenqueue(input: { kind: WorkerAgentMessageKind; content: string }): WorkerAgentMessage {\n\t\tconst content = input.content.trim();\n\t\tif (!content) throw new TypeError(\"A worker control message is required.\");\n\t\tif (content.length > MAX_MAILBOX_MESSAGE_CHARS) {\n\t\t\tthrow new TypeError(\n\t\t\t\t`Worker control messages may not exceed ${MAX_MAILBOX_MESSAGE_CHARS.toLocaleString(\"en-US\")} characters.`,\n\t\t\t);\n\t\t}\n\t\tconst message: WorkerAgentMessage = {\n\t\t\tmessageId: `worker-message-${randomUUID()}`,\n\t\t\tkind: input.kind,\n\t\t\tcontent,\n\t\t\tcreatedAt: new Date().toISOString(),\n\t\t};\n\t\tthis.update((state) => {\n\t\t\tif (state.messages.length >= MAX_MAILBOX_MESSAGES) {\n\t\t\t\tthrow new Error(`Worker agent mailbox reached its ${MAX_MAILBOX_MESSAGES} message limit.`);\n\t\t\t}\n\t\t\treturn { ...state, messages: [...state.messages, message] };\n\t\t});\n\t\tthis.notify();\n\t\treturn structuredClone(message);\n\t}\n\n\tpending(kind?: WorkerAgentMessageKind): WorkerAgentMessage[] {\n\t\treturn this.read()\n\t\t\t.messages.filter(\n\t\t\t\t(message) => message.deliveredAt === undefined && (kind === undefined || message.kind === kind),\n\t\t\t)\n\t\t\t.map((message) => structuredClone(message));\n\t}\n\n\tacknowledgeDelivered(messageId: string): void {\n\t\tconst normalized = messageId.trim();\n\t\tif (!normalized) throw new TypeError(\"A worker control message id is required.\");\n\t\tlet changed = false;\n\t\tthis.update((state) => {\n\t\t\tconst messages = state.messages.filter((message) => {\n\t\t\t\tif (message.messageId !== normalized) return true;\n\t\t\t\tchanged = true;\n\t\t\t\treturn false;\n\t\t\t});\n\t\t\treturn changed ? { ...state, messages } : state;\n\t\t});\n\t\tif (changed) this.notify();\n\t}\n\n\tsubscribe(listener: () => void): () => void {\n\t\tthis.listeners.add(listener);\n\t\treturn () => this.listeners.delete(listener);\n\t}\n\n\tprivate notify(): void {\n\t\tfor (const listener of this.listeners) listener();\n\t}\n\n\tprivate read(): WorkerAgentMailboxState {\n\t\tif (!existsSync(this.file)) {\n\t\t\treturn { version: 1, parentSessionId: this.parentSessionId, agentId: this.agentId, messages: [] };\n\t\t}\n\t\treturn parseState(\n\t\t\treadBoundedTextFileSync(this.file, MAX_MAILBOX_BYTES, \"Worker agent mailbox durable size bound\"),\n\t\t\tthis.parentSessionId,\n\t\t\tthis.agentId,\n\t\t);\n\t}\n\n\tprivate update(mutator: (state: WorkerAgentMailboxState) => WorkerAgentMailboxState): void {\n\t\twithFileLockSync(this.file, () => {\n\t\t\tconst state = this.read();\n\t\t\tconst next = mutator(state);\n\t\t\tif (next.messages.length > MAX_MAILBOX_MESSAGES || encodedStateBytes(next) > MAX_MAILBOX_BYTES) {\n\t\t\t\tthrow new Error(\"Worker agent mailbox exceeds its durable size bound.\");\n\t\t\t}\n\t\t\tif (JSON.stringify(next) !== JSON.stringify(state))\n\t\t\t\twriteFileAtomicSync(this.file, `${JSON.stringify(next)}\\n`, { mode: 0o600 });\n\t\t});\n\t}\n}\n"]}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { workerAgentMailboxFile } from "../agent-paths.js";
|
|
4
|
+
import { withFileLockSync, writeFileAtomicSync } from "../util/atomic-file.js";
|
|
5
|
+
import { readBoundedTextFileSync } from "../util/bounded-file.js";
|
|
6
|
+
const MAX_MAILBOX_MESSAGES = 64;
|
|
7
|
+
const MAX_MAILBOX_MESSAGE_CHARS = 4_096;
|
|
8
|
+
const MAX_MAILBOX_BYTES = 128 * 1024;
|
|
9
|
+
const MAX_MAILBOX_MESSAGE_ID_CHARS = 512;
|
|
10
|
+
const MAX_MAILBOX_TIMESTAMP_CHARS = 128;
|
|
11
|
+
function mailboxDigest(parentSessionId, agentId) {
|
|
12
|
+
return createHash("sha256")
|
|
13
|
+
.update("pi-worker-agent-mailbox-v1")
|
|
14
|
+
.update("\0")
|
|
15
|
+
.update(parentSessionId)
|
|
16
|
+
.update("\0")
|
|
17
|
+
.update(agentId)
|
|
18
|
+
.digest("hex");
|
|
19
|
+
}
|
|
20
|
+
function assertIdentity(value, label) {
|
|
21
|
+
const normalized = value.trim();
|
|
22
|
+
if (!normalized)
|
|
23
|
+
throw new TypeError(`A worker ${label} is required.`);
|
|
24
|
+
if (normalized.length > 512)
|
|
25
|
+
throw new TypeError(`Worker ${label} exceeds 512 characters.`);
|
|
26
|
+
return normalized;
|
|
27
|
+
}
|
|
28
|
+
function parseState(raw, parentSessionId, agentId) {
|
|
29
|
+
let candidate;
|
|
30
|
+
try {
|
|
31
|
+
candidate = JSON.parse(raw);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
throw new Error("Worker agent mailbox is invalid JSON.");
|
|
35
|
+
}
|
|
36
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
|
|
37
|
+
throw new Error("Worker agent mailbox is invalid.");
|
|
38
|
+
}
|
|
39
|
+
const state = candidate;
|
|
40
|
+
if (state.version !== 1 ||
|
|
41
|
+
state.parentSessionId !== parentSessionId ||
|
|
42
|
+
state.agentId !== agentId ||
|
|
43
|
+
!Array.isArray(state.messages)) {
|
|
44
|
+
throw new Error("Worker agent mailbox identity conflicts with the requested logical worker.");
|
|
45
|
+
}
|
|
46
|
+
const messages = state.messages.map((message) => {
|
|
47
|
+
if (!message ||
|
|
48
|
+
typeof message !== "object" ||
|
|
49
|
+
Array.isArray(message) ||
|
|
50
|
+
typeof message.messageId !== "string" ||
|
|
51
|
+
message.messageId.length === 0 ||
|
|
52
|
+
message.messageId.length > MAX_MAILBOX_MESSAGE_ID_CHARS ||
|
|
53
|
+
(message.kind !== "steer" && message.kind !== "follow_up") ||
|
|
54
|
+
typeof message.content !== "string" ||
|
|
55
|
+
message.content.length === 0 ||
|
|
56
|
+
message.content.length > MAX_MAILBOX_MESSAGE_CHARS ||
|
|
57
|
+
typeof message.createdAt !== "string" ||
|
|
58
|
+
message.createdAt.length === 0 ||
|
|
59
|
+
message.createdAt.length > MAX_MAILBOX_TIMESTAMP_CHARS ||
|
|
60
|
+
(message.deliveredAt !== undefined &&
|
|
61
|
+
(typeof message.deliveredAt !== "string" ||
|
|
62
|
+
message.deliveredAt.length === 0 ||
|
|
63
|
+
message.deliveredAt.length > MAX_MAILBOX_TIMESTAMP_CHARS))) {
|
|
64
|
+
throw new Error("Worker agent mailbox contains an invalid message.");
|
|
65
|
+
}
|
|
66
|
+
return { ...message };
|
|
67
|
+
});
|
|
68
|
+
const pendingMessages = messages.filter((message) => message.deliveredAt === undefined);
|
|
69
|
+
if (pendingMessages.length > MAX_MAILBOX_MESSAGES)
|
|
70
|
+
throw new Error("Worker agent mailbox exceeds its pending-message bound.");
|
|
71
|
+
return { version: 1, parentSessionId, agentId, messages: pendingMessages };
|
|
72
|
+
}
|
|
73
|
+
function encodedStateBytes(state) {
|
|
74
|
+
return Buffer.byteLength(JSON.stringify(state), "utf-8");
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Bounded durable inbox for a single logical worker agent.
|
|
78
|
+
*
|
|
79
|
+
* A message is never considered delivered merely because a controller read it. Its caller must
|
|
80
|
+
* acknowledge it only after the exact corresponding user message has been appended to the child
|
|
81
|
+
* WorkerConversation. The in-process subscription is a notification edge, not a polling loop.
|
|
82
|
+
*/
|
|
83
|
+
export class WorkerAgentMailbox {
|
|
84
|
+
parentSessionId;
|
|
85
|
+
agentId;
|
|
86
|
+
file;
|
|
87
|
+
listeners = new Set();
|
|
88
|
+
constructor(options) {
|
|
89
|
+
this.parentSessionId = assertIdentity(options.parentSessionId, "parent session id");
|
|
90
|
+
this.agentId = assertIdentity(options.agentId, "agent id");
|
|
91
|
+
this.file = workerAgentMailboxFile(options.agentDir, this.parentSessionId, mailboxDigest(this.parentSessionId, this.agentId));
|
|
92
|
+
}
|
|
93
|
+
enqueue(input) {
|
|
94
|
+
const content = input.content.trim();
|
|
95
|
+
if (!content)
|
|
96
|
+
throw new TypeError("A worker control message is required.");
|
|
97
|
+
if (content.length > MAX_MAILBOX_MESSAGE_CHARS) {
|
|
98
|
+
throw new TypeError(`Worker control messages may not exceed ${MAX_MAILBOX_MESSAGE_CHARS.toLocaleString("en-US")} characters.`);
|
|
99
|
+
}
|
|
100
|
+
const message = {
|
|
101
|
+
messageId: `worker-message-${randomUUID()}`,
|
|
102
|
+
kind: input.kind,
|
|
103
|
+
content,
|
|
104
|
+
createdAt: new Date().toISOString(),
|
|
105
|
+
};
|
|
106
|
+
this.update((state) => {
|
|
107
|
+
if (state.messages.length >= MAX_MAILBOX_MESSAGES) {
|
|
108
|
+
throw new Error(`Worker agent mailbox reached its ${MAX_MAILBOX_MESSAGES} message limit.`);
|
|
109
|
+
}
|
|
110
|
+
return { ...state, messages: [...state.messages, message] };
|
|
111
|
+
});
|
|
112
|
+
this.notify();
|
|
113
|
+
return structuredClone(message);
|
|
114
|
+
}
|
|
115
|
+
pending(kind) {
|
|
116
|
+
return this.read()
|
|
117
|
+
.messages.filter((message) => message.deliveredAt === undefined && (kind === undefined || message.kind === kind))
|
|
118
|
+
.map((message) => structuredClone(message));
|
|
119
|
+
}
|
|
120
|
+
acknowledgeDelivered(messageId) {
|
|
121
|
+
const normalized = messageId.trim();
|
|
122
|
+
if (!normalized)
|
|
123
|
+
throw new TypeError("A worker control message id is required.");
|
|
124
|
+
let changed = false;
|
|
125
|
+
this.update((state) => {
|
|
126
|
+
const messages = state.messages.filter((message) => {
|
|
127
|
+
if (message.messageId !== normalized)
|
|
128
|
+
return true;
|
|
129
|
+
changed = true;
|
|
130
|
+
return false;
|
|
131
|
+
});
|
|
132
|
+
return changed ? { ...state, messages } : state;
|
|
133
|
+
});
|
|
134
|
+
if (changed)
|
|
135
|
+
this.notify();
|
|
136
|
+
}
|
|
137
|
+
subscribe(listener) {
|
|
138
|
+
this.listeners.add(listener);
|
|
139
|
+
return () => this.listeners.delete(listener);
|
|
140
|
+
}
|
|
141
|
+
notify() {
|
|
142
|
+
for (const listener of this.listeners)
|
|
143
|
+
listener();
|
|
144
|
+
}
|
|
145
|
+
read() {
|
|
146
|
+
if (!existsSync(this.file)) {
|
|
147
|
+
return { version: 1, parentSessionId: this.parentSessionId, agentId: this.agentId, messages: [] };
|
|
148
|
+
}
|
|
149
|
+
return parseState(readBoundedTextFileSync(this.file, MAX_MAILBOX_BYTES, "Worker agent mailbox durable size bound"), this.parentSessionId, this.agentId);
|
|
150
|
+
}
|
|
151
|
+
update(mutator) {
|
|
152
|
+
withFileLockSync(this.file, () => {
|
|
153
|
+
const state = this.read();
|
|
154
|
+
const next = mutator(state);
|
|
155
|
+
if (next.messages.length > MAX_MAILBOX_MESSAGES || encodedStateBytes(next) > MAX_MAILBOX_BYTES) {
|
|
156
|
+
throw new Error("Worker agent mailbox exceeds its durable size bound.");
|
|
157
|
+
}
|
|
158
|
+
if (JSON.stringify(next) !== JSON.stringify(state))
|
|
159
|
+
writeFileAtomicSync(this.file, `${JSON.stringify(next)}\n`, { mode: 0o600 });
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=worker-agent-control.js.map
|