@caupulican/pi-adaptative 0.90.5 → 0.90.7
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 +14 -0
- package/dist/core/agent-session-contracts.d.ts +2 -0
- 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 +5 -10
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +33 -10
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/delegation/worker-delegation-controller.d.ts +3 -7
- package/dist/core/delegation/worker-delegation-controller.d.ts.map +1 -1
- package/dist/core/delegation/worker-delegation-controller.js.map +1 -1
- package/dist/core/delegation/worker-notification-coordinator.d.ts +2 -0
- package/dist/core/delegation/worker-notification-coordinator.d.ts.map +1 -1
- package/dist/core/delegation/worker-notification-coordinator.js +9 -20
- package/dist/core/delegation/worker-notification-coordinator.js.map +1 -1
- package/dist/core/durable-custom-message-turn-controller.d.ts +24 -0
- package/dist/core/durable-custom-message-turn-controller.d.ts.map +1 -0
- package/dist/core/durable-custom-message-turn-controller.js +60 -0
- package/dist/core/durable-custom-message-turn-controller.js.map +1 -0
- package/dist/core/foreground-terminal-handoff-controller.d.ts +13 -7
- package/dist/core/foreground-terminal-handoff-controller.d.ts.map +1 -1
- package/dist/core/foreground-terminal-handoff-controller.js +48 -10
- package/dist/core/foreground-terminal-handoff-controller.js.map +1 -1
- package/dist/core/goal-loop-controller.d.ts +5 -9
- package/dist/core/goal-loop-controller.d.ts.map +1 -1
- package/dist/core/goal-loop-controller.js +9 -6
- package/dist/core/goal-loop-controller.js.map +1 -1
- package/dist/core/goals/goal-session-controller.d.ts +30 -1
- package/dist/core/goals/goal-session-controller.d.ts.map +1 -1
- package/dist/core/goals/goal-session-controller.js +155 -18
- package/dist/core/goals/goal-session-controller.js.map +1 -1
- package/dist/core/goals/goal-state.d.ts +6 -7
- package/dist/core/goals/goal-state.d.ts.map +1 -1
- package/dist/core/goals/goal-state.js.map +1 -1
- package/dist/core/goals/natural-language-goal.d.ts +6 -0
- package/dist/core/goals/natural-language-goal.d.ts.map +1 -1
- package/dist/core/goals/natural-language-goal.js +36 -1
- package/dist/core/goals/natural-language-goal.js.map +1 -1
- package/dist/core/provider-request-runtime-controller.d.ts +1 -0
- package/dist/core/provider-request-runtime-controller.d.ts.map +1 -1
- package/dist/core/provider-request-runtime-controller.js +10 -2
- package/dist/core/provider-request-runtime-controller.js.map +1 -1
- package/dist/core/provider-tool-text.d.ts.map +1 -1
- package/dist/core/provider-tool-text.js +29 -13
- package/dist/core/provider-tool-text.js.map +1 -1
- package/dist/core/runtime-builder.d.ts +3 -0
- package/dist/core/runtime-builder.d.ts.map +1 -1
- package/dist/core/runtime-builder.js +3 -0
- package/dist/core/runtime-builder.js.map +1 -1
- package/dist/core/tool-recovery-logger.d.ts +2 -0
- package/dist/core/tool-recovery-logger.d.ts.map +1 -1
- package/dist/core/tool-recovery-logger.js +11 -0
- package/dist/core/tool-recovery-logger.js.map +1 -1
- package/dist/core/tools/goal.d.ts +2 -0
- package/dist/core/tools/goal.d.ts.map +1 -1
- package/dist/core/tools/goal.js +9 -0
- package/dist/core/tools/goal.js.map +1 -1
- 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
|
@@ -3,6 +3,8 @@ export interface WorkerTerminalHandoffRecord {
|
|
|
3
3
|
laneId: string;
|
|
4
4
|
status: LaneTerminalStatus;
|
|
5
5
|
reasonCode?: string;
|
|
6
|
+
/** Goal ownership retained until delivery so a stopped goal cannot be resurrected by a late terminal. */
|
|
7
|
+
goalId?: string;
|
|
6
8
|
}
|
|
7
9
|
export interface WorkerNotificationStatus {
|
|
8
10
|
active: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker-notification-coordinator.d.ts","sourceRoot":"","sources":["../../../src/core/delegation/worker-notification-coordinator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"worker-notification-coordinator.d.ts","sourceRoot":"","sources":["../../../src/core/delegation/worker-notification-coordinator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAIlF,MAAM,WAAW,2BAA2B;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,kBAAkB,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yGAAyG;IACzG,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,wBAAwB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,gBAAgB,EAAE,MAAM,CAAC;IACzB,kBAAkB,EAAE,SAAS,2BAA2B,EAAE,CAAC;CAC3D;AAQD,MAAM,WAAW,oCAAoC;IACpD,gBAAgB,IAAI,SAAS,UAAU,EAAE,CAAC;IAC1C,UAAU,CAAC,MAAM,EAAE,wBAAwB,GAAG,IAAI,CAAC;IACnD,MAAM,CAAC,OAAO,EAAE,SAAS,2BAA2B,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,oBAAoB,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;CAC/D;AAED,sGAAsG;AACtG,qBAAa,6BAA6B;IACzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuC;IAC/D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgD;IACxE,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,UAAU,CAA4C;IAC9D,OAAO,CAAC,UAAU,CAAK;IAEvB,YAAY,OAAO,EAAE,oCAAoC,EAExD;IAED,cAAc,CAAC,MAAM,EAAE,UAAU,EAAE,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAcvE;IAED,aAAa,IAAI,IAAI,CAEpB;IAED,OAAO,IAAI,IAAI,CAMd;IAED,OAAO,CAAC,QAAQ;IAQhB,OAAO,CAAC,aAAa;IAWrB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,KAAK;CAoDb"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const HANDOFF_TIMEOUT_MS = 1_800_000;
|
|
2
1
|
const FAILED_TERMINAL_STATUSES = new Set(["failed", "timeout", "budget_exhausted"]);
|
|
3
2
|
/** Event-driven, bounded terminal outbox. Durable worker events can be replayed into it on resume. */
|
|
4
3
|
export class WorkerNotificationCoordinator {
|
|
@@ -20,6 +19,7 @@ export class WorkerNotificationCoordinator {
|
|
|
20
19
|
laneId: record.laneId,
|
|
21
20
|
status: record.status,
|
|
22
21
|
...(record.reasonCode ? { reasonCode: record.reasonCode } : {}),
|
|
22
|
+
...(record.goalId ? { goalId: record.goalId } : {}),
|
|
23
23
|
},
|
|
24
24
|
...(durableNotificationId ? { durableNotificationId } : {}),
|
|
25
25
|
});
|
|
@@ -100,25 +100,14 @@ export class WorkerNotificationCoordinator {
|
|
|
100
100
|
const delivery = this.deliveryTail.then(async () => {
|
|
101
101
|
if (this.disposed)
|
|
102
102
|
return;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}),
|
|
112
|
-
]);
|
|
113
|
-
this.retryCount = 0;
|
|
114
|
-
const durableIds = batch.flatMap((notification) => notification.durableNotificationId ? [notification.durableNotificationId] : []);
|
|
115
|
-
if (durableIds.length > 0)
|
|
116
|
-
this.options.markDurableDelivered(durableIds);
|
|
117
|
-
}
|
|
118
|
-
finally {
|
|
119
|
-
if (timeout)
|
|
120
|
-
clearTimeout(timeout);
|
|
121
|
-
}
|
|
103
|
+
// A foreground lease wait is an in-flight side effect, not a failed attempt. Re-dispatching it
|
|
104
|
+
// on a wall-clock timer creates concurrent consumers that later persist the same handoff.
|
|
105
|
+
// Explicit rejection remains retryable; process restart replays the durable pending record.
|
|
106
|
+
await this.options.notify(terminalSinceFlush);
|
|
107
|
+
this.retryCount = 0;
|
|
108
|
+
const durableIds = batch.flatMap((notification) => notification.durableNotificationId ? [notification.durableNotificationId] : []);
|
|
109
|
+
if (durableIds.length > 0)
|
|
110
|
+
this.options.markDurableDelivered(durableIds);
|
|
122
111
|
});
|
|
123
112
|
this.deliveryTail = delivery.catch((error) => {
|
|
124
113
|
for (const notification of batch)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker-notification-coordinator.js","sourceRoot":"","sources":["../../../src/core/delegation/worker-notification-coordinator.ts"],"names":[],"mappings":"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","sourcesContent":["import type { LaneRecord, LaneTerminalStatus } from \"../autonomy/lane-tracker.ts\";\n\nconst HANDOFF_TIMEOUT_MS = 1_800_000;\nconst FAILED_TERMINAL_STATUSES: ReadonlySet<LaneTerminalStatus> = new Set([\"failed\", \"timeout\", \"budget_exhausted\"]);\n\nexport interface WorkerTerminalHandoffRecord {\n\tlaneId: string;\n\tstatus: LaneTerminalStatus;\n\treasonCode?: string;\n}\n\nexport interface WorkerNotificationStatus {\n\tactive: number;\n\tqueued: number;\n\trunning: number;\n\tcompletedSinceFlush: number;\n\tfailedSinceFlush: number;\n\tterminalSinceFlush: readonly WorkerTerminalHandoffRecord[];\n}\n\ninterface PendingWorkerNotification {\n\tkey: string;\n\trecord: WorkerTerminalHandoffRecord;\n\tdurableNotificationId?: string;\n}\n\nexport interface WorkerNotificationCoordinatorOptions {\n\tgetWorkerRecords(): readonly LaneRecord[];\n\temitStatus(status: WorkerNotificationStatus): void;\n\tnotify(records: readonly WorkerTerminalHandoffRecord[]): Promise<void>;\n\twarn(message: string): void;\n\tmarkDurableDelivered(notificationIds: readonly string[]): void;\n}\n\n/** Event-driven, bounded terminal outbox. Durable worker events can be replayed into it on resume. */\nexport class WorkerNotificationCoordinator {\n\tprivate readonly options: WorkerNotificationCoordinatorOptions;\n\tprivate readonly pending = new Map<string, PendingWorkerNotification>();\n\tprivate scheduled = false;\n\tprivate disposed = false;\n\tprivate deliveryTail = Promise.resolve();\n\tprivate retryTimer: ReturnType<typeof setTimeout> | undefined;\n\tprivate retryCount = 0;\n\n\tconstructor(options: WorkerNotificationCoordinatorOptions) {\n\t\tthis.options = options;\n\t}\n\n\trecordTerminal(record: LaneRecord, durableNotificationId?: string): void {\n\t\tif (record.status === \"queued\" || record.status === \"running\") return;\n\t\tconst key = durableNotificationId ?? `transient:${record.laneId}:${record.completedAt ?? record.status}`;\n\t\tthis.pending.set(key, {\n\t\t\tkey,\n\t\t\trecord: {\n\t\t\t\tlaneId: record.laneId,\n\t\t\t\tstatus: record.status,\n\t\t\t\t...(record.reasonCode ? { reasonCode: record.reasonCode } : {}),\n\t\t\t},\n\t\t\t...(durableNotificationId ? { durableNotificationId } : {}),\n\t\t});\n\t\tthis.schedule();\n\t}\n\n\tstatusChanged(): void {\n\t\tthis.schedule();\n\t}\n\n\tdispose(): void {\n\t\tthis.disposed = true;\n\t\tthis.scheduled = false;\n\t\tif (this.retryTimer) clearTimeout(this.retryTimer);\n\t\tthis.retryTimer = undefined;\n\t\tthis.pending.clear();\n\t}\n\n\tprivate schedule(): void {\n\t\tif (this.disposed || this.scheduled) return;\n\t\tif (this.retryTimer) clearTimeout(this.retryTimer);\n\t\tthis.retryTimer = undefined;\n\t\tthis.scheduled = true;\n\t\tqueueMicrotask(() => this.flush());\n\t}\n\n\tprivate scheduleRetry(): void {\n\t\tif (this.disposed || this.scheduled || this.retryTimer) return;\n\t\tconst delayMs = Math.min(100 * 2 ** Math.min(this.retryCount, 5), 5_000);\n\t\tthis.retryCount++;\n\t\tthis.retryTimer = setTimeout(() => {\n\t\t\tthis.retryTimer = undefined;\n\t\t\tthis.schedule();\n\t\t}, delayMs);\n\t\tif (typeof this.retryTimer === \"object\" && \"unref\" in this.retryTimer) this.retryTimer.unref();\n\t}\n\n\tprivate warnBestEffort(message: string): void {\n\t\ttry {\n\t\t\tthis.options.warn(message);\n\t\t} catch {\n\t\t\t// Diagnostics cannot consume or stop durable notification delivery.\n\t\t}\n\t}\n\n\tprivate flush(): void {\n\t\tthis.scheduled = false;\n\t\tif (this.disposed) return;\n\t\tconst batch = [...this.pending.values()];\n\t\tthis.pending.clear();\n\t\tlet workerRecords: readonly LaneRecord[] = [];\n\t\ttry {\n\t\t\tworkerRecords = this.options.getWorkerRecords();\n\t\t} catch (error) {\n\t\t\tthis.warnBestEffort(\n\t\t\t\t`Background worker status projection failed: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t}\n\t\tconst queued = workerRecords.filter((record) => record.status === \"queued\").length;\n\t\tconst running = workerRecords.filter((record) => record.status === \"running\").length;\n\t\tconst terminalSinceFlush = batch.map((notification) => notification.record);\n\t\tconst status = {\n\t\t\tactive: queued + running,\n\t\t\tqueued,\n\t\t\trunning,\n\t\t\tcompletedSinceFlush: terminalSinceFlush.filter((record) => record.status === \"succeeded\").length,\n\t\t\tfailedSinceFlush: terminalSinceFlush.filter((record) => FAILED_TERMINAL_STATUSES.has(record.status)).length,\n\t\t\tterminalSinceFlush,\n\t\t};\n\t\ttry {\n\t\t\tthis.options.emitStatus(status);\n\t\t} catch (error) {\n\t\t\tthis.warnBestEffort(\n\t\t\t\t`Background worker status observer failed: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t}\n\t\tif (batch.length === 0) return;\n\t\tconst delivery = this.deliveryTail.then(async () => {\n\t\t\tif (this.disposed) return;\n\t\t\tlet timeout: ReturnType<typeof setTimeout> | undefined;\n\t\t\ttry {\n\t\t\t\tawait Promise.race([\n\t\t\t\t\tthis.options.notify(terminalSinceFlush),\n\t\t\t\t\tnew Promise<never>((_resolve, reject) => {\n\t\t\t\t\t\ttimeout = setTimeout(\n\t\t\t\t\t\t\t() => reject(new Error(`worker terminal handoff timed out after ${HANDOFF_TIMEOUT_MS}ms`)),\n\t\t\t\t\t\t\tHANDOFF_TIMEOUT_MS,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (typeof timeout === \"object\" && timeout && \"unref\" in timeout) timeout.unref();\n\t\t\t\t\t}),\n\t\t\t\t]);\n\t\t\t\tthis.retryCount = 0;\n\t\t\t\tconst durableIds = batch.flatMap((notification) =>\n\t\t\t\t\tnotification.durableNotificationId ? [notification.durableNotificationId] : [],\n\t\t\t\t);\n\t\t\t\tif (durableIds.length > 0) this.options.markDurableDelivered(durableIds);\n\t\t\t} finally {\n\t\t\t\tif (timeout) clearTimeout(timeout);\n\t\t\t}\n\t\t});\n\t\tthis.deliveryTail = delivery.catch((error: unknown) => {\n\t\t\tfor (const notification of batch) this.pending.set(notification.key, notification);\n\t\t\tthis.warnBestEffort(\n\t\t\t\t`Background worker handoff failed: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t\tthis.scheduleRetry();\n\t\t});\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"worker-notification-coordinator.js","sourceRoot":"","sources":["../../../src/core/delegation/worker-notification-coordinator.ts"],"names":[],"mappings":"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","sourcesContent":["import type { LaneRecord, LaneTerminalStatus } from \"../autonomy/lane-tracker.ts\";\n\nconst FAILED_TERMINAL_STATUSES: ReadonlySet<LaneTerminalStatus> = new Set([\"failed\", \"timeout\", \"budget_exhausted\"]);\n\nexport interface WorkerTerminalHandoffRecord {\n\tlaneId: string;\n\tstatus: LaneTerminalStatus;\n\treasonCode?: string;\n\t/** Goal ownership retained until delivery so a stopped goal cannot be resurrected by a late terminal. */\n\tgoalId?: string;\n}\n\nexport interface WorkerNotificationStatus {\n\tactive: number;\n\tqueued: number;\n\trunning: number;\n\tcompletedSinceFlush: number;\n\tfailedSinceFlush: number;\n\tterminalSinceFlush: readonly WorkerTerminalHandoffRecord[];\n}\n\ninterface PendingWorkerNotification {\n\tkey: string;\n\trecord: WorkerTerminalHandoffRecord;\n\tdurableNotificationId?: string;\n}\n\nexport interface WorkerNotificationCoordinatorOptions {\n\tgetWorkerRecords(): readonly LaneRecord[];\n\temitStatus(status: WorkerNotificationStatus): void;\n\tnotify(records: readonly WorkerTerminalHandoffRecord[]): Promise<void>;\n\twarn(message: string): void;\n\tmarkDurableDelivered(notificationIds: readonly string[]): void;\n}\n\n/** Event-driven, bounded terminal outbox. Durable worker events can be replayed into it on resume. */\nexport class WorkerNotificationCoordinator {\n\tprivate readonly options: WorkerNotificationCoordinatorOptions;\n\tprivate readonly pending = new Map<string, PendingWorkerNotification>();\n\tprivate scheduled = false;\n\tprivate disposed = false;\n\tprivate deliveryTail = Promise.resolve();\n\tprivate retryTimer: ReturnType<typeof setTimeout> | undefined;\n\tprivate retryCount = 0;\n\n\tconstructor(options: WorkerNotificationCoordinatorOptions) {\n\t\tthis.options = options;\n\t}\n\n\trecordTerminal(record: LaneRecord, durableNotificationId?: string): void {\n\t\tif (record.status === \"queued\" || record.status === \"running\") return;\n\t\tconst key = durableNotificationId ?? `transient:${record.laneId}:${record.completedAt ?? record.status}`;\n\t\tthis.pending.set(key, {\n\t\t\tkey,\n\t\t\trecord: {\n\t\t\t\tlaneId: record.laneId,\n\t\t\t\tstatus: record.status,\n\t\t\t\t...(record.reasonCode ? { reasonCode: record.reasonCode } : {}),\n\t\t\t\t...(record.goalId ? { goalId: record.goalId } : {}),\n\t\t\t},\n\t\t\t...(durableNotificationId ? { durableNotificationId } : {}),\n\t\t});\n\t\tthis.schedule();\n\t}\n\n\tstatusChanged(): void {\n\t\tthis.schedule();\n\t}\n\n\tdispose(): void {\n\t\tthis.disposed = true;\n\t\tthis.scheduled = false;\n\t\tif (this.retryTimer) clearTimeout(this.retryTimer);\n\t\tthis.retryTimer = undefined;\n\t\tthis.pending.clear();\n\t}\n\n\tprivate schedule(): void {\n\t\tif (this.disposed || this.scheduled) return;\n\t\tif (this.retryTimer) clearTimeout(this.retryTimer);\n\t\tthis.retryTimer = undefined;\n\t\tthis.scheduled = true;\n\t\tqueueMicrotask(() => this.flush());\n\t}\n\n\tprivate scheduleRetry(): void {\n\t\tif (this.disposed || this.scheduled || this.retryTimer) return;\n\t\tconst delayMs = Math.min(100 * 2 ** Math.min(this.retryCount, 5), 5_000);\n\t\tthis.retryCount++;\n\t\tthis.retryTimer = setTimeout(() => {\n\t\t\tthis.retryTimer = undefined;\n\t\t\tthis.schedule();\n\t\t}, delayMs);\n\t\tif (typeof this.retryTimer === \"object\" && \"unref\" in this.retryTimer) this.retryTimer.unref();\n\t}\n\n\tprivate warnBestEffort(message: string): void {\n\t\ttry {\n\t\t\tthis.options.warn(message);\n\t\t} catch {\n\t\t\t// Diagnostics cannot consume or stop durable notification delivery.\n\t\t}\n\t}\n\n\tprivate flush(): void {\n\t\tthis.scheduled = false;\n\t\tif (this.disposed) return;\n\t\tconst batch = [...this.pending.values()];\n\t\tthis.pending.clear();\n\t\tlet workerRecords: readonly LaneRecord[] = [];\n\t\ttry {\n\t\t\tworkerRecords = this.options.getWorkerRecords();\n\t\t} catch (error) {\n\t\t\tthis.warnBestEffort(\n\t\t\t\t`Background worker status projection failed: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t}\n\t\tconst queued = workerRecords.filter((record) => record.status === \"queued\").length;\n\t\tconst running = workerRecords.filter((record) => record.status === \"running\").length;\n\t\tconst terminalSinceFlush = batch.map((notification) => notification.record);\n\t\tconst status = {\n\t\t\tactive: queued + running,\n\t\t\tqueued,\n\t\t\trunning,\n\t\t\tcompletedSinceFlush: terminalSinceFlush.filter((record) => record.status === \"succeeded\").length,\n\t\t\tfailedSinceFlush: terminalSinceFlush.filter((record) => FAILED_TERMINAL_STATUSES.has(record.status)).length,\n\t\t\tterminalSinceFlush,\n\t\t};\n\t\ttry {\n\t\t\tthis.options.emitStatus(status);\n\t\t} catch (error) {\n\t\t\tthis.warnBestEffort(\n\t\t\t\t`Background worker status observer failed: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t}\n\t\tif (batch.length === 0) return;\n\t\tconst delivery = this.deliveryTail.then(async () => {\n\t\t\tif (this.disposed) return;\n\t\t\t// A foreground lease wait is an in-flight side effect, not a failed attempt. Re-dispatching it\n\t\t\t// on a wall-clock timer creates concurrent consumers that later persist the same handoff.\n\t\t\t// Explicit rejection remains retryable; process restart replays the durable pending record.\n\t\t\tawait this.options.notify(terminalSinceFlush);\n\t\t\tthis.retryCount = 0;\n\t\t\tconst durableIds = batch.flatMap((notification) =>\n\t\t\t\tnotification.durableNotificationId ? [notification.durableNotificationId] : [],\n\t\t\t);\n\t\t\tif (durableIds.length > 0) this.options.markDurableDelivered(durableIds);\n\t\t});\n\t\tthis.deliveryTail = delivery.catch((error: unknown) => {\n\t\t\tfor (const notification of batch) this.pending.set(notification.key, notification);\n\t\t\tthis.warnBestEffort(\n\t\t\t\t`Background worker handoff failed: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t\tthis.scheduleRetry();\n\t\t});\n\t}\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { CustomMessage } from "@caupulican/pi-agent-core";
|
|
2
|
+
import type { ForegroundRecoveryController, ForegroundSubmissionLease } from "./foreground-recovery-controller.ts";
|
|
3
|
+
import type { GoalSessionController } from "./goals/goal-session-controller.ts";
|
|
4
|
+
interface DurableCustomMessageTurnControllerDeps {
|
|
5
|
+
foreground: ForegroundRecoveryController;
|
|
6
|
+
goals: Pick<GoalSessionController, "beginExecution" | "endExecution">;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Starts an internal custom-message turn and separates durable input acceptance from full model-run
|
|
10
|
+
* completion. Notification outboxes may acknowledge after `start` resolves, while the foreground
|
|
11
|
+
* owner retains its lease until the returned completion settles.
|
|
12
|
+
*/
|
|
13
|
+
export declare class DurableCustomMessageTurnController {
|
|
14
|
+
private readonly deps;
|
|
15
|
+
private readonly pending;
|
|
16
|
+
constructor(deps: DurableCustomMessageTurnControllerDeps);
|
|
17
|
+
/** Accept only the exact custom-message object after SessionManager append succeeds. */
|
|
18
|
+
notePersisted(message: CustomMessage<unknown>): boolean;
|
|
19
|
+
start<T>(message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details">, submissionLease: ForegroundSubmissionLease, goalId?: string): Promise<{
|
|
20
|
+
completion: Promise<void>;
|
|
21
|
+
}>;
|
|
22
|
+
}
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=durable-custom-message-turn-controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"durable-custom-message-turn-controller.d.ts","sourceRoot":"","sources":["../../src/core/durable-custom-message-turn-controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,KAAK,EAAE,4BAA4B,EAAE,yBAAyB,EAAE,MAAM,qCAAqC,CAAC;AACnH,OAAO,KAAK,EAAsB,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAEpG,UAAU,sCAAsC;IAC/C,UAAU,EAAE,4BAA4B,CAAC;IACzC,KAAK,EAAE,IAAI,CAAC,qBAAqB,EAAE,gBAAgB,GAAG,cAAc,CAAC,CAAC;CACtE;AAED;;;;GAIG;AACH,qBAAa,kCAAkC;IAC9C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyC;IAC9D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwF;IAEhH,YAAY,IAAI,EAAE,sCAAsC,EAEvD;IAED,wFAAwF;IACxF,aAAa,CAAC,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG,OAAO,CAMtD;IAEK,KAAK,CAAC,CAAC,EACZ,OAAO,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,YAAY,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC,EACjF,eAAe,EAAE,yBAAyB,EAC1C,MAAM,CAAC,EAAE,MAAM,GACb,OAAO,CAAC;QAAE,UAAU,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAC,CAsCxC;CACD"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Starts an internal custom-message turn and separates durable input acceptance from full model-run
|
|
3
|
+
* completion. Notification outboxes may acknowledge after `start` resolves, while the foreground
|
|
4
|
+
* owner retains its lease until the returned completion settles.
|
|
5
|
+
*/
|
|
6
|
+
export class DurableCustomMessageTurnController {
|
|
7
|
+
constructor(deps) {
|
|
8
|
+
this.pending = new Map();
|
|
9
|
+
this.deps = deps;
|
|
10
|
+
}
|
|
11
|
+
/** Accept only the exact custom-message object after SessionManager append succeeds. */
|
|
12
|
+
notePersisted(message) {
|
|
13
|
+
const acceptance = this.pending.get(message);
|
|
14
|
+
if (!acceptance)
|
|
15
|
+
return false;
|
|
16
|
+
this.pending.delete(message);
|
|
17
|
+
acceptance.resolve();
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
async start(message, submissionLease, goalId) {
|
|
21
|
+
const appMessage = {
|
|
22
|
+
role: "custom",
|
|
23
|
+
customType: message.customType,
|
|
24
|
+
content: message.content,
|
|
25
|
+
display: message.display,
|
|
26
|
+
details: message.details,
|
|
27
|
+
timestamp: Date.now(),
|
|
28
|
+
};
|
|
29
|
+
let resolveAccepted;
|
|
30
|
+
let rejectAccepted;
|
|
31
|
+
const acceptedPromise = new Promise((resolve, reject) => {
|
|
32
|
+
resolveAccepted = resolve;
|
|
33
|
+
rejectAccepted = reject;
|
|
34
|
+
});
|
|
35
|
+
this.pending.set(appMessage, { resolve: resolveAccepted, reject: rejectAccepted });
|
|
36
|
+
let goalExecutionLease;
|
|
37
|
+
try {
|
|
38
|
+
goalExecutionLease = this.deps.goals.beginExecution(goalId);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
this.pending.delete(appMessage);
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
const completion = this.deps.foreground
|
|
45
|
+
.runAgentPrompt(appMessage, submissionLease)
|
|
46
|
+
.finally(() => this.deps.goals.endExecution(goalExecutionLease));
|
|
47
|
+
void completion.then(() => {
|
|
48
|
+
if (!this.pending.delete(appMessage))
|
|
49
|
+
return;
|
|
50
|
+
rejectAccepted(new Error("Custom-message turn ended before its input was persisted"));
|
|
51
|
+
}, (error) => {
|
|
52
|
+
if (!this.pending.delete(appMessage))
|
|
53
|
+
return;
|
|
54
|
+
rejectAccepted(error);
|
|
55
|
+
});
|
|
56
|
+
await acceptedPromise;
|
|
57
|
+
return { completion };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=durable-custom-message-turn-controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"durable-custom-message-turn-controller.js","sourceRoot":"","sources":["../../src/core/durable-custom-message-turn-controller.ts"],"names":[],"mappings":"AASA;;;;GAIG;AACH,MAAM,OAAO,kCAAkC;IAI9C,YAAY,IAA4C;QAFvC,YAAO,GAAG,IAAI,GAAG,EAA6E,CAAC;QAG/G,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IAClB,CAAC;IAED,wFAAwF;IACxF,aAAa,CAAC,OAA+B;QAC5C,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC7C,IAAI,CAAC,UAAU;YAAE,OAAO,KAAK,CAAC;QAC9B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC7B,UAAU,CAAC,OAAO,EAAE,CAAC;QACrB,OAAO,IAAI,CAAC;IACb,CAAC;IAED,KAAK,CAAC,KAAK,CACV,OAAiF,EACjF,eAA0C,EAC1C,MAAe;QAEf,MAAM,UAAU,GAAG;YAClB,IAAI,EAAE,QAAiB;YACvB,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACM,CAAC;QAC7B,IAAI,eAA4B,CAAC;QACjC,IAAI,cAAyC,CAAC;QAC9C,MAAM,eAAe,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC7D,eAAe,GAAG,OAAO,CAAC;YAC1B,cAAc,GAAG,MAAM,CAAC;QACzB,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,CAAC;QACnF,IAAI,kBAAkD,CAAC;QACvD,IAAI,CAAC;YACJ,kBAAkB,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;QAC7D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YAChC,MAAM,KAAK,CAAC;QACb,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU;aACrC,cAAc,CAAC,UAAU,EAAE,eAAe,CAAC;aAC3C,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,kBAAkB,CAAC,CAAC,CAAC;QAClE,KAAK,UAAU,CAAC,IAAI,CACnB,GAAG,EAAE;YACJ,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC;gBAAE,OAAO;YAC7C,cAAc,CAAC,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC,CAAC;QACvF,CAAC,EACD,CAAC,KAAc,EAAE,EAAE;YAClB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC;gBAAE,OAAO;YAC7C,cAAc,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC,CACD,CAAC;QACF,MAAM,eAAe,CAAC;QACtB,OAAO,EAAE,UAAU,EAAE,CAAC;IACvB,CAAC;CACD","sourcesContent":["import type { CustomMessage } from \"@caupulican/pi-agent-core\";\nimport type { ForegroundRecoveryController, ForegroundSubmissionLease } from \"./foreground-recovery-controller.ts\";\nimport type { GoalExecutionLease, GoalSessionController } from \"./goals/goal-session-controller.ts\";\n\ninterface DurableCustomMessageTurnControllerDeps {\n\tforeground: ForegroundRecoveryController;\n\tgoals: Pick<GoalSessionController, \"beginExecution\" | \"endExecution\">;\n}\n\n/**\n * Starts an internal custom-message turn and separates durable input acceptance from full model-run\n * completion. Notification outboxes may acknowledge after `start` resolves, while the foreground\n * owner retains its lease until the returned completion settles.\n */\nexport class DurableCustomMessageTurnController {\n\tprivate readonly deps: DurableCustomMessageTurnControllerDeps;\n\tprivate readonly pending = new Map<CustomMessage<unknown>, { resolve(): void; reject(error: unknown): void }>();\n\n\tconstructor(deps: DurableCustomMessageTurnControllerDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\t/** Accept only the exact custom-message object after SessionManager append succeeds. */\n\tnotePersisted(message: CustomMessage<unknown>): boolean {\n\t\tconst acceptance = this.pending.get(message);\n\t\tif (!acceptance) return false;\n\t\tthis.pending.delete(message);\n\t\tacceptance.resolve();\n\t\treturn true;\n\t}\n\n\tasync start<T>(\n\t\tmessage: Pick<CustomMessage<T>, \"customType\" | \"content\" | \"display\" | \"details\">,\n\t\tsubmissionLease: ForegroundSubmissionLease,\n\t\tgoalId?: string,\n\t): Promise<{ completion: Promise<void> }> {\n\t\tconst appMessage = {\n\t\t\trole: \"custom\" as const,\n\t\t\tcustomType: message.customType,\n\t\t\tcontent: message.content,\n\t\t\tdisplay: message.display,\n\t\t\tdetails: message.details,\n\t\t\ttimestamp: Date.now(),\n\t\t} satisfies CustomMessage<T>;\n\t\tlet resolveAccepted!: () => void;\n\t\tlet rejectAccepted!: (error: unknown) => void;\n\t\tconst acceptedPromise = new Promise<void>((resolve, reject) => {\n\t\t\tresolveAccepted = resolve;\n\t\t\trejectAccepted = reject;\n\t\t});\n\t\tthis.pending.set(appMessage, { resolve: resolveAccepted, reject: rejectAccepted });\n\t\tlet goalExecutionLease: GoalExecutionLease | undefined;\n\t\ttry {\n\t\t\tgoalExecutionLease = this.deps.goals.beginExecution(goalId);\n\t\t} catch (error) {\n\t\t\tthis.pending.delete(appMessage);\n\t\t\tthrow error;\n\t\t}\n\t\tconst completion = this.deps.foreground\n\t\t\t.runAgentPrompt(appMessage, submissionLease)\n\t\t\t.finally(() => this.deps.goals.endExecution(goalExecutionLease));\n\t\tvoid completion.then(\n\t\t\t() => {\n\t\t\t\tif (!this.pending.delete(appMessage)) return;\n\t\t\t\trejectAccepted(new Error(\"Custom-message turn ended before its input was persisted\"));\n\t\t\t},\n\t\t\t(error: unknown) => {\n\t\t\t\tif (!this.pending.delete(appMessage)) return;\n\t\t\t\trejectAccepted(error);\n\t\t\t},\n\t\t);\n\t\tawait acceptedPromise;\n\t\treturn { completion };\n\t}\n}\n"]}
|
|
@@ -1,31 +1,37 @@
|
|
|
1
1
|
import type { CustomMessage } from "@caupulican/pi-agent-core";
|
|
2
2
|
import type { LaneTerminalStatus } from "./autonomy/lane-tracker.ts";
|
|
3
3
|
import { type BackgroundToolTaskRecord } from "./background-tool-task-controller.ts";
|
|
4
|
+
import type { WorkerTerminalHandoffRecord } from "./delegation/worker-notification-coordinator.ts";
|
|
4
5
|
import type { ForegroundRecoveryController, ForegroundSubmissionLease } from "./foreground-recovery-controller.ts";
|
|
6
|
+
import { type GoalState } from "./goals/goal-state.ts";
|
|
5
7
|
interface ForegroundTerminalHandoffControllerDeps {
|
|
6
8
|
foreground: ForegroundRecoveryController;
|
|
7
9
|
isDisposed(): boolean;
|
|
10
|
+
getGoalStateSnapshot(): Pick<GoalState, "goalId" | "status"> | undefined;
|
|
8
11
|
workerInputsWillWakeParent(workerRequestIds: readonly string[]): boolean;
|
|
12
|
+
startCustomMessageTurn(message: Pick<CustomMessage<unknown>, "customType" | "content" | "display" | "details">, lease: ForegroundSubmissionLease, goalId?: string): Promise<{
|
|
13
|
+
completion: Promise<void>;
|
|
14
|
+
}>;
|
|
9
15
|
sendCustomMessage(message: Pick<CustomMessage<unknown>, "customType" | "content" | "display" | "details">, options: {
|
|
10
|
-
triggerTurn:
|
|
16
|
+
triggerTurn: false;
|
|
11
17
|
deliverAs: "followUp";
|
|
12
18
|
}, lease: ForegroundSubmissionLease): Promise<void>;
|
|
19
|
+
warn(message: string): void;
|
|
13
20
|
}
|
|
14
21
|
export declare function buildForegroundWorkerTerminalHandoffContent(records: readonly {
|
|
15
22
|
laneId: string;
|
|
16
23
|
status: LaneTerminalStatus;
|
|
17
24
|
reasonCode?: string;
|
|
18
|
-
}[]
|
|
25
|
+
}[], options?: {
|
|
26
|
+
wakeParent?: boolean;
|
|
27
|
+
}): string;
|
|
19
28
|
/** Serializes durable background terminal delivery through the foreground submission owner. */
|
|
20
29
|
export declare class ForegroundTerminalHandoffController {
|
|
21
30
|
private readonly deps;
|
|
22
31
|
constructor(deps: ForegroundTerminalHandoffControllerDeps);
|
|
23
|
-
notifyWorkers(records: readonly
|
|
24
|
-
laneId: string;
|
|
25
|
-
status: LaneTerminalStatus;
|
|
26
|
-
reasonCode?: string;
|
|
27
|
-
}[]): Promise<void>;
|
|
32
|
+
notifyWorkers(records: readonly WorkerTerminalHandoffRecord[]): Promise<void>;
|
|
28
33
|
notifyTools(records: readonly BackgroundToolTaskRecord[], wakeParent: boolean): Promise<void>;
|
|
34
|
+
private releaseAfterTurn;
|
|
29
35
|
private assertLive;
|
|
30
36
|
}
|
|
31
37
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"foreground-terminal-handoff-controller.d.ts","sourceRoot":"","sources":["../../src/core/foreground-terminal-handoff-controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EACN,KAAK,wBAAwB,EAE7B,MAAM,sCAAsC,CAAC;
|
|
1
|
+
{"version":3,"file":"foreground-terminal-handoff-controller.d.ts","sourceRoot":"","sources":["../../src/core/foreground-terminal-handoff-controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EACN,KAAK,wBAAwB,EAE7B,MAAM,sCAAsC,CAAC;AAC9C,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,iDAAiD,CAAC;AAEnG,OAAO,KAAK,EAAE,4BAA4B,EAAE,yBAAyB,EAAE,MAAM,qCAAqC,CAAC;AACnH,OAAO,EAAE,KAAK,SAAS,EAAyB,MAAM,uBAAuB,CAAC;AAE9E,UAAU,uCAAuC;IAChD,UAAU,EAAE,4BAA4B,CAAC;IACzC,UAAU,IAAI,OAAO,CAAC;IACtB,oBAAoB,IAAI,IAAI,CAAC,SAAS,EAAE,QAAQ,GAAG,QAAQ,CAAC,GAAG,SAAS,CAAC;IACzE,0BAA0B,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC;IACzE,sBAAsB,CACrB,OAAO,EAAE,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,YAAY,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC,EACvF,KAAK,EAAE,yBAAyB,EAChC,MAAM,CAAC,EAAE,MAAM,GACb,OAAO,CAAC;QAAE,UAAU,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAC,CAAC;IAC1C,iBAAiB,CAChB,OAAO,EAAE,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,YAAY,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC,EACvF,OAAO,EAAE;QAAE,WAAW,EAAE,KAAK,CAAC;QAAC,SAAS,EAAE,UAAU,CAAA;KAAE,EACtD,KAAK,EAAE,yBAAyB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,wBAAgB,2CAA2C,CAC1D,OAAO,EAAE,SAAS;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,kBAAkB,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,EACvF,OAAO,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GAChC,MAAM,CAmCR;AAED,+FAA+F;AAC/F,qBAAa,mCAAmC;IAC/C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA0C;IAE/D,YAAY,IAAI,EAAE,uCAAuC,EAExD;IAEK,aAAa,CAAC,OAAO,EAAE,SAAS,2BAA2B,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAoClF;IAEK,WAAW,CAAC,OAAO,EAAE,SAAS,wBAAwB,EAAE,EAAE,UAAU,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAalG;IAED,OAAO,CAAC,gBAAgB;IAYxB,OAAO,CAAC,UAAU;CAGlB"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { createBackgroundToolTerminalMessage, } from "./background-tool-task-controller.js";
|
|
2
2
|
import { WORKER_COMPLETION_ERROR_CAVEMAN_GUIDANCE } from "./delegation/worker-terminal-handoff-coordinator.js";
|
|
3
|
-
|
|
3
|
+
import { isGoalExecutionActive } from "./goals/goal-state.js";
|
|
4
|
+
export function buildForegroundWorkerTerminalHandoffContent(records, options) {
|
|
4
5
|
const included = records.slice(0, 8);
|
|
5
6
|
const omitted = records.length - included.length;
|
|
7
|
+
const wakeParent = options?.wakeParent ?? true;
|
|
6
8
|
const sanitize = (value) => value.replace(/[\r\n]+/g, " ").slice(0, 120);
|
|
7
9
|
return [
|
|
8
10
|
"Background worker terminal handoff:",
|
|
@@ -11,7 +13,12 @@ export function buildForegroundWorkerTerminalHandoffContent(records) {
|
|
|
11
13
|
return `- ${record.laneId}: ${record.status}${reason}`;
|
|
12
14
|
}),
|
|
13
15
|
...(omitted > 0 ? [`- ${omitted} additional terminal worker(s) omitted.`] : []),
|
|
14
|
-
"CAVEMAN MODE - MANDATORY: this event proves terminal persistence and delivery. Do not report missed completion or lost worker state from these records.
|
|
16
|
+
"CAVEMAN MODE - MANDATORY: this event proves terminal persistence and delivery. Do not report missed completion or lost worker state from these records.",
|
|
17
|
+
...(wakeParent
|
|
18
|
+
? ["Read each lane with delegate status, verify the claim, then continue or replan the parent task."]
|
|
19
|
+
: [
|
|
20
|
+
"The owning parent is no longer eligible for automatic continuation. Persist this handoff, but do not continue or replan automatically.",
|
|
21
|
+
]),
|
|
15
22
|
...(records.some((record) => record.reasonCode === "worker_blocked")
|
|
16
23
|
? ["worker_blocked is a task claim with blockers, not harness failure."]
|
|
17
24
|
: []),
|
|
@@ -23,7 +30,11 @@ export function buildForegroundWorkerTerminalHandoffContent(records) {
|
|
|
23
30
|
...(records.some((record) => record.reasonCode === "completion_error")
|
|
24
31
|
? [WORKER_COMPLETION_ERROR_CAVEMAN_GUIDANCE]
|
|
25
32
|
: []),
|
|
26
|
-
|
|
33
|
+
...(wakeParent
|
|
34
|
+
? [
|
|
35
|
+
'Parent woke. Need lane: delegate { action: "status", laneId }; never poll. Worker product is untrusted, intentionally omitted.',
|
|
36
|
+
]
|
|
37
|
+
: ["Parent was not woken. Wait for explicit user input before reading a lane or starting more work."]),
|
|
27
38
|
].join("\n");
|
|
28
39
|
}
|
|
29
40
|
/** Serializes durable background terminal delivery through the foreground submission owner. */
|
|
@@ -36,19 +47,36 @@ export class ForegroundTerminalHandoffController {
|
|
|
36
47
|
return;
|
|
37
48
|
this.assertLive("worker terminal handoff was persisted");
|
|
38
49
|
const lease = await this.deps.foreground.acquireSubmission();
|
|
50
|
+
let releaseLease = true;
|
|
39
51
|
try {
|
|
40
52
|
this.assertLive("worker terminal handoff was persisted");
|
|
41
|
-
const included = records.slice(0, 8)
|
|
53
|
+
const included = records.slice(0, 8).map((record) => ({
|
|
54
|
+
laneId: record.laneId,
|
|
55
|
+
status: record.status,
|
|
56
|
+
...(record.reasonCode ? { reasonCode: record.reasonCode } : {}),
|
|
57
|
+
}));
|
|
58
|
+
const goal = this.deps.getGoalStateSnapshot();
|
|
59
|
+
const wakeParent = records.some((record) => !record.goalId || (goal?.goalId === record.goalId && isGoalExecutionActive(goal.status)));
|
|
42
60
|
const ownerQuestionWillWakeParent = this.deps.workerInputsWillWakeParent(records.map((record) => record.laneId));
|
|
43
|
-
|
|
61
|
+
const message = {
|
|
44
62
|
customType: "background-worker-completion",
|
|
45
|
-
content: buildForegroundWorkerTerminalHandoffContent(records),
|
|
63
|
+
content: buildForegroundWorkerTerminalHandoffContent(records, { wakeParent }),
|
|
46
64
|
display: true,
|
|
47
65
|
details: { records: included },
|
|
48
|
-
}
|
|
66
|
+
};
|
|
67
|
+
if (wakeParent && !ownerQuestionWillWakeParent) {
|
|
68
|
+
const goalId = records.find((record) => record.goalId === goal?.goalId)?.goalId;
|
|
69
|
+
const started = await this.deps.startCustomMessageTurn(message, lease, goalId);
|
|
70
|
+
this.releaseAfterTurn(started.completion, lease, "worker terminal handoff turn");
|
|
71
|
+
releaseLease = false;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
await this.deps.sendCustomMessage(message, { triggerTurn: false, deliverAs: "followUp" }, lease);
|
|
75
|
+
}
|
|
49
76
|
}
|
|
50
77
|
finally {
|
|
51
|
-
|
|
78
|
+
if (releaseLease)
|
|
79
|
+
this.deps.foreground.releaseSubmission(lease);
|
|
52
80
|
}
|
|
53
81
|
}
|
|
54
82
|
async notifyTools(records, wakeParent) {
|
|
@@ -56,14 +84,24 @@ export class ForegroundTerminalHandoffController {
|
|
|
56
84
|
return;
|
|
57
85
|
this.assertLive("background tool terminal handoff was delivered");
|
|
58
86
|
const lease = await this.deps.foreground.acquireSubmission();
|
|
87
|
+
let releaseLease = true;
|
|
59
88
|
try {
|
|
60
89
|
this.assertLive("background tool terminal handoff was delivered");
|
|
61
|
-
await this.deps.
|
|
90
|
+
const started = await this.deps.startCustomMessageTurn(createBackgroundToolTerminalMessage(records), lease);
|
|
91
|
+
this.releaseAfterTurn(started.completion, lease, "background tool terminal handoff turn");
|
|
92
|
+
releaseLease = false;
|
|
62
93
|
}
|
|
63
94
|
finally {
|
|
64
|
-
|
|
95
|
+
if (releaseLease)
|
|
96
|
+
this.deps.foreground.releaseSubmission(lease);
|
|
65
97
|
}
|
|
66
98
|
}
|
|
99
|
+
releaseAfterTurn(completion, lease, label) {
|
|
100
|
+
void completion.then(() => this.deps.foreground.releaseSubmission(lease), (error) => {
|
|
101
|
+
this.deps.foreground.releaseSubmission(lease);
|
|
102
|
+
this.deps.warn(`${label} failed after durable delivery: ${error instanceof Error ? error.message : String(error)}`);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
67
105
|
assertLive(action) {
|
|
68
106
|
if (this.deps.isDisposed())
|
|
69
107
|
throw new Error(`Session disposed before ${action}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"foreground-terminal-handoff-controller.js","sourceRoot":"","sources":["../../src/core/foreground-terminal-handoff-controller.ts"],"names":[],"mappings":"AAEA,OAAO,EAEN,mCAAmC,GACnC,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,wCAAwC,EAAE,MAAM,qDAAqD,CAAC;AAc/G,MAAM,UAAU,2CAA2C,CAC1D,OAAuF;IAEvF,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACrC,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC;IACjD,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACzF,OAAO;QACN,qCAAqC;QACrC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;YAC1B,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACjF,OAAO,KAAK,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,GAAG,MAAM,EAAE,CAAC;QACxD,CAAC,CAAC;QACF,GAAG,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,OAAO,yCAAyC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/E,yPAAyP;QACzP,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,KAAK,gBAAgB,CAAC;YACnE,CAAC,CAAC,CAAC,oEAAoE,CAAC;YACxE,CAAC,CAAC,EAAE,CAAC;QACN,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,KAAK,kBAAkB,CAAC;YACjE,CAAC,CAAC;gBACA,oPAAoP;aACpP;YACF,CAAC,CAAC,EAAE,CAAC;QACN,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,KAAK,kBAAkB,CAAC;YACrE,CAAC,CAAC,CAAC,wCAAwC,CAAC;YAC5C,CAAC,CAAC,EAAE,CAAC;QACN,gIAAgI;KAChI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACd,CAAC;AAED,+FAA+F;AAC/F,MAAM,OAAO,mCAAmC;IAG/C,YAAY,IAA6C;QACxD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IAClB,CAAC;IAED,KAAK,CAAC,aAAa,CAClB,OAAuF;QAEvF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QACjC,IAAI,CAAC,UAAU,CAAC,uCAAuC,CAAC,CAAC;QACzD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC;QAC7D,IAAI,CAAC;YACJ,IAAI,CAAC,UAAU,CAAC,uCAAuC,CAAC,CAAC;YACzD,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACrC,MAAM,2BAA2B,GAAG,IAAI,CAAC,IAAI,CAAC,0BAA0B,CACvE,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CACtC,CAAC;YACF,MAAM,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAChC;gBACC,UAAU,EAAE,8BAA8B;gBAC1C,OAAO,EAAE,2CAA2C,CAAC,OAAO,CAAC;gBAC7D,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE;aAC9B,EACD,EAAE,WAAW,EAAE,CAAC,2BAA2B,EAAE,SAAS,EAAE,UAAU,EAAE,EACpE,KAAK,CACL,CAAC;QACH,CAAC;gBAAS,CAAC;YACV,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;QAC/C,CAAC;IACF,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,OAA4C,EAAE,UAAmB;QAClF,IAAI,CAAC,UAAU;YAAE,OAAO;QACxB,IAAI,CAAC,UAAU,CAAC,gDAAgD,CAAC,CAAC;QAClE,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC;QAC7D,IAAI,CAAC;YACJ,IAAI,CAAC,UAAU,CAAC,gDAAgD,CAAC,CAAC;YAClE,MAAM,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAChC,mCAAmC,CAAC,OAAO,CAAC,EAC5C,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,EAC5C,KAAK,CACL,CAAC;QACH,CAAC;gBAAS,CAAC;YACV,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;QAC/C,CAAC;IACF,CAAC;IAEO,UAAU,CAAC,MAAc;QAChC,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,MAAM,EAAE,CAAC,CAAC;IAClF,CAAC;CACD","sourcesContent":["import type { CustomMessage } from \"@caupulican/pi-agent-core\";\nimport type { LaneTerminalStatus } from \"./autonomy/lane-tracker.ts\";\nimport {\n\ttype BackgroundToolTaskRecord,\n\tcreateBackgroundToolTerminalMessage,\n} from \"./background-tool-task-controller.ts\";\nimport { WORKER_COMPLETION_ERROR_CAVEMAN_GUIDANCE } from \"./delegation/worker-terminal-handoff-coordinator.ts\";\nimport type { ForegroundRecoveryController, ForegroundSubmissionLease } from \"./foreground-recovery-controller.ts\";\n\ninterface ForegroundTerminalHandoffControllerDeps {\n\tforeground: ForegroundRecoveryController;\n\tisDisposed(): boolean;\n\tworkerInputsWillWakeParent(workerRequestIds: readonly string[]): boolean;\n\tsendCustomMessage(\n\t\tmessage: Pick<CustomMessage<unknown>, \"customType\" | \"content\" | \"display\" | \"details\">,\n\t\toptions: { triggerTurn: boolean; deliverAs: \"followUp\" },\n\t\tlease: ForegroundSubmissionLease,\n\t): Promise<void>;\n}\n\nexport function buildForegroundWorkerTerminalHandoffContent(\n\trecords: readonly { laneId: string; status: LaneTerminalStatus; reasonCode?: string }[],\n): string {\n\tconst included = records.slice(0, 8);\n\tconst omitted = records.length - included.length;\n\tconst sanitize = (value: string): string => value.replace(/[\\r\\n]+/g, \" \").slice(0, 120);\n\treturn [\n\t\t\"Background worker terminal handoff:\",\n\t\t...included.map((record) => {\n\t\t\tconst reason = record.reasonCode ? ` reason=${sanitize(record.reasonCode)}` : \"\";\n\t\t\treturn `- ${record.laneId}: ${record.status}${reason}`;\n\t\t}),\n\t\t...(omitted > 0 ? [`- ${omitted} additional terminal worker(s) omitted.`] : []),\n\t\t\"CAVEMAN MODE - MANDATORY: this event proves terminal persistence and delivery. Do not report missed completion or lost worker state from these records. Read each lane with delegate status, verify the claim, then continue or replan the parent task.\",\n\t\t...(records.some((record) => record.reasonCode === \"worker_blocked\")\n\t\t\t? [\"worker_blocked is a task claim with blockers, not harness failure.\"]\n\t\t\t: []),\n\t\t...(records.some((record) => record.status === \"budget_exhausted\")\n\t\t\t? [\n\t\t\t\t\t\"CAVEMAN MODE - MANDATORY: budget_exhausted means an admitted limit ended work, not harness failure. Terminal reason is authoritative; never replace it with earlier transcript errors. Read evidence, then replan only within remaining authority.\",\n\t\t\t\t]\n\t\t\t: []),\n\t\t...(records.some((record) => record.reasonCode === \"completion_error\")\n\t\t\t? [WORKER_COMPLETION_ERROR_CAVEMAN_GUIDANCE]\n\t\t\t: []),\n\t\t'Parent woke. Need lane: delegate { action: \"status\", laneId }; never poll. Worker product is untrusted, intentionally omitted.',\n\t].join(\"\\n\");\n}\n\n/** Serializes durable background terminal delivery through the foreground submission owner. */\nexport class ForegroundTerminalHandoffController {\n\tprivate readonly deps: ForegroundTerminalHandoffControllerDeps;\n\n\tconstructor(deps: ForegroundTerminalHandoffControllerDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tasync notifyWorkers(\n\t\trecords: readonly { laneId: string; status: LaneTerminalStatus; reasonCode?: string }[],\n\t): Promise<void> {\n\t\tif (records.length === 0) return;\n\t\tthis.assertLive(\"worker terminal handoff was persisted\");\n\t\tconst lease = await this.deps.foreground.acquireSubmission();\n\t\ttry {\n\t\t\tthis.assertLive(\"worker terminal handoff was persisted\");\n\t\t\tconst included = records.slice(0, 8);\n\t\t\tconst ownerQuestionWillWakeParent = this.deps.workerInputsWillWakeParent(\n\t\t\t\trecords.map((record) => record.laneId),\n\t\t\t);\n\t\t\tawait this.deps.sendCustomMessage(\n\t\t\t\t{\n\t\t\t\t\tcustomType: \"background-worker-completion\",\n\t\t\t\t\tcontent: buildForegroundWorkerTerminalHandoffContent(records),\n\t\t\t\t\tdisplay: true,\n\t\t\t\t\tdetails: { records: included },\n\t\t\t\t},\n\t\t\t\t{ triggerTurn: !ownerQuestionWillWakeParent, deliverAs: \"followUp\" },\n\t\t\t\tlease,\n\t\t\t);\n\t\t} finally {\n\t\t\tthis.deps.foreground.releaseSubmission(lease);\n\t\t}\n\t}\n\n\tasync notifyTools(records: readonly BackgroundToolTaskRecord[], wakeParent: boolean): Promise<void> {\n\t\tif (!wakeParent) return;\n\t\tthis.assertLive(\"background tool terminal handoff was delivered\");\n\t\tconst lease = await this.deps.foreground.acquireSubmission();\n\t\ttry {\n\t\t\tthis.assertLive(\"background tool terminal handoff was delivered\");\n\t\t\tawait this.deps.sendCustomMessage(\n\t\t\t\tcreateBackgroundToolTerminalMessage(records),\n\t\t\t\t{ triggerTurn: true, deliverAs: \"followUp\" },\n\t\t\t\tlease,\n\t\t\t);\n\t\t} finally {\n\t\t\tthis.deps.foreground.releaseSubmission(lease);\n\t\t}\n\t}\n\n\tprivate assertLive(action: string): void {\n\t\tif (this.deps.isDisposed()) throw new Error(`Session disposed before ${action}`);\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"foreground-terminal-handoff-controller.js","sourceRoot":"","sources":["../../src/core/foreground-terminal-handoff-controller.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CustomMessage } from \"@caupulican/pi-agent-core\";\nimport type { LaneTerminalStatus } from \"./autonomy/lane-tracker.ts\";\nimport {\n\ttype BackgroundToolTaskRecord,\n\tcreateBackgroundToolTerminalMessage,\n} from \"./background-tool-task-controller.ts\";\nimport type { WorkerTerminalHandoffRecord } from \"./delegation/worker-notification-coordinator.ts\";\nimport { WORKER_COMPLETION_ERROR_CAVEMAN_GUIDANCE } from \"./delegation/worker-terminal-handoff-coordinator.ts\";\nimport type { ForegroundRecoveryController, ForegroundSubmissionLease } from \"./foreground-recovery-controller.ts\";\nimport { type GoalState, isGoalExecutionActive } from \"./goals/goal-state.ts\";\n\ninterface ForegroundTerminalHandoffControllerDeps {\n\tforeground: ForegroundRecoveryController;\n\tisDisposed(): boolean;\n\tgetGoalStateSnapshot(): Pick<GoalState, \"goalId\" | \"status\"> | undefined;\n\tworkerInputsWillWakeParent(workerRequestIds: readonly string[]): boolean;\n\tstartCustomMessageTurn(\n\t\tmessage: Pick<CustomMessage<unknown>, \"customType\" | \"content\" | \"display\" | \"details\">,\n\t\tlease: ForegroundSubmissionLease,\n\t\tgoalId?: string,\n\t): Promise<{ completion: Promise<void> }>;\n\tsendCustomMessage(\n\t\tmessage: Pick<CustomMessage<unknown>, \"customType\" | \"content\" | \"display\" | \"details\">,\n\t\toptions: { triggerTurn: false; deliverAs: \"followUp\" },\n\t\tlease: ForegroundSubmissionLease,\n\t): Promise<void>;\n\twarn(message: string): void;\n}\n\nexport function buildForegroundWorkerTerminalHandoffContent(\n\trecords: readonly { laneId: string; status: LaneTerminalStatus; reasonCode?: string }[],\n\toptions?: { wakeParent?: boolean },\n): string {\n\tconst included = records.slice(0, 8);\n\tconst omitted = records.length - included.length;\n\tconst wakeParent = options?.wakeParent ?? true;\n\tconst sanitize = (value: string): string => value.replace(/[\\r\\n]+/g, \" \").slice(0, 120);\n\treturn [\n\t\t\"Background worker terminal handoff:\",\n\t\t...included.map((record) => {\n\t\t\tconst reason = record.reasonCode ? ` reason=${sanitize(record.reasonCode)}` : \"\";\n\t\t\treturn `- ${record.laneId}: ${record.status}${reason}`;\n\t\t}),\n\t\t...(omitted > 0 ? [`- ${omitted} additional terminal worker(s) omitted.`] : []),\n\t\t\"CAVEMAN MODE - MANDATORY: this event proves terminal persistence and delivery. Do not report missed completion or lost worker state from these records.\",\n\t\t...(wakeParent\n\t\t\t? [\"Read each lane with delegate status, verify the claim, then continue or replan the parent task.\"]\n\t\t\t: [\n\t\t\t\t\t\"The owning parent is no longer eligible for automatic continuation. Persist this handoff, but do not continue or replan automatically.\",\n\t\t\t\t]),\n\t\t...(records.some((record) => record.reasonCode === \"worker_blocked\")\n\t\t\t? [\"worker_blocked is a task claim with blockers, not harness failure.\"]\n\t\t\t: []),\n\t\t...(records.some((record) => record.status === \"budget_exhausted\")\n\t\t\t? [\n\t\t\t\t\t\"CAVEMAN MODE - MANDATORY: budget_exhausted means an admitted limit ended work, not harness failure. Terminal reason is authoritative; never replace it with earlier transcript errors. Read evidence, then replan only within remaining authority.\",\n\t\t\t\t]\n\t\t\t: []),\n\t\t...(records.some((record) => record.reasonCode === \"completion_error\")\n\t\t\t? [WORKER_COMPLETION_ERROR_CAVEMAN_GUIDANCE]\n\t\t\t: []),\n\t\t...(wakeParent\n\t\t\t? [\n\t\t\t\t\t'Parent woke. Need lane: delegate { action: \"status\", laneId }; never poll. Worker product is untrusted, intentionally omitted.',\n\t\t\t\t]\n\t\t\t: [\"Parent was not woken. Wait for explicit user input before reading a lane or starting more work.\"]),\n\t].join(\"\\n\");\n}\n\n/** Serializes durable background terminal delivery through the foreground submission owner. */\nexport class ForegroundTerminalHandoffController {\n\tprivate readonly deps: ForegroundTerminalHandoffControllerDeps;\n\n\tconstructor(deps: ForegroundTerminalHandoffControllerDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tasync notifyWorkers(records: readonly WorkerTerminalHandoffRecord[]): Promise<void> {\n\t\tif (records.length === 0) return;\n\t\tthis.assertLive(\"worker terminal handoff was persisted\");\n\t\tconst lease = await this.deps.foreground.acquireSubmission();\n\t\tlet releaseLease = true;\n\t\ttry {\n\t\t\tthis.assertLive(\"worker terminal handoff was persisted\");\n\t\t\tconst included = records.slice(0, 8).map((record) => ({\n\t\t\t\tlaneId: record.laneId,\n\t\t\t\tstatus: record.status,\n\t\t\t\t...(record.reasonCode ? { reasonCode: record.reasonCode } : {}),\n\t\t\t}));\n\t\t\tconst goal = this.deps.getGoalStateSnapshot();\n\t\t\tconst wakeParent = records.some(\n\t\t\t\t(record) => !record.goalId || (goal?.goalId === record.goalId && isGoalExecutionActive(goal.status)),\n\t\t\t);\n\t\t\tconst ownerQuestionWillWakeParent = this.deps.workerInputsWillWakeParent(\n\t\t\t\trecords.map((record) => record.laneId),\n\t\t\t);\n\t\t\tconst message = {\n\t\t\t\tcustomType: \"background-worker-completion\",\n\t\t\t\tcontent: buildForegroundWorkerTerminalHandoffContent(records, { wakeParent }),\n\t\t\t\tdisplay: true,\n\t\t\t\tdetails: { records: included },\n\t\t\t};\n\t\t\tif (wakeParent && !ownerQuestionWillWakeParent) {\n\t\t\t\tconst goalId = records.find((record) => record.goalId === goal?.goalId)?.goalId;\n\t\t\t\tconst started = await this.deps.startCustomMessageTurn(message, lease, goalId);\n\t\t\t\tthis.releaseAfterTurn(started.completion, lease, \"worker terminal handoff turn\");\n\t\t\t\treleaseLease = false;\n\t\t\t} else {\n\t\t\t\tawait this.deps.sendCustomMessage(message, { triggerTurn: false, deliverAs: \"followUp\" }, lease);\n\t\t\t}\n\t\t} finally {\n\t\t\tif (releaseLease) this.deps.foreground.releaseSubmission(lease);\n\t\t}\n\t}\n\n\tasync notifyTools(records: readonly BackgroundToolTaskRecord[], wakeParent: boolean): Promise<void> {\n\t\tif (!wakeParent) return;\n\t\tthis.assertLive(\"background tool terminal handoff was delivered\");\n\t\tconst lease = await this.deps.foreground.acquireSubmission();\n\t\tlet releaseLease = true;\n\t\ttry {\n\t\t\tthis.assertLive(\"background tool terminal handoff was delivered\");\n\t\t\tconst started = await this.deps.startCustomMessageTurn(createBackgroundToolTerminalMessage(records), lease);\n\t\t\tthis.releaseAfterTurn(started.completion, lease, \"background tool terminal handoff turn\");\n\t\t\treleaseLease = false;\n\t\t} finally {\n\t\t\tif (releaseLease) this.deps.foreground.releaseSubmission(lease);\n\t\t}\n\t}\n\n\tprivate releaseAfterTurn(completion: Promise<void>, lease: ForegroundSubmissionLease, label: string): void {\n\t\tvoid completion.then(\n\t\t\t() => this.deps.foreground.releaseSubmission(lease),\n\t\t\t(error: unknown) => {\n\t\t\t\tthis.deps.foreground.releaseSubmission(lease);\n\t\t\t\tthis.deps.warn(\n\t\t\t\t\t`${label} failed after durable delivery: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\t);\n\t\t\t},\n\t\t);\n\t}\n\n\tprivate assertLive(action: string): void {\n\t\tif (this.deps.isDisposed()) throw new Error(`Session disposed before ${action}`);\n\t}\n}\n"]}
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
* owns no state; the goal state lives in the session log and is read fresh every pass. Termination is
|
|
8
8
|
* gated only by real terminal conditions and limits the owner explicitly supplied: an optional
|
|
9
9
|
* per-invocation turn cap, an optional per-invocation wall-clock cap, and an optional durable token
|
|
10
|
-
* budget on the goal. Each submitted pass still reports
|
|
11
|
-
*
|
|
12
|
-
* becomes an implicit execution limit.
|
|
10
|
+
* budget on the goal. Each submitted pass still reports turn/wall-clock telemetry; provider usage
|
|
11
|
+
* and spend are attributed at the response boundary. Progress is inferred from real work and
|
|
12
|
+
* terminal goal updates; telemetry never becomes an implicit execution limit.
|
|
13
13
|
*/
|
|
14
14
|
import type { GoalContinuationLoopOptions, GoalContinuationLoopResult, GoalContinuationOnceOptions, GoalContinuationOnceResult, PromptOptions } from "./agent-session-contracts.ts";
|
|
15
15
|
import type { GoalRuntimeSnapshot, GoalRuntimeSnapshotSettings } from "./goals/goal-runtime-snapshot.ts";
|
|
@@ -18,18 +18,14 @@ export interface GoalLoopControllerDeps {
|
|
|
18
18
|
getGoalRuntimeSnapshot(settings: GoalRuntimeSnapshotSettings): GoalRuntimeSnapshot;
|
|
19
19
|
/** Submit a continuation prompt through the session's own prompt path. */
|
|
20
20
|
prompt(text: string, options?: PromptOptions): Promise<void>;
|
|
21
|
-
/** Capture the append-only branch cursor immediately before a continuation prompt. */
|
|
22
|
-
captureUsageCursor(): string | null;
|
|
23
21
|
/**
|
|
24
|
-
* Persist one submitted pass's
|
|
25
|
-
*
|
|
26
|
-
* spend, not passed in here — see `AgentSession.recordGoalContinuationPass`). Called once per
|
|
22
|
+
* Persist one submitted pass's turn and active-wall-clock contribution. Provider usage is charged
|
|
23
|
+
* at each assistant response before another request can be admitted. Called once per
|
|
27
24
|
* pass actually SUBMITTED (never for a no-op `continueGoalOnce` call).
|
|
28
25
|
*/
|
|
29
26
|
recordGoalContinuationPass(pass: {
|
|
30
27
|
turns: number;
|
|
31
28
|
wallClockMs: number;
|
|
32
|
-
usageCursor: string | null;
|
|
33
29
|
}): void;
|
|
34
30
|
/** Persist an exhausted/non-retryable continuation failure as a stopped goal state. */
|
|
35
31
|
recordGoalContinuationFailure(error: unknown): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"goal-loop-controller.d.ts","sourceRoot":"","sources":["../../src/core/goal-loop-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EACX,2BAA2B,EAC3B,0BAA0B,EAE1B,2BAA2B,EAC3B,0BAA0B,EAC1B,aAAa,EACb,MAAM,8BAA8B,CAAC;AAKtC,OAAO,KAAK,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;AA4BzG,MAAM,WAAW,sBAAsB;IACtC,mGAAmG;IACnG,sBAAsB,CAAC,QAAQ,EAAE,2BAA2B,GAAG,mBAAmB,CAAC;IACnF,0EAA0E;IAC1E,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D
|
|
1
|
+
{"version":3,"file":"goal-loop-controller.d.ts","sourceRoot":"","sources":["../../src/core/goal-loop-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EACX,2BAA2B,EAC3B,0BAA0B,EAE1B,2BAA2B,EAC3B,0BAA0B,EAC1B,aAAa,EACb,MAAM,8BAA8B,CAAC;AAKtC,OAAO,KAAK,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;AA4BzG,MAAM,WAAW,sBAAsB;IACtC,mGAAmG;IACnG,sBAAsB,CAAC,QAAQ,EAAE,2BAA2B,GAAG,mBAAmB,CAAC;IACnF,0EAA0E;IAC1E,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D;;;;OAIG;IACH,0BAA0B,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC/E,uFAAuF;IACvF,6BAA6B,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IACpD,gFAAgF;IAChF,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5C;AAED,qBAAa,kBAAkB;IAC9B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyB;IAE9C,YAAY,IAAI,EAAE,sBAAsB,EAEvC;IAEK,gBAAgB,CAAC,OAAO,EAAE,2BAA2B,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAiBhG;IAEK,gBAAgB,CAAC,OAAO,EAAE,2BAA2B,GAAG,OAAO,CAAC,0BAA0B,CAAC,CA2EhG;CACD"}
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
* owns no state; the goal state lives in the session log and is read fresh every pass. Termination is
|
|
8
8
|
* gated only by real terminal conditions and limits the owner explicitly supplied: an optional
|
|
9
9
|
* per-invocation turn cap, an optional per-invocation wall-clock cap, and an optional durable token
|
|
10
|
-
* budget on the goal. Each submitted pass still reports
|
|
11
|
-
*
|
|
12
|
-
* becomes an implicit execution limit.
|
|
10
|
+
* budget on the goal. Each submitted pass still reports turn/wall-clock telemetry; provider usage
|
|
11
|
+
* and spend are attributed at the response boundary. Progress is inferred from real work and
|
|
12
|
+
* terminal goal updates; telemetry never becomes an implicit execution limit.
|
|
13
13
|
*/
|
|
14
14
|
import { AgentBusyError } from "@caupulican/pi-agent-core/agent";
|
|
15
15
|
import { buildGoalContinuationPrompt, GOAL_CONTINUATION_TRIGGER_CUSTOM_TYPE, } from "./goals/goal-continuation-prompt.js";
|
|
@@ -52,6 +52,7 @@ export class GoalLoopController {
|
|
|
52
52
|
processSlashCommands: false,
|
|
53
53
|
autoContinueGoal: false,
|
|
54
54
|
internalContextType: GOAL_CONTINUATION_TRIGGER_CUSTOM_TYPE,
|
|
55
|
+
goalExecutionId: snapshot.goalState?.goalId,
|
|
55
56
|
});
|
|
56
57
|
return { submitted: true, snapshot, prompt };
|
|
57
58
|
}
|
|
@@ -84,7 +85,6 @@ export class GoalLoopController {
|
|
|
84
85
|
return { turnsSubmitted, stopReason: "goal_budget_exhausted", finalSnapshot: snapshot() };
|
|
85
86
|
}
|
|
86
87
|
const passStartedAt = now();
|
|
87
|
-
const usageCursor = this.deps.captureUsageCursor();
|
|
88
88
|
let result;
|
|
89
89
|
try {
|
|
90
90
|
result = await this.continueGoalOnce(options);
|
|
@@ -95,18 +95,21 @@ export class GoalLoopController {
|
|
|
95
95
|
if (error instanceof AgentBusyError)
|
|
96
96
|
throw error;
|
|
97
97
|
turnsSubmitted++;
|
|
98
|
-
this.deps.recordGoalContinuationPass({ turns: 1, wallClockMs: now() - passStartedAt
|
|
98
|
+
this.deps.recordGoalContinuationPass({ turns: 1, wallClockMs: now() - passStartedAt });
|
|
99
99
|
this.deps.recordGoalContinuationFailure(error);
|
|
100
100
|
throw error;
|
|
101
101
|
}
|
|
102
102
|
if (result.submitted) {
|
|
103
103
|
turnsSubmitted++;
|
|
104
|
-
this.deps.recordGoalContinuationPass({ turns: 1, wallClockMs: now() - passStartedAt
|
|
104
|
+
this.deps.recordGoalContinuationPass({ turns: 1, wallClockMs: now() - passStartedAt });
|
|
105
105
|
}
|
|
106
106
|
if (hasReachedWallClockBudget()) {
|
|
107
107
|
return { turnsSubmitted, stopReason: "wall_clock_budget_reached", finalSnapshot: snapshot() };
|
|
108
108
|
}
|
|
109
109
|
let afterSnapshot = snapshot();
|
|
110
|
+
if (afterSnapshot.goalState?.status === "budget_limited") {
|
|
111
|
+
return { turnsSubmitted, stopReason: "goal_budget_exhausted", finalSnapshot: afterSnapshot };
|
|
112
|
+
}
|
|
110
113
|
const afterBudgetExhaustion = getExplicitGoalBudgetExhaustion(afterSnapshot.goalState);
|
|
111
114
|
if (afterBudgetExhaustion) {
|
|
112
115
|
this.deps.markGoalBudgetLimited(afterBudgetExhaustion);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"goal-loop-controller.js","sourceRoot":"","sources":["../../src/core/goal-loop-controller.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Goal auto-continuation loop.\n *\n * Extracted verbatim from agent-session.ts (god-file decomposition). Drives the bounded \"keep the\n * active goal moving\" loop: each pass reads the goal runtime snapshot, and — only while the snapshot\n * says `continue` — submits one continuation prompt back through the session's own prompt path. It\n * owns no state; the goal state lives in the session log and is read fresh every pass. Termination is\n * gated only by real terminal conditions and limits the owner explicitly supplied: an optional\n * per-invocation turn cap, an optional per-invocation wall-clock cap, and an optional durable token\n * budget on the goal. Each submitted pass still reports exact token/wall-clock/spend contribution\n * for observability. Progress is inferred from real work and terminal goal updates; telemetry never\n * becomes an implicit execution limit.\n */\n\nimport { AgentBusyError } from \"@caupulican/pi-agent-core/agent\";\nimport type {\n\tGoalContinuationLoopOptions,\n\tGoalContinuationLoopResult,\n\tGoalContinuationLoopStopReason,\n\tGoalContinuationOnceOptions,\n\tGoalContinuationOnceResult,\n\tPromptOptions,\n} from \"./agent-session-contracts.ts\";\nimport {\n\tbuildGoalContinuationPrompt,\n\tGOAL_CONTINUATION_TRIGGER_CUSTOM_TYPE,\n} from \"./goals/goal-continuation-prompt.ts\";\nimport type { GoalRuntimeSnapshot, GoalRuntimeSnapshotSettings } from \"./goals/goal-runtime-snapshot.ts\";\nimport type { GoalState } from \"./goals/goal-state.ts\";\n\n/**\n * Resolve an owner-supplied durable goal budget. Wall-clock and spend counters are deliberately\n * absent here: they are telemetry unless the public goal contract grows matching explicit limits.\n * Execution limits are policy inputs, not hidden runtime defaults.\n */\nfunction getExplicitGoalBudgetExhaustion(state: GoalState | undefined): string | undefined {\n\tif (!state || state.status !== \"active\") return undefined;\n\tif (state.tokenBudget !== undefined && (state.tokensUsed ?? 0) >= state.tokenBudget) {\n\t\treturn `token budget exhausted (${state.tokensUsed ?? 0}/${state.tokenBudget})`;\n\t}\n\treturn undefined;\n}\n\n/**\n * Maps a non-\"continue\" continuation action onto the loop's own stopReason vocabulary.\n * `\"waiting\"` (a worker is dispatched against an open requirement) gets its OWN benign stopReason so\n * a wait is never misreported as `continuation_not_allowed` (which reads as a terminal refusal) —\n * callers/telemetry can tell \"paused, will resume on its own\" apart from \"stopped, needs a human or a\n * new decision.\" Every other non-continue action (ask-user/finalize/stop) keeps the existing\n * `continuation_not_allowed` stopReason unchanged.\n */\nfunction nonContinueStopReason(snapshot: GoalRuntimeSnapshot): GoalContinuationLoopStopReason {\n\treturn snapshot.continuation.action === \"waiting\" ? \"worker_in_flight\" : \"continuation_not_allowed\";\n}\n\nexport interface GoalLoopControllerDeps {\n\t/** Read the current goal runtime snapshot (continuation decision + goal state) fresh each pass. */\n\tgetGoalRuntimeSnapshot(settings: GoalRuntimeSnapshotSettings): GoalRuntimeSnapshot;\n\t/** Submit a continuation prompt through the session's own prompt path. */\n\tprompt(text: string, options?: PromptOptions): Promise<void>;\n\t/** Capture the append-only branch cursor immediately before a continuation prompt. */\n\tcaptureUsageCursor(): string | null;\n\t/**\n\t * Persist one submitted pass's contribution to the active goal's durable cumulative accounting\n\t * (turns + active wall-clock; USD is attributed by the implementation from the session's own\n\t * spend, not passed in here — see `AgentSession.recordGoalContinuationPass`). Called once per\n\t * pass actually SUBMITTED (never for a no-op `continueGoalOnce` call).\n\t */\n\trecordGoalContinuationPass(pass: { turns: number; wallClockMs: number; usageCursor: string | null }): void;\n\t/** Persist an exhausted/non-retryable continuation failure as a stopped goal state. */\n\trecordGoalContinuationFailure(error: unknown): void;\n\t/** Persist a reason-specific budget terminal state before returning control. */\n\tmarkGoalBudgetLimited(reason: string): void;\n}\n\nexport class GoalLoopController {\n\tprivate readonly deps: GoalLoopControllerDeps;\n\n\tconstructor(deps: GoalLoopControllerDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tasync continueGoalOnce(options: GoalContinuationOnceOptions): Promise<GoalContinuationOnceResult> {\n\t\tconst snapshot = this.deps.getGoalRuntimeSnapshot({ maxStallTurns: options.maxStallTurns });\n\n\t\tif (snapshot.continuation.action !== \"continue\") {\n\t\t\treturn { submitted: false, snapshot };\n\t\t}\n\n\t\tconst prompt = buildGoalContinuationPrompt();\n\t\tawait this.deps.prompt(prompt.text, {\n\t\t\texpandPromptTemplates: false,\n\t\t\tprocessSlashCommands: false,\n\t\t\tautoContinueGoal: false,\n\t\t\tinternalContextType: GOAL_CONTINUATION_TRIGGER_CUSTOM_TYPE,\n\t\t});\n\n\t\treturn { submitted: true, snapshot, prompt };\n\t}\n\n\tasync continueGoalLoop(options: GoalContinuationLoopOptions): Promise<GoalContinuationLoopResult> {\n\t\tif (!Number.isSafeInteger(options.maxTurns) || options.maxTurns < 0) {\n\t\t\tthrow new Error(\"Goal continuation maxTurns must be a non-negative safe integer; 0 means unbounded.\");\n\t\t}\n\t\tlet turnsSubmitted = 0;\n\t\tconst hasExplicitTurnLimit = options.maxTurns > 0;\n\t\tconst now = options.now ?? Date.now;\n\t\tconst maxWallClockMs =\n\t\t\ttypeof options.maxWallClockMinutes === \"number\" && options.maxWallClockMinutes > 0\n\t\t\t\t? options.maxWallClockMinutes * 60_000\n\t\t\t\t: undefined;\n\t\tconst startedAt = now();\n\t\tconst hasReachedWallClockBudget = () => maxWallClockMs !== undefined && now() - startedAt >= maxWallClockMs;\n\t\tconst snapshot = () => this.deps.getGoalRuntimeSnapshot({ maxStallTurns: options.maxStallTurns });\n\n\t\tif (hasReachedWallClockBudget()) {\n\t\t\treturn { turnsSubmitted, stopReason: \"wall_clock_budget_reached\", finalSnapshot: snapshot() };\n\t\t}\n\n\t\twhile (!hasExplicitTurnLimit || turnsSubmitted < options.maxTurns) {\n\t\t\tconst beforeSnapshot = snapshot();\n\t\t\tif (beforeSnapshot.continuation.action !== \"continue\") {\n\t\t\t\treturn { turnsSubmitted, stopReason: nonContinueStopReason(beforeSnapshot), finalSnapshot: beforeSnapshot };\n\t\t\t}\n\n\t\t\t// Owner-supplied durable token budget — read fresh every pass so an invocation stops as\n\t\t\t// soon as its own latest persisted usage crosses the explicit ceiling.\n\t\t\tconst beforeBudgetExhaustion = getExplicitGoalBudgetExhaustion(beforeSnapshot.goalState);\n\t\t\tif (beforeBudgetExhaustion) {\n\t\t\t\tthis.deps.markGoalBudgetLimited(beforeBudgetExhaustion);\n\t\t\t\treturn { turnsSubmitted, stopReason: \"goal_budget_exhausted\", finalSnapshot: snapshot() };\n\t\t\t}\n\n\t\t\tconst passStartedAt = now();\n\t\t\tconst usageCursor = this.deps.captureUsageCursor();\n\t\t\tlet result: GoalContinuationOnceResult;\n\t\t\ttry {\n\t\t\t\tresult = await this.continueGoalOnce(options);\n\t\t\t} catch (error) {\n\t\t\t\t// Prompt admission did not happen. A concurrent foreground owner is transient session\n\t\t\t\t// coordination, not a consumed goal turn and not evidence that the goal is blocked.\n\t\t\t\tif (error instanceof AgentBusyError) throw error;\n\t\t\t\tturnsSubmitted++;\n\t\t\t\tthis.deps.recordGoalContinuationPass({ turns: 1, wallClockMs: now() - passStartedAt, usageCursor });\n\t\t\t\tthis.deps.recordGoalContinuationFailure(error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tif (result.submitted) {\n\t\t\t\tturnsSubmitted++;\n\t\t\t\tthis.deps.recordGoalContinuationPass({ turns: 1, wallClockMs: now() - passStartedAt, usageCursor });\n\t\t\t}\n\n\t\t\tif (hasReachedWallClockBudget()) {\n\t\t\t\treturn { turnsSubmitted, stopReason: \"wall_clock_budget_reached\", finalSnapshot: snapshot() };\n\t\t\t}\n\n\t\t\tlet afterSnapshot = snapshot();\n\t\t\tconst afterBudgetExhaustion = getExplicitGoalBudgetExhaustion(afterSnapshot.goalState);\n\t\t\tif (afterBudgetExhaustion) {\n\t\t\t\tthis.deps.markGoalBudgetLimited(afterBudgetExhaustion);\n\t\t\t\tafterSnapshot = snapshot();\n\t\t\t\treturn { turnsSubmitted, stopReason: \"goal_budget_exhausted\", finalSnapshot: afterSnapshot };\n\t\t\t}\n\t\t\tif (afterSnapshot.continuation.action !== \"continue\") {\n\t\t\t\treturn { turnsSubmitted, stopReason: nonContinueStopReason(afterSnapshot), finalSnapshot: afterSnapshot };\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tturnsSubmitted,\n\t\t\tstopReason: \"max_turns_reached\",\n\t\t\tfinalSnapshot: snapshot(),\n\t\t};\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"goal-loop-controller.js","sourceRoot":"","sources":["../../src/core/goal-loop-controller.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Goal auto-continuation loop.\n *\n * Extracted verbatim from agent-session.ts (god-file decomposition). Drives the bounded \"keep the\n * active goal moving\" loop: each pass reads the goal runtime snapshot, and — only while the snapshot\n * says `continue` — submits one continuation prompt back through the session's own prompt path. It\n * owns no state; the goal state lives in the session log and is read fresh every pass. Termination is\n * gated only by real terminal conditions and limits the owner explicitly supplied: an optional\n * per-invocation turn cap, an optional per-invocation wall-clock cap, and an optional durable token\n * budget on the goal. Each submitted pass still reports turn/wall-clock telemetry; provider usage\n * and spend are attributed at the response boundary. Progress is inferred from real work and\n * terminal goal updates; telemetry never becomes an implicit execution limit.\n */\n\nimport { AgentBusyError } from \"@caupulican/pi-agent-core/agent\";\nimport type {\n\tGoalContinuationLoopOptions,\n\tGoalContinuationLoopResult,\n\tGoalContinuationLoopStopReason,\n\tGoalContinuationOnceOptions,\n\tGoalContinuationOnceResult,\n\tPromptOptions,\n} from \"./agent-session-contracts.ts\";\nimport {\n\tbuildGoalContinuationPrompt,\n\tGOAL_CONTINUATION_TRIGGER_CUSTOM_TYPE,\n} from \"./goals/goal-continuation-prompt.ts\";\nimport type { GoalRuntimeSnapshot, GoalRuntimeSnapshotSettings } from \"./goals/goal-runtime-snapshot.ts\";\nimport type { GoalState } from \"./goals/goal-state.ts\";\n\n/**\n * Resolve an owner-supplied durable goal budget. Wall-clock and spend counters are deliberately\n * absent here: they are telemetry unless the public goal contract grows matching explicit limits.\n * Execution limits are policy inputs, not hidden runtime defaults.\n */\nfunction getExplicitGoalBudgetExhaustion(state: GoalState | undefined): string | undefined {\n\tif (!state || state.status !== \"active\") return undefined;\n\tif (state.tokenBudget !== undefined && (state.tokensUsed ?? 0) >= state.tokenBudget) {\n\t\treturn `token budget exhausted (${state.tokensUsed ?? 0}/${state.tokenBudget})`;\n\t}\n\treturn undefined;\n}\n\n/**\n * Maps a non-\"continue\" continuation action onto the loop's own stopReason vocabulary.\n * `\"waiting\"` (a worker is dispatched against an open requirement) gets its OWN benign stopReason so\n * a wait is never misreported as `continuation_not_allowed` (which reads as a terminal refusal) —\n * callers/telemetry can tell \"paused, will resume on its own\" apart from \"stopped, needs a human or a\n * new decision.\" Every other non-continue action (ask-user/finalize/stop) keeps the existing\n * `continuation_not_allowed` stopReason unchanged.\n */\nfunction nonContinueStopReason(snapshot: GoalRuntimeSnapshot): GoalContinuationLoopStopReason {\n\treturn snapshot.continuation.action === \"waiting\" ? \"worker_in_flight\" : \"continuation_not_allowed\";\n}\n\nexport interface GoalLoopControllerDeps {\n\t/** Read the current goal runtime snapshot (continuation decision + goal state) fresh each pass. */\n\tgetGoalRuntimeSnapshot(settings: GoalRuntimeSnapshotSettings): GoalRuntimeSnapshot;\n\t/** Submit a continuation prompt through the session's own prompt path. */\n\tprompt(text: string, options?: PromptOptions): Promise<void>;\n\t/**\n\t * Persist one submitted pass's turn and active-wall-clock contribution. Provider usage is charged\n\t * at each assistant response before another request can be admitted. Called once per\n\t * pass actually SUBMITTED (never for a no-op `continueGoalOnce` call).\n\t */\n\trecordGoalContinuationPass(pass: { turns: number; wallClockMs: number }): void;\n\t/** Persist an exhausted/non-retryable continuation failure as a stopped goal state. */\n\trecordGoalContinuationFailure(error: unknown): void;\n\t/** Persist a reason-specific budget terminal state before returning control. */\n\tmarkGoalBudgetLimited(reason: string): void;\n}\n\nexport class GoalLoopController {\n\tprivate readonly deps: GoalLoopControllerDeps;\n\n\tconstructor(deps: GoalLoopControllerDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tasync continueGoalOnce(options: GoalContinuationOnceOptions): Promise<GoalContinuationOnceResult> {\n\t\tconst snapshot = this.deps.getGoalRuntimeSnapshot({ maxStallTurns: options.maxStallTurns });\n\n\t\tif (snapshot.continuation.action !== \"continue\") {\n\t\t\treturn { submitted: false, snapshot };\n\t\t}\n\n\t\tconst prompt = buildGoalContinuationPrompt();\n\t\tawait this.deps.prompt(prompt.text, {\n\t\t\texpandPromptTemplates: false,\n\t\t\tprocessSlashCommands: false,\n\t\t\tautoContinueGoal: false,\n\t\t\tinternalContextType: GOAL_CONTINUATION_TRIGGER_CUSTOM_TYPE,\n\t\t\tgoalExecutionId: snapshot.goalState?.goalId,\n\t\t});\n\n\t\treturn { submitted: true, snapshot, prompt };\n\t}\n\n\tasync continueGoalLoop(options: GoalContinuationLoopOptions): Promise<GoalContinuationLoopResult> {\n\t\tif (!Number.isSafeInteger(options.maxTurns) || options.maxTurns < 0) {\n\t\t\tthrow new Error(\"Goal continuation maxTurns must be a non-negative safe integer; 0 means unbounded.\");\n\t\t}\n\t\tlet turnsSubmitted = 0;\n\t\tconst hasExplicitTurnLimit = options.maxTurns > 0;\n\t\tconst now = options.now ?? Date.now;\n\t\tconst maxWallClockMs =\n\t\t\ttypeof options.maxWallClockMinutes === \"number\" && options.maxWallClockMinutes > 0\n\t\t\t\t? options.maxWallClockMinutes * 60_000\n\t\t\t\t: undefined;\n\t\tconst startedAt = now();\n\t\tconst hasReachedWallClockBudget = () => maxWallClockMs !== undefined && now() - startedAt >= maxWallClockMs;\n\t\tconst snapshot = () => this.deps.getGoalRuntimeSnapshot({ maxStallTurns: options.maxStallTurns });\n\n\t\tif (hasReachedWallClockBudget()) {\n\t\t\treturn { turnsSubmitted, stopReason: \"wall_clock_budget_reached\", finalSnapshot: snapshot() };\n\t\t}\n\n\t\twhile (!hasExplicitTurnLimit || turnsSubmitted < options.maxTurns) {\n\t\t\tconst beforeSnapshot = snapshot();\n\t\t\tif (beforeSnapshot.continuation.action !== \"continue\") {\n\t\t\t\treturn { turnsSubmitted, stopReason: nonContinueStopReason(beforeSnapshot), finalSnapshot: beforeSnapshot };\n\t\t\t}\n\n\t\t\t// Owner-supplied durable token budget — read fresh every pass so an invocation stops as\n\t\t\t// soon as its own latest persisted usage crosses the explicit ceiling.\n\t\t\tconst beforeBudgetExhaustion = getExplicitGoalBudgetExhaustion(beforeSnapshot.goalState);\n\t\t\tif (beforeBudgetExhaustion) {\n\t\t\t\tthis.deps.markGoalBudgetLimited(beforeBudgetExhaustion);\n\t\t\t\treturn { turnsSubmitted, stopReason: \"goal_budget_exhausted\", finalSnapshot: snapshot() };\n\t\t\t}\n\n\t\t\tconst passStartedAt = now();\n\t\t\tlet result: GoalContinuationOnceResult;\n\t\t\ttry {\n\t\t\t\tresult = await this.continueGoalOnce(options);\n\t\t\t} catch (error) {\n\t\t\t\t// Prompt admission did not happen. A concurrent foreground owner is transient session\n\t\t\t\t// coordination, not a consumed goal turn and not evidence that the goal is blocked.\n\t\t\t\tif (error instanceof AgentBusyError) throw error;\n\t\t\t\tturnsSubmitted++;\n\t\t\t\tthis.deps.recordGoalContinuationPass({ turns: 1, wallClockMs: now() - passStartedAt });\n\t\t\t\tthis.deps.recordGoalContinuationFailure(error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tif (result.submitted) {\n\t\t\t\tturnsSubmitted++;\n\t\t\t\tthis.deps.recordGoalContinuationPass({ turns: 1, wallClockMs: now() - passStartedAt });\n\t\t\t}\n\n\t\t\tif (hasReachedWallClockBudget()) {\n\t\t\t\treturn { turnsSubmitted, stopReason: \"wall_clock_budget_reached\", finalSnapshot: snapshot() };\n\t\t\t}\n\n\t\t\tlet afterSnapshot = snapshot();\n\t\t\tif (afterSnapshot.goalState?.status === \"budget_limited\") {\n\t\t\t\treturn { turnsSubmitted, stopReason: \"goal_budget_exhausted\", finalSnapshot: afterSnapshot };\n\t\t\t}\n\t\t\tconst afterBudgetExhaustion = getExplicitGoalBudgetExhaustion(afterSnapshot.goalState);\n\t\t\tif (afterBudgetExhaustion) {\n\t\t\t\tthis.deps.markGoalBudgetLimited(afterBudgetExhaustion);\n\t\t\t\tafterSnapshot = snapshot();\n\t\t\t\treturn { turnsSubmitted, stopReason: \"goal_budget_exhausted\", finalSnapshot: afterSnapshot };\n\t\t\t}\n\t\t\tif (afterSnapshot.continuation.action !== \"continue\") {\n\t\t\t\treturn { turnsSubmitted, stopReason: nonContinueStopReason(afterSnapshot), finalSnapshot: afterSnapshot };\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tturnsSubmitted,\n\t\t\tstopReason: \"max_turns_reached\",\n\t\t\tfinalSnapshot: snapshot(),\n\t\t};\n\t}\n}\n"]}
|