@scotthuang/agent-knock-knock 0.12.11 → 0.12.13
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 +40 -0
- package/README.md +43 -30
- package/dist/src/approval-policy.d.ts +11 -2
- package/dist/src/approval-policy.js +33 -7
- package/dist/src/approval-policy.js.map +1 -1
- package/dist/src/callback-cli-adapter.d.ts +47 -0
- package/dist/src/callback-cli-adapter.js +156 -0
- package/dist/src/callback-cli-adapter.js.map +1 -0
- package/dist/src/callback-outbox-policy.d.ts +95 -0
- package/dist/src/callback-outbox-policy.js +149 -0
- package/dist/src/callback-outbox-policy.js.map +1 -0
- package/dist/src/callback-outbox-service.d.ts +247 -0
- package/dist/src/callback-outbox-service.js +1053 -0
- package/dist/src/callback-outbox-service.js.map +1 -0
- package/dist/src/callback-outbox-settlement.d.ts +54 -0
- package/dist/src/callback-outbox-settlement.js +241 -0
- package/dist/src/callback-outbox-settlement.js.map +1 -0
- package/dist/src/canonical-json.d.ts +1 -0
- package/dist/src/canonical-json.js +14 -0
- package/dist/src/canonical-json.js.map +1 -0
- package/dist/src/claude-lifecycle-compatibility.js +15 -0
- package/dist/src/claude-lifecycle-compatibility.js.map +1 -1
- package/dist/src/claude-local-transcript-provider.d.ts +82 -1
- package/dist/src/claude-local-transcript-provider.js +873 -23
- package/dist/src/claude-local-transcript-provider.js.map +1 -1
- package/dist/src/cli-command-runtime.d.ts +14 -0
- package/dist/src/cli-command-runtime.js +164 -0
- package/dist/src/cli-command-runtime.js.map +1 -0
- package/dist/src/cli-core.d.ts +4 -42
- package/dist/src/cli-core.js +958 -28684
- package/dist/src/cli-core.js.map +1 -1
- package/dist/src/cli-runtime-context.d.ts +57 -0
- package/dist/src/cli-runtime-context.js +118 -0
- package/dist/src/cli-runtime-context.js.map +1 -0
- package/dist/src/codex-lifecycle-compatibility.js +2 -1
- package/dist/src/codex-lifecycle-compatibility.js.map +1 -1
- package/dist/src/codex-local-session-provider.d.ts +18 -0
- package/dist/src/codex-local-session-provider.js +67 -0
- package/dist/src/codex-local-session-provider.js.map +1 -1
- package/dist/src/codex-session-provider.js +1 -3
- package/dist/src/codex-session-provider.js.map +1 -1
- package/dist/src/conversation-trace.d.ts +30 -0
- package/dist/src/conversation-trace.js +185 -0
- package/dist/src/conversation-trace.js.map +1 -0
- package/dist/src/deferred-foreground-application-service.d.ts +84 -0
- package/dist/src/deferred-foreground-application-service.js +742 -0
- package/dist/src/deferred-foreground-application-service.js.map +1 -0
- package/dist/src/deferred-foreground-authority-cli-adapter.d.ts +78 -0
- package/dist/src/deferred-foreground-authority-cli-adapter.js +795 -0
- package/dist/src/deferred-foreground-authority-cli-adapter.js.map +1 -0
- package/dist/src/deferred-foreground-boundary.d.ts +61 -0
- package/dist/src/deferred-foreground-boundary.js +2 -0
- package/dist/src/deferred-foreground-boundary.js.map +1 -0
- package/dist/src/deferred-foreground-capability.d.ts +4 -0
- package/dist/src/deferred-foreground-capability.js +184 -0
- package/dist/src/deferred-foreground-capability.js.map +1 -0
- package/dist/src/deferred-foreground-preparation-cli-adapter.d.ts +53 -0
- package/dist/src/deferred-foreground-preparation-cli-adapter.js +210 -0
- package/dist/src/deferred-foreground-preparation-cli-adapter.js.map +1 -0
- package/dist/src/deferred-foreground-preparation-service.d.ts +72 -0
- package/dist/src/deferred-foreground-preparation-service.js +179 -0
- package/dist/src/deferred-foreground-preparation-service.js.map +1 -0
- package/dist/src/deferred-foreground-recovery-cli-adapter.d.ts +139 -0
- package/dist/src/deferred-foreground-recovery-cli-adapter.js +1125 -0
- package/dist/src/deferred-foreground-recovery-cli-adapter.js.map +1 -0
- package/dist/src/deferred-foreground-recovery-service.d.ts +48 -0
- package/dist/src/deferred-foreground-recovery-service.js +91 -0
- package/dist/src/deferred-foreground-recovery-service.js.map +1 -0
- package/dist/src/deferred-foreground-transfer-policy.d.ts +2 -0
- package/dist/src/deferred-foreground-transfer-policy.js +10 -0
- package/dist/src/deferred-foreground-transfer-policy.js.map +1 -0
- package/dist/src/deferred-foreground-transfer.js +46 -105
- package/dist/src/deferred-foreground-transfer.js.map +1 -1
- package/dist/src/doctor-capabilities.d.ts +9 -0
- package/dist/src/doctor-capabilities.js +22 -0
- package/dist/src/doctor-capabilities.js.map +1 -1
- package/dist/src/durable-json-file.d.ts +22 -0
- package/dist/src/durable-json-file.js +127 -0
- package/dist/src/durable-json-file.js.map +1 -0
- package/dist/src/file-lock-cli-adapter.d.ts +43 -0
- package/dist/src/file-lock-cli-adapter.js +150 -0
- package/dist/src/file-lock-cli-adapter.js.map +1 -0
- package/dist/src/herdr-terminal-control-provider.js +1 -3
- package/dist/src/herdr-terminal-control-provider.js.map +1 -1
- package/dist/src/install-doctor-command-adapter.d.ts +17 -0
- package/dist/src/install-doctor-command-adapter.js +225 -0
- package/dist/src/install-doctor-command-adapter.js.map +1 -0
- package/dist/src/live-lifecycle-evidence.js +76 -46
- package/dist/src/live-lifecycle-evidence.js.map +1 -1
- package/dist/src/live-lifecycle-smoke.js +74 -50
- package/dist/src/live-lifecycle-smoke.js.map +1 -1
- package/dist/src/managed-session.d.ts +2 -0
- package/dist/src/managed-session.js +24 -0
- package/dist/src/managed-session.js.map +1 -1
- package/dist/src/managed-turn-recovery-service.d.ts +86 -0
- package/dist/src/managed-turn-recovery-service.js +145 -0
- package/dist/src/managed-turn-recovery-service.js.map +1 -0
- package/dist/src/mutation-transaction.d.ts +48 -0
- package/dist/src/mutation-transaction.js +73 -0
- package/dist/src/mutation-transaction.js.map +1 -0
- package/dist/src/native-thread-lifecycle-cli-adapter.d.ts +154 -0
- package/dist/src/native-thread-lifecycle-cli-adapter.js +786 -0
- package/dist/src/native-thread-lifecycle-cli-adapter.js.map +1 -0
- package/dist/src/native-thread-lifecycle-ledger-cli-adapter.d.ts +25 -0
- package/dist/src/native-thread-lifecycle-ledger-cli-adapter.js +50 -0
- package/dist/src/native-thread-lifecycle-ledger-cli-adapter.js.map +1 -0
- package/dist/src/native-thread-lifecycle-query-service.d.ts +75 -0
- package/dist/src/native-thread-lifecycle-query-service.js +297 -0
- package/dist/src/native-thread-lifecycle-query-service.js.map +1 -0
- package/dist/src/native-thread-lifecycle-recovery-adapter.d.ts +45 -0
- package/dist/src/native-thread-lifecycle-recovery-adapter.js +154 -0
- package/dist/src/native-thread-lifecycle-recovery-adapter.js.map +1 -0
- package/dist/src/native-thread-lifecycle-recovery-service.d.ts +211 -0
- package/dist/src/native-thread-lifecycle-recovery-service.js +1093 -0
- package/dist/src/native-thread-lifecycle-recovery-service.js.map +1 -0
- package/dist/src/native-thread-resume-snapshot-policy.d.ts +73 -0
- package/dist/src/native-thread-resume-snapshot-policy.js +218 -0
- package/dist/src/native-thread-resume-snapshot-policy.js.map +1 -0
- package/dist/src/native-thread-resume-snapshot.d.ts +3 -61
- package/dist/src/native-thread-resume-snapshot.js +2 -143
- package/dist/src/native-thread-resume-snapshot.js.map +1 -1
- package/dist/src/native-thread-transition-application.d.ts +147 -0
- package/dist/src/native-thread-transition-application.js +974 -0
- package/dist/src/native-thread-transition-application.js.map +1 -0
- package/dist/src/native-thread-transition-policy.d.ts +127 -0
- package/dist/src/native-thread-transition-policy.js +154 -0
- package/dist/src/native-thread-transition-policy.js.map +1 -0
- package/dist/src/native-thread-transition-resource-adapter.d.ts +12 -0
- package/dist/src/native-thread-transition-resource-adapter.js +44 -0
- package/dist/src/native-thread-transition-resource-adapter.js.map +1 -0
- package/dist/src/native-thread-transition-settlement-service.d.ts +94 -0
- package/dist/src/native-thread-transition-settlement-service.js +134 -0
- package/dist/src/native-thread-transition-settlement-service.js.map +1 -0
- package/dist/src/native-thread-transition-verification-adapter.d.ts +62 -0
- package/dist/src/native-thread-transition-verification-adapter.js +451 -0
- package/dist/src/native-thread-transition-verification-adapter.js.map +1 -0
- package/dist/src/openclaw-callback-transport.d.ts +69 -0
- package/dist/src/openclaw-callback-transport.js +583 -0
- package/dist/src/openclaw-callback-transport.js.map +1 -0
- package/dist/src/openclaw-plugin-callback-adapter.d.ts +1 -0
- package/dist/src/openclaw-plugin-callback-adapter.js +423 -0
- package/dist/src/openclaw-plugin-callback-adapter.js.map +1 -0
- package/dist/src/openclaw-plugin-command-adapter.d.ts +16 -0
- package/dist/src/openclaw-plugin-command-adapter.js +1868 -0
- package/dist/src/openclaw-plugin-command-adapter.js.map +1 -0
- package/dist/src/openclaw-plugin-helpers.d.ts +27 -4
- package/dist/src/openclaw-plugin-helpers.js +356 -81
- package/dist/src/openclaw-plugin-helpers.js.map +1 -1
- package/dist/src/openclaw-plugin-schemas.d.ts +358 -0
- package/dist/src/openclaw-plugin-schemas.js +366 -0
- package/dist/src/openclaw-plugin-schemas.js.map +1 -0
- package/dist/src/openclaw-plugin-supervisor.d.ts +6 -0
- package/dist/src/openclaw-plugin-supervisor.js +123 -0
- package/dist/src/openclaw-plugin-supervisor.js.map +1 -0
- package/dist/src/openclaw-plugin.js +7 -2212
- package/dist/src/openclaw-plugin.js.map +1 -1
- package/dist/src/openclaw-private-authority-offers.d.ts +22 -0
- package/dist/src/openclaw-private-authority-offers.js +177 -0
- package/dist/src/openclaw-private-authority-offers.js.map +1 -0
- package/dist/src/protocol.d.ts +5 -0
- package/dist/src/protocol.js +59 -3
- package/dist/src/protocol.js.map +1 -1
- package/dist/src/session-store.d.ts +1 -0
- package/dist/src/session-store.js +84 -106
- package/dist/src/session-store.js.map +1 -1
- package/dist/src/store.d.ts +1 -0
- package/dist/src/store.js +5 -2
- package/dist/src/store.js.map +1 -1
- package/dist/src/terminal-acceptance-application-service.d.ts +65 -0
- package/dist/src/terminal-acceptance-application-service.js +63 -0
- package/dist/src/terminal-acceptance-application-service.js.map +1 -0
- package/dist/src/terminal-acceptance-cli-adapter.d.ts +210 -0
- package/dist/src/terminal-acceptance-cli-adapter.js +957 -0
- package/dist/src/terminal-acceptance-cli-adapter.js.map +1 -0
- package/dist/src/terminal-action-projection.d.ts +309 -0
- package/dist/src/terminal-action-projection.js +376 -0
- package/dist/src/terminal-action-projection.js.map +1 -0
- package/dist/src/terminal-agent-bridge.d.ts +3 -0
- package/dist/src/terminal-agent-bridge.js +29 -8
- package/dist/src/terminal-agent-bridge.js.map +1 -1
- package/dist/src/terminal-authority-policy.d.ts +141 -0
- package/dist/src/terminal-authority-policy.js +367 -0
- package/dist/src/terminal-authority-policy.js.map +1 -0
- package/dist/src/terminal-binding-authority.d.ts +118 -0
- package/dist/src/terminal-binding-authority.js +245 -0
- package/dist/src/terminal-binding-authority.js.map +1 -0
- package/dist/src/terminal-binding-reconciliation-service.d.ts +75 -0
- package/dist/src/terminal-binding-reconciliation-service.js +73 -0
- package/dist/src/terminal-binding-reconciliation-service.js.map +1 -0
- package/dist/src/terminal-command-cli-adapter.d.ts +380 -0
- package/dist/src/terminal-command-cli-adapter.js +3288 -0
- package/dist/src/terminal-command-cli-adapter.js.map +1 -0
- package/dist/src/terminal-control-ref.d.ts +4 -0
- package/dist/src/terminal-control-ref.js +11 -8
- package/dist/src/terminal-control-ref.js.map +1 -1
- package/dist/src/terminal-delegate-cli-adapter.d.ts +41 -0
- package/dist/src/terminal-delegate-cli-adapter.js +242 -0
- package/dist/src/terminal-delegate-cli-adapter.js.map +1 -0
- package/dist/src/terminal-dispatch-abort.d.ts +22 -0
- package/dist/src/terminal-dispatch-abort.js +30 -0
- package/dist/src/terminal-dispatch-abort.js.map +1 -0
- package/dist/src/terminal-dispatch-application.d.ts +115 -0
- package/dist/src/terminal-dispatch-application.js +478 -0
- package/dist/src/terminal-dispatch-application.js.map +1 -0
- package/dist/src/terminal-dispatch-capability.d.ts +63 -0
- package/dist/src/terminal-dispatch-capability.js +133 -0
- package/dist/src/terminal-dispatch-capability.js.map +1 -0
- package/dist/src/terminal-dispatch-completion-cli-adapter.d.ts +22 -0
- package/dist/src/terminal-dispatch-completion-cli-adapter.js +96 -0
- package/dist/src/terminal-dispatch-completion-cli-adapter.js.map +1 -0
- package/dist/src/terminal-dispatch-composition.d.ts +79 -0
- package/dist/src/terminal-dispatch-composition.js +2 -0
- package/dist/src/terminal-dispatch-composition.js.map +1 -0
- package/dist/src/terminal-dispatch-execution.d.ts +290 -0
- package/dist/src/terminal-dispatch-execution.js +916 -0
- package/dist/src/terminal-dispatch-execution.js.map +1 -0
- package/dist/src/terminal-dispatch-ledger-codec.d.ts +110 -0
- package/dist/src/terminal-dispatch-ledger-codec.js +399 -0
- package/dist/src/terminal-dispatch-ledger-codec.js.map +1 -0
- package/dist/src/terminal-dispatch-policy.d.ts +93 -0
- package/dist/src/terminal-dispatch-policy.js +122 -0
- package/dist/src/terminal-dispatch-policy.js.map +1 -0
- package/dist/src/terminal-dispatch-presenter.d.ts +65 -0
- package/dist/src/terminal-dispatch-presenter.js +258 -0
- package/dist/src/terminal-dispatch-presenter.js.map +1 -0
- package/dist/src/terminal-dispatch-receipt.d.ts +79 -0
- package/dist/src/terminal-dispatch-receipt.js +314 -0
- package/dist/src/terminal-dispatch-receipt.js.map +1 -0
- package/dist/src/terminal-dispatch-recovery-cli-adapter.d.ts +132 -0
- package/dist/src/terminal-dispatch-recovery-cli-adapter.js +1330 -0
- package/dist/src/terminal-dispatch-recovery-cli-adapter.js.map +1 -0
- package/dist/src/terminal-dispatch-recovery-service.d.ts +271 -0
- package/dist/src/terminal-dispatch-recovery-service.js +374 -0
- package/dist/src/terminal-dispatch-recovery-service.js.map +1 -0
- package/dist/src/terminal-dispatch-repository-cli-adapter.d.ts +35 -0
- package/dist/src/terminal-dispatch-repository-cli-adapter.js +483 -0
- package/dist/src/terminal-dispatch-repository-cli-adapter.js.map +1 -0
- package/dist/src/terminal-handoff-application-service.d.ts +7 -0
- package/dist/src/terminal-handoff-application-service.js +18 -0
- package/dist/src/terminal-handoff-application-service.js.map +1 -0
- package/dist/src/terminal-handoff-cli-adapter.d.ts +229 -0
- package/dist/src/terminal-handoff-cli-adapter.js +1389 -0
- package/dist/src/terminal-handoff-cli-adapter.js.map +1 -0
- package/dist/src/terminal-handoff-facts.d.ts +12 -0
- package/dist/src/terminal-handoff-facts.js +16 -0
- package/dist/src/terminal-handoff-facts.js.map +1 -0
- package/dist/src/terminal-identity-authority-cli-adapter.d.ts +318 -0
- package/dist/src/terminal-identity-authority-cli-adapter.js +1072 -0
- package/dist/src/terminal-identity-authority-cli-adapter.js.map +1 -0
- package/dist/src/terminal-identity-authority-service.d.ts +48 -0
- package/dist/src/terminal-identity-authority-service.js +45 -0
- package/dist/src/terminal-identity-authority-service.js.map +1 -0
- package/dist/src/terminal-list-cli-adapter.d.ts +274 -0
- package/dist/src/terminal-list-cli-adapter.js +2444 -0
- package/dist/src/terminal-list-cli-adapter.js.map +1 -0
- package/dist/src/terminal-list-renderer.d.ts +34 -0
- package/dist/src/terminal-list-renderer.js +643 -0
- package/dist/src/terminal-list-renderer.js.map +1 -0
- package/dist/src/terminal-maintenance-cli-adapter.d.ts +102 -0
- package/dist/src/terminal-maintenance-cli-adapter.js +1059 -0
- package/dist/src/terminal-maintenance-cli-adapter.js.map +1 -0
- package/dist/src/terminal-monitor-application-service.d.ts +275 -0
- package/dist/src/terminal-monitor-application-service.js +982 -0
- package/dist/src/terminal-monitor-application-service.js.map +1 -0
- package/dist/src/terminal-monitor-cli-adapter.d.ts +130 -0
- package/dist/src/terminal-monitor-cli-adapter.js +442 -0
- package/dist/src/terminal-monitor-cli-adapter.js.map +1 -0
- package/dist/src/terminal-monitor-decision-policy.d.ts +98 -0
- package/dist/src/terminal-monitor-decision-policy.js +246 -0
- package/dist/src/terminal-monitor-decision-policy.js.map +1 -0
- package/dist/src/terminal-monitor-launch-plan.d.ts +45 -0
- package/dist/src/terminal-monitor-launch-plan.js +94 -0
- package/dist/src/terminal-monitor-launch-plan.js.map +1 -0
- package/dist/src/terminal-monitor-ownership-policy.d.ts +31 -0
- package/dist/src/terminal-monitor-ownership-policy.js +64 -0
- package/dist/src/terminal-monitor-ownership-policy.js.map +1 -0
- package/dist/src/terminal-monitor-poll-policy.d.ts +48 -0
- package/dist/src/terminal-monitor-poll-policy.js +67 -0
- package/dist/src/terminal-monitor-poll-policy.js.map +1 -0
- package/dist/src/terminal-monitor-reconciliation-eligibility.d.ts +53 -0
- package/dist/src/terminal-monitor-reconciliation-eligibility.js +114 -0
- package/dist/src/terminal-monitor-reconciliation-eligibility.js.map +1 -0
- package/dist/src/terminal-monitor-state-cli-adapter.d.ts +106 -0
- package/dist/src/terminal-monitor-state-cli-adapter.js +1492 -0
- package/dist/src/terminal-monitor-state-cli-adapter.js.map +1 -0
- package/dist/src/terminal-monitor-state-reconciliation-service.d.ts +87 -0
- package/dist/src/terminal-monitor-state-reconciliation-service.js +106 -0
- package/dist/src/terminal-monitor-state-reconciliation-service.js.map +1 -0
- package/dist/src/terminal-monitor-supervision-cli-adapter.d.ts +122 -0
- package/dist/src/terminal-monitor-supervision-cli-adapter.js +685 -0
- package/dist/src/terminal-monitor-supervision-cli-adapter.js.map +1 -0
- package/dist/src/terminal-process-facts.d.ts +2 -0
- package/dist/src/terminal-process-facts.js +19 -0
- package/dist/src/terminal-process-facts.js.map +1 -0
- package/dist/src/terminal-process-source.d.ts +6 -4
- package/dist/src/terminal-process-source.js +25 -20
- package/dist/src/terminal-process-source.js.map +1 -1
- package/dist/src/terminal-runtime-cli-adapter.d.ts +80 -0
- package/dist/src/terminal-runtime-cli-adapter.js +488 -0
- package/dist/src/terminal-runtime-cli-adapter.js.map +1 -0
- package/dist/src/terminal-scoped-approval-authority.d.ts +62 -0
- package/dist/src/terminal-scoped-approval-authority.js +281 -0
- package/dist/src/terminal-scoped-approval-authority.js.map +1 -0
- package/dist/src/terminal-selector-projection-service.d.ts +26 -0
- package/dist/src/terminal-selector-projection-service.js +81 -0
- package/dist/src/terminal-selector-projection-service.js.map +1 -0
- package/dist/src/terminal-status-cli-adapter.d.ts +126 -0
- package/dist/src/terminal-status-cli-adapter.js +577 -0
- package/dist/src/terminal-status-cli-adapter.js.map +1 -0
- package/dist/src/terminal-status-facts.d.ts +56 -0
- package/dist/src/terminal-status-facts.js +199 -0
- package/dist/src/terminal-status-facts.js.map +1 -0
- package/dist/src/terminal-submission-acceptance.d.ts +62 -145
- package/dist/src/terminal-submission-acceptance.js +555 -222
- package/dist/src/terminal-submission-acceptance.js.map +1 -1
- package/dist/src/terminal-submission-facts.d.ts +123 -0
- package/dist/src/terminal-submission-facts.js +205 -0
- package/dist/src/terminal-submission-facts.js.map +1 -0
- package/dist/src/terminal-turn-binding-authority-cli-adapter.d.ts +14 -0
- package/dist/src/terminal-turn-binding-authority-cli-adapter.js +90 -0
- package/dist/src/terminal-turn-binding-authority-cli-adapter.js.map +1 -0
- package/dist/src/terminal-watch-callback-cli-adapter.d.ts +20 -0
- package/dist/src/terminal-watch-callback-cli-adapter.js +55 -0
- package/dist/src/terminal-watch-callback-cli-adapter.js.map +1 -0
- package/dist/src/terminal-watch-cli-adapter.d.ts +64 -0
- package/dist/src/terminal-watch-cli-adapter.js +508 -0
- package/dist/src/terminal-watch-cli-adapter.js.map +1 -0
- package/dist/src/terminal-watch-service.d.ts +97 -0
- package/dist/src/terminal-watch-service.js +714 -0
- package/dist/src/terminal-watch-service.js.map +1 -0
- package/dist/src/terminal-watch-store.d.ts +149 -0
- package/dist/src/terminal-watch-store.js +861 -0
- package/dist/src/terminal-watch-store.js.map +1 -0
- package/dist/src/value-guards.d.ts +4 -0
- package/dist/src/value-guards.js +12 -0
- package/dist/src/value-guards.js.map +1 -0
- package/dist/src/verified-dead-agent-policy.d.ts +192 -0
- package/dist/src/verified-dead-agent-policy.js +262 -0
- package/dist/src/verified-dead-agent-policy.js.map +1 -0
- package/docs/quickstart-herdr.md +8 -0
- package/docs/quickstart-tmux.md +33 -6
- package/openclaw.plugin.json +11 -3
- package/package.json +3 -1
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +58 -40
|
@@ -0,0 +1,1125 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { managedSessionBindingToken, managedSessionRevision } from "./managed-session.js";
|
|
5
|
+
import { executorForConversation, sessionIdForConversation, turnIdForConversation } from "./protocol.js";
|
|
6
|
+
import { loadManagedSession } from "./session-store.js";
|
|
7
|
+
import { isCompleteNativeRollout, terminalControlsShareIncarnation } from "./terminal-authority-policy.js";
|
|
8
|
+
import { terminalControlEvidenceMatches } from "./terminal-control-ref.js";
|
|
9
|
+
import { terminalSubmissionPayload } from "./terminal-dispatch-execution.js";
|
|
10
|
+
import { terminalDispatchLedgerLooksLifecycle, terminalDispatchReceiptHistory as terminalLedgerReceiptHistory } from "./terminal-dispatch-ledger-codec.js";
|
|
11
|
+
import { terminalBridgeRequestFingerprint, terminalBridgeSubmission } from "./terminal-dispatch-receipt.js";
|
|
12
|
+
import { detectCodexCandidateSetRolloutAcceptance, detectCodexRolloutAcceptance, validateCodexRolloutAcceptanceAnchor, validateTerminalSubmissionAcceptanceEvidence } from "./terminal-submission-acceptance.js";
|
|
13
|
+
import { loadState, pathsForConversation, pathsForConversationDir, saveState } from "./store.js";
|
|
14
|
+
import { isRecord, nonBlankString as stringValue } from "./value-guards.js";
|
|
15
|
+
import { cliEnv, cliExit, cliNow, cliRuntimeLog as runtimeLog } from "./cli-runtime-context.js";
|
|
16
|
+
const TERMINAL_INPUT_EVIDENCE_FIELDS = [
|
|
17
|
+
"text_injected_at",
|
|
18
|
+
"enter_dispatched_at",
|
|
19
|
+
"submitted_at",
|
|
20
|
+
"agent_accepted_at",
|
|
21
|
+
"not_accepted_at",
|
|
22
|
+
"uncertain_at",
|
|
23
|
+
"acceptance_evidence"
|
|
24
|
+
];
|
|
25
|
+
const DEFERRED_INPUT_EVIDENCE_FIELDS = [
|
|
26
|
+
"dispatch_started_at",
|
|
27
|
+
...TERMINAL_INPUT_EVIDENCE_FIELDS
|
|
28
|
+
];
|
|
29
|
+
const DEFERRED_ACCEPTANCE_RECOVERY_STATUSES = new Set([
|
|
30
|
+
"prepared",
|
|
31
|
+
"text_injected",
|
|
32
|
+
"enter_dispatched",
|
|
33
|
+
"uncertain",
|
|
34
|
+
"agent_accepted"
|
|
35
|
+
]);
|
|
36
|
+
function required(value, message) {
|
|
37
|
+
if (value === undefined || value === null || value === "") {
|
|
38
|
+
throw new Error(message);
|
|
39
|
+
}
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
export function deferredCodexBoundaryFromTransfer(ports, { terminal, transfer }) {
|
|
43
|
+
const liveIncarnation = ports.native.processIncarnation(terminal.pid);
|
|
44
|
+
if (terminal.agent !== "codex" ||
|
|
45
|
+
transfer.terminal_id !== terminal.conversationId ||
|
|
46
|
+
transfer.process_pid !== terminal.pid ||
|
|
47
|
+
transfer.process_uuid !== liveIncarnation.processUuid ||
|
|
48
|
+
transfer.process_birth !== liveIncarnation.processBirth ||
|
|
49
|
+
path.resolve(transfer.workspace) !== path.resolve(terminal.terminalControl.currentPath ?? "") ||
|
|
50
|
+
!terminalControlEvidenceMatches(transfer.terminal_endpoint, terminal.terminalControl)) {
|
|
51
|
+
throw new Error(`deferred foreground transfer ${transfer.transfer_id} no longer ` +
|
|
52
|
+
"matches the exact terminal incarnation");
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
terminal,
|
|
56
|
+
transferId: transfer.transfer_id,
|
|
57
|
+
targetSessionId: transfer.target_session_id,
|
|
58
|
+
sourceSessionId: transfer.source_session_id,
|
|
59
|
+
sourceBoundRevision: transfer.source_expected_revision,
|
|
60
|
+
sourceBoundBindingToken: transfer.source_binding_token,
|
|
61
|
+
processUuid: transfer.process_uuid,
|
|
62
|
+
processBirth: transfer.process_birth,
|
|
63
|
+
previousDispatchSnapshot: {
|
|
64
|
+
status: transfer.previous_dispatch_status,
|
|
65
|
+
fingerprint: transfer.previous_dispatch_fingerprint
|
|
66
|
+
},
|
|
67
|
+
sourceKind: transfer.version === 1
|
|
68
|
+
? "status_card_only"
|
|
69
|
+
: required(transfer.source_kind, "deferred foreground source kind is unavailable"),
|
|
70
|
+
sourceRolloutAuthority: transfer.source_rollout_authority ?? "present",
|
|
71
|
+
...(transfer.source_abandonment_fingerprint
|
|
72
|
+
? {
|
|
73
|
+
sourceAbandonmentFingerprint: transfer.source_abandonment_fingerprint
|
|
74
|
+
}
|
|
75
|
+
: {}),
|
|
76
|
+
...(transfer.source_turn_history
|
|
77
|
+
? { sourceTurnHistory: transfer.source_turn_history }
|
|
78
|
+
: {}),
|
|
79
|
+
...(transfer.source_previous_last_transition_id
|
|
80
|
+
? {
|
|
81
|
+
sourcePreviousLastTransitionId: transfer.source_previous_last_transition_id
|
|
82
|
+
}
|
|
83
|
+
: {}),
|
|
84
|
+
sourceReservedRevision: transfer.source_expected_revision + 1,
|
|
85
|
+
sourceReservedBindingToken: managedSessionBindingToken({
|
|
86
|
+
session_id: transfer.source_session_id,
|
|
87
|
+
status: "transitioning",
|
|
88
|
+
binding: transfer.source_before_binding
|
|
89
|
+
}),
|
|
90
|
+
targetPreparedRevision: transfer.target_prepared_revision,
|
|
91
|
+
targetPreparedBindingToken: transfer.target_prepared_binding_token
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export function loadDeferredForegroundTurnAuthority(ports, { storeDir, terminal, transfer, scope }) {
|
|
95
|
+
if (scope)
|
|
96
|
+
assertActiveTurnRoute(scope, transfer);
|
|
97
|
+
const statePath = canonicalDeferredTurnStatePath(storeDir, transfer);
|
|
98
|
+
const canonical = pathsForConversationDir(path.dirname(statePath));
|
|
99
|
+
const conversation = loadState(statePath);
|
|
100
|
+
const takeover = isRecord(conversation.native_session_takeover)
|
|
101
|
+
? conversation.native_session_takeover
|
|
102
|
+
: undefined;
|
|
103
|
+
const submission = terminalBridgeSubmission(conversation);
|
|
104
|
+
const terminalControl = ports.turn.terminalControl(takeover);
|
|
105
|
+
const anchor = validatedDeferredAnchor(takeover?.codex_rollout_acceptance_anchor);
|
|
106
|
+
const target = loadManagedSession(storeDir, transfer.target_session_id);
|
|
107
|
+
const turnNativeThreadId = stringValue(conversation.native_thread_id);
|
|
108
|
+
const takeoverNativeThreadId = stringValue(takeover?.terminal_agent_session_id);
|
|
109
|
+
const submissionNativeThreadId = stringValue(submission?.native_thread_id);
|
|
110
|
+
const context = {
|
|
111
|
+
ports,
|
|
112
|
+
storeDir,
|
|
113
|
+
terminal,
|
|
114
|
+
transfer,
|
|
115
|
+
conversation,
|
|
116
|
+
statePath,
|
|
117
|
+
takeover,
|
|
118
|
+
submission,
|
|
119
|
+
terminalControl,
|
|
120
|
+
target,
|
|
121
|
+
targetBefore: transfer.target_before_binding,
|
|
122
|
+
anchor,
|
|
123
|
+
prebindingTurn: turnNativeThreadId === undefined &&
|
|
124
|
+
takeoverNativeThreadId === undefined &&
|
|
125
|
+
submissionNativeThreadId === undefined &&
|
|
126
|
+
takeover?.terminal_agent_rollout === undefined,
|
|
127
|
+
acceptedTurn: exactAcceptedDeferredTurn({
|
|
128
|
+
transfer,
|
|
129
|
+
submission,
|
|
130
|
+
turnNativeThreadId,
|
|
131
|
+
takeoverNativeThreadId,
|
|
132
|
+
submissionNativeThreadId,
|
|
133
|
+
turnRollout: takeover?.terminal_agent_rollout
|
|
134
|
+
}),
|
|
135
|
+
precommitTargetAuthority: exactPrecommitTarget(transfer, target),
|
|
136
|
+
committedTargetAuthority: exactCommittedTarget(transfer, target)
|
|
137
|
+
};
|
|
138
|
+
if (!deferredTurnAuthorityMatches(context)) {
|
|
139
|
+
throw new Error(`deferred foreground transfer ${transfer.transfer_id} Turn authority ` +
|
|
140
|
+
"does not match its exact request/terminal/process fence");
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
conversation,
|
|
144
|
+
statePath,
|
|
145
|
+
logPath: canonical.logPath,
|
|
146
|
+
takeover: takeover,
|
|
147
|
+
submission: submission,
|
|
148
|
+
anchor: anchor
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function canonicalDeferredTurnStatePath(storeDir, transfer) {
|
|
152
|
+
const statePath = path.resolve(required(transfer.state_path, "deferred foreground Turn state path is unavailable"));
|
|
153
|
+
const canonical = pathsForConversationDir(path.dirname(statePath));
|
|
154
|
+
if (path.resolve(canonical.storeDir) !== path.resolve(storeDir) ||
|
|
155
|
+
path.resolve(canonical.statePath) !== statePath) {
|
|
156
|
+
throw new Error(`deferred foreground transfer ${transfer.transfer_id} has a noncanonical ` +
|
|
157
|
+
"Turn state path");
|
|
158
|
+
}
|
|
159
|
+
return statePath;
|
|
160
|
+
}
|
|
161
|
+
function assertActiveTurnRoute(scope, transfer) {
|
|
162
|
+
if (!scope.transferBelongsToTurn(transfer)) {
|
|
163
|
+
throw new Error(`deferred foreground transfer ${transfer.transfer_id} escaped its ` +
|
|
164
|
+
"active Turn state capability");
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function validatedDeferredAnchor(value) {
|
|
168
|
+
try {
|
|
169
|
+
return validateCodexRolloutAcceptanceAnchor(value);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function exactPrecommitTarget(transfer, target) {
|
|
176
|
+
return Boolean(!["committed", "resolved"].includes(transfer.status) &&
|
|
177
|
+
target.status === "transitioning" &&
|
|
178
|
+
target.last_transition_id === transfer.transfer_id);
|
|
179
|
+
}
|
|
180
|
+
function exactCommittedTarget(transfer, target) {
|
|
181
|
+
const binding = transfer.target_accepted_binding;
|
|
182
|
+
if (!binding || !["committed", "resolved"].includes(transfer.status)) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
const transitioning = target.status === "transitioning" &&
|
|
186
|
+
target.last_transition_id === transfer.transfer_id &&
|
|
187
|
+
managedSessionRevision(target) === transfer.target_accepted_revision &&
|
|
188
|
+
managedSessionBindingToken(target) ===
|
|
189
|
+
transfer.target_accepted_binding_token;
|
|
190
|
+
const bound = target.status === "bound" &&
|
|
191
|
+
!target.last_transition_id &&
|
|
192
|
+
managedSessionRevision(target) ===
|
|
193
|
+
Number(transfer.target_accepted_revision) + 1 &&
|
|
194
|
+
managedSessionBindingToken(target) === managedSessionBindingToken({
|
|
195
|
+
session_id: transfer.target_session_id,
|
|
196
|
+
status: "bound",
|
|
197
|
+
binding
|
|
198
|
+
});
|
|
199
|
+
return (transitioning || bound) &&
|
|
200
|
+
JSON.stringify(target.binding) === JSON.stringify(binding);
|
|
201
|
+
}
|
|
202
|
+
function exactAcceptedDeferredTurn(input) {
|
|
203
|
+
if (!input.turnNativeThreadId ||
|
|
204
|
+
input.takeoverNativeThreadId !== input.turnNativeThreadId ||
|
|
205
|
+
input.submissionNativeThreadId !== input.turnNativeThreadId ||
|
|
206
|
+
!isCompleteNativeRollout(input.turnRollout) ||
|
|
207
|
+
String(input.submission?.status) !== "agent_accepted") {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
try {
|
|
211
|
+
validateTerminalSubmissionAcceptanceEvidence(input.submission?.acceptance_evidence, {
|
|
212
|
+
source: "codex_rollout",
|
|
213
|
+
nativeThreadId: input.turnNativeThreadId,
|
|
214
|
+
requestHash: input.transfer.request_hash
|
|
215
|
+
});
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function deferredTurnAuthorityMatches(context) {
|
|
223
|
+
return deferredTurnTerminalMatches(context) &&
|
|
224
|
+
deferredTurnStorageMatches(context) &&
|
|
225
|
+
deferredTurnRequestMatches(context) &&
|
|
226
|
+
deferredTurnBindingMatches(context) &&
|
|
227
|
+
deferredTargetMatches(context) &&
|
|
228
|
+
exactDeferredAcceptanceAnchor(context.anchor, context.transfer);
|
|
229
|
+
}
|
|
230
|
+
function deferredTurnTerminalMatches(context) {
|
|
231
|
+
const { terminalControl, terminal, conversation, transfer } = context;
|
|
232
|
+
return Boolean(context.takeover && context.submission && terminalControl &&
|
|
233
|
+
context.targetBefore &&
|
|
234
|
+
terminalControlsShareIncarnation(terminalControl, terminal.terminalControl) &&
|
|
235
|
+
terminalControlEvidenceMatches(context.takeover.terminal_endpoint ?? terminalControl, terminal.terminalControl) &&
|
|
236
|
+
executorForConversation(conversation).kind === "codex" &&
|
|
237
|
+
conversation.conversation_id === transfer.turn_id &&
|
|
238
|
+
sessionIdForConversation(conversation) === transfer.target_session_id &&
|
|
239
|
+
turnIdForConversation(conversation) === transfer.turn_id);
|
|
240
|
+
}
|
|
241
|
+
function deferredTurnStorageMatches(context) {
|
|
242
|
+
const { ports, storeDir, conversation, statePath, takeover, transfer } = context;
|
|
243
|
+
return Boolean(path.resolve(required(stringValue(conversation.state_path), "deferred foreground Turn state path is missing")) === statePath &&
|
|
244
|
+
path.resolve(pathsForConversation(conversation.conversation_id, storeDir).statePath) === statePath &&
|
|
245
|
+
path.resolve(required(ports.turn.storeDir(conversation), "deferred foreground Turn Store is missing")) === path.resolve(storeDir) &&
|
|
246
|
+
stringValue(takeover.deferred_foreground_transfer_id) ===
|
|
247
|
+
transfer.transfer_id &&
|
|
248
|
+
stringValue(takeover.terminal_bridge_message_id) === transfer.message_id);
|
|
249
|
+
}
|
|
250
|
+
function deferredTurnRequestMatches(context) {
|
|
251
|
+
const { storeDir, conversation, takeover, submission, transfer } = context;
|
|
252
|
+
const messageBodyHash = stringValue(submission.message_body_hash);
|
|
253
|
+
const requestText = stringValue(takeover.terminal_bridge_request_text);
|
|
254
|
+
return Boolean(stringValue(submission.session_id) === transfer.target_session_id &&
|
|
255
|
+
stringValue(submission.turn_id) === transfer.turn_id &&
|
|
256
|
+
stringValue(submission.message_id) === transfer.message_id &&
|
|
257
|
+
stringValue(submission.message_type) === "task" &&
|
|
258
|
+
stringValue(submission.executor_kind) === "codex" &&
|
|
259
|
+
messageBodyHash && /^[0-9a-f]{64}$/u.test(messageBodyHash) &&
|
|
260
|
+
createHash("sha256").update(String(conversation.user_request)).digest("hex") ===
|
|
261
|
+
messageBodyHash &&
|
|
262
|
+
terminalBridgeRequestFingerprint(terminalSubmissionPayload(String(conversation.user_request))) === transfer.request_hash &&
|
|
263
|
+
stringValue(takeover.terminal_bridge_request_hash) ===
|
|
264
|
+
transfer.request_hash &&
|
|
265
|
+
stringValue(submission.request_hash) === transfer.request_hash &&
|
|
266
|
+
requestText &&
|
|
267
|
+
terminalBridgeRequestFingerprint(requestText) === transfer.request_hash &&
|
|
268
|
+
path.resolve(required(stringValue(submission.store_dir), "deferred foreground submission Store is missing")) === path.resolve(storeDir));
|
|
269
|
+
}
|
|
270
|
+
function deferredTurnBindingMatches(context) {
|
|
271
|
+
const { conversation, takeover, submission, targetBefore, transfer } = context;
|
|
272
|
+
if (!targetBefore)
|
|
273
|
+
return false;
|
|
274
|
+
const bindingAuthority = stringValue(conversation.terminal_binding_id) === targetBefore.binding_id &&
|
|
275
|
+
Number(conversation.terminal_binding_generation) === targetBefore.generation &&
|
|
276
|
+
stringValue(takeover.terminal_binding_id) === targetBefore.binding_id &&
|
|
277
|
+
Number(takeover.terminal_binding_generation) === targetBefore.generation &&
|
|
278
|
+
stringValue(submission.binding_id) === targetBefore.binding_id &&
|
|
279
|
+
Number(submission.binding_generation) === targetBefore.generation;
|
|
280
|
+
return bindingAuthority && (context.prebindingTurn || context.acceptedTurn) &&
|
|
281
|
+
stringValue(takeover.terminal_agent_expected_session_id) === undefined &&
|
|
282
|
+
Number(takeover.terminal_agent_pid) === transfer.process_pid &&
|
|
283
|
+
stringValue(takeover.terminal_agent_process_uuid) ===
|
|
284
|
+
transfer.process_uuid &&
|
|
285
|
+
stringValue(takeover.terminal_agent_process_birth) ===
|
|
286
|
+
transfer.process_birth;
|
|
287
|
+
}
|
|
288
|
+
function deferredTargetMatches(context) {
|
|
289
|
+
const { target, targetBefore, transfer } = context;
|
|
290
|
+
if (!targetBefore)
|
|
291
|
+
return false;
|
|
292
|
+
return Boolean(target.session_id === transfer.target_session_id &&
|
|
293
|
+
(context.precommitTargetAuthority || context.committedTargetAuthority) &&
|
|
294
|
+
target.lineage.transition_id === transfer.transfer_id &&
|
|
295
|
+
target.lineage.previous_session_id === transfer.source_session_id &&
|
|
296
|
+
target.binding &&
|
|
297
|
+
target.binding.binding_id === targetBefore.binding_id &&
|
|
298
|
+
target.binding.generation === targetBefore.generation &&
|
|
299
|
+
target.binding.terminal_id === targetBefore.terminal_id &&
|
|
300
|
+
terminalControlsShareIncarnation(target.binding.terminal_control, targetBefore.terminal_control) &&
|
|
301
|
+
target.binding.native_process.pid === transfer.process_pid &&
|
|
302
|
+
target.binding.native_process.process_uuid === transfer.process_uuid &&
|
|
303
|
+
target.binding.native_process.process_birth === transfer.process_birth);
|
|
304
|
+
}
|
|
305
|
+
function exactDeferredAcceptanceAnchor(anchor, transfer) {
|
|
306
|
+
if (!anchor || anchor.process_uuid !== transfer.process_uuid ||
|
|
307
|
+
anchor.process_birth !== transfer.process_birth) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
if (anchor.version === 2) {
|
|
311
|
+
return anchor.mode === "pre_materialization" &&
|
|
312
|
+
anchor.native_thread_binding === "post_submission" &&
|
|
313
|
+
anchor.file_existed === false && anchor.offset_bytes === 0 &&
|
|
314
|
+
anchor.expected_empty_native_session === true;
|
|
315
|
+
}
|
|
316
|
+
return anchor.version === 3 && anchor.mode === "candidate_set" &&
|
|
317
|
+
anchor.inventory_pid === transfer.process_pid &&
|
|
318
|
+
path.resolve(anchor.inventory_cwd ?? "") === path.resolve(transfer.workspace) &&
|
|
319
|
+
anchor.candidate_rollouts.length > 0;
|
|
320
|
+
}
|
|
321
|
+
export function assertDeferredForegroundLedgerAuthority(ports, { storeDir, terminal, transfer, ledger, statePath, expectedMessageBodyHash }) {
|
|
322
|
+
const targetBefore = transfer.target_before_binding;
|
|
323
|
+
const ledgerMessageBodyHash = stringValue(ledger.message_body_hash);
|
|
324
|
+
const exactCommittedAcceptance = exactCommittedLedgerAcceptance(transfer, ledger);
|
|
325
|
+
const exactPostDispatchPending = exactPostDispatchPendingLedger(transfer, ledger);
|
|
326
|
+
const matches = targetBefore && ledgerIdentityMatches({
|
|
327
|
+
ports,
|
|
328
|
+
storeDir,
|
|
329
|
+
terminal,
|
|
330
|
+
transfer,
|
|
331
|
+
ledger,
|
|
332
|
+
statePath,
|
|
333
|
+
expectedMessageBodyHash,
|
|
334
|
+
ledgerMessageBodyHash,
|
|
335
|
+
targetBefore,
|
|
336
|
+
exactCommittedAcceptance,
|
|
337
|
+
exactPostDispatchPending
|
|
338
|
+
});
|
|
339
|
+
if (!matches) {
|
|
340
|
+
throw new Error(`deferred foreground transfer ${transfer.transfer_id} dispatch ledger ` +
|
|
341
|
+
"does not match its exact Turn authority");
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function exactCommittedLedgerAcceptance(transfer, ledger) {
|
|
345
|
+
if (!["committed", "resolved"].includes(transfer.status) ||
|
|
346
|
+
ledger.status !== "agent_accepted" ||
|
|
347
|
+
stringValue(ledger.native_thread_id) !== transfer.target_native_thread_id ||
|
|
348
|
+
ledger.dispatcher_pid !== null ||
|
|
349
|
+
stringValue(ledger.agent_accepted_at) !== transfer.agent_accepted_at ||
|
|
350
|
+
!transfer.target_native_thread_id) {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
try {
|
|
354
|
+
validateTerminalSubmissionAcceptanceEvidence(ledger.acceptance_evidence, {
|
|
355
|
+
source: "codex_rollout",
|
|
356
|
+
nativeThreadId: transfer.target_native_thread_id,
|
|
357
|
+
requestHash: transfer.request_hash
|
|
358
|
+
});
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
catch {
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
function exactPostDispatchPendingLedger(transfer, ledger) {
|
|
366
|
+
return transfer.status === "dispatch_started" &&
|
|
367
|
+
transfer.input_stage === "enter_dispatched" &&
|
|
368
|
+
ledger.status === "enter_dispatched" &&
|
|
369
|
+
ledger.dispatcher_pid === null &&
|
|
370
|
+
stringValue(ledger.prepared_at) === transfer.prepared_at &&
|
|
371
|
+
stringValue(ledger.text_injected_at) === transfer.text_injected_at &&
|
|
372
|
+
stringValue(ledger.enter_dispatched_at) === transfer.enter_dispatched_at &&
|
|
373
|
+
ledger.agent_accepted_at === undefined &&
|
|
374
|
+
ledger.not_accepted_at === undefined &&
|
|
375
|
+
ledger.uncertain_at === undefined &&
|
|
376
|
+
ledger.acceptance_evidence === undefined &&
|
|
377
|
+
ledger.safe_to_retry !== true;
|
|
378
|
+
}
|
|
379
|
+
function ledgerIdentityMatches(context) {
|
|
380
|
+
return ledgerOwnerFieldsMatch(context) && ledgerRouteMatches(context) &&
|
|
381
|
+
ledgerDispatchFenceMatches(context);
|
|
382
|
+
}
|
|
383
|
+
function ledgerOwnerFieldsMatch(context) {
|
|
384
|
+
const { ports, terminal, transfer, ledger, ledgerMessageBodyHash } = context;
|
|
385
|
+
return !terminalDispatchLedgerLooksLifecycle(ledger) &&
|
|
386
|
+
ports.ledger.matchesControl(ledger, terminal.terminalControl) &&
|
|
387
|
+
stringValue(ledger.deferred_foreground_transfer_id) ===
|
|
388
|
+
transfer.transfer_id &&
|
|
389
|
+
stringValue(ledger.generation_id) === transfer.message_id &&
|
|
390
|
+
stringValue(ledger.conversation_id) === transfer.turn_id &&
|
|
391
|
+
stringValue(ledger.session_id) === transfer.target_session_id &&
|
|
392
|
+
stringValue(ledger.turn_id) === transfer.turn_id &&
|
|
393
|
+
stringValue(ledger.message_id) === transfer.message_id &&
|
|
394
|
+
stringValue(ledger.message_type) === "task" &&
|
|
395
|
+
stringValue(ledger.executor_kind) === "codex" &&
|
|
396
|
+
Boolean(ledgerMessageBodyHash) &&
|
|
397
|
+
/^[0-9a-f]{64}$/u.test(ledgerMessageBodyHash) &&
|
|
398
|
+
(context.expectedMessageBodyHash === undefined ||
|
|
399
|
+
ledgerMessageBodyHash === context.expectedMessageBodyHash) &&
|
|
400
|
+
stringValue(ledger.request_hash) === transfer.request_hash;
|
|
401
|
+
}
|
|
402
|
+
function ledgerRouteMatches(context) {
|
|
403
|
+
const { storeDir, transfer, ledger, statePath, targetBefore } = context;
|
|
404
|
+
const nativeThreadId = stringValue(ledger.native_thread_id);
|
|
405
|
+
const nativeAuthority = context.exactCommittedAcceptance
|
|
406
|
+
? nativeThreadId === transfer.target_native_thread_id
|
|
407
|
+
: nativeThreadId === undefined;
|
|
408
|
+
return stringValue(ledger.binding_id) === targetBefore.binding_id &&
|
|
409
|
+
Number(ledger.binding_generation) === targetBefore.generation &&
|
|
410
|
+
nativeAuthority &&
|
|
411
|
+
path.resolve(required(ledger.state_path, "dispatch state path is missing")) ===
|
|
412
|
+
statePath &&
|
|
413
|
+
path.resolve(required(ledger.store_dir, "dispatch Store is missing")) ===
|
|
414
|
+
path.resolve(storeDir);
|
|
415
|
+
}
|
|
416
|
+
function ledgerDispatchFenceMatches(context) {
|
|
417
|
+
const { transfer, ledger } = context;
|
|
418
|
+
return ledger.status === "resolved" || context.exactCommittedAcceptance ||
|
|
419
|
+
context.exactPostDispatchPending ||
|
|
420
|
+
Number(ledger.dispatcher_pid) === transfer.dispatcher_pid;
|
|
421
|
+
}
|
|
422
|
+
export function assertDeferredForegroundResolvedZeroInputLedger(ports, { storeDir, terminal, transfer, ledger, statePath }) {
|
|
423
|
+
assertDeferredForegroundLedgerAuthority(ports, {
|
|
424
|
+
storeDir,
|
|
425
|
+
terminal,
|
|
426
|
+
transfer,
|
|
427
|
+
ledger,
|
|
428
|
+
statePath
|
|
429
|
+
});
|
|
430
|
+
const abortedAt = stringValue(ledger.aborted_at);
|
|
431
|
+
const resolvedAt = stringValue(ledger.resolved_at);
|
|
432
|
+
if (ledger.status !== "resolved" ||
|
|
433
|
+
ledger.safe_to_retry !== true ||
|
|
434
|
+
!abortedAt ||
|
|
435
|
+
!resolvedAt ||
|
|
436
|
+
!Number.isFinite(Date.parse(abortedAt)) ||
|
|
437
|
+
!Number.isFinite(Date.parse(resolvedAt)) ||
|
|
438
|
+
Date.parse(resolvedAt) < Date.parse(abortedAt) ||
|
|
439
|
+
DEFERRED_INPUT_EVIDENCE_FIELDS.some((field) => ledger[field] !== undefined)) {
|
|
440
|
+
throw new Error(`deferred foreground transfer ${transfer.transfer_id} resolved ledger ` +
|
|
441
|
+
"does not prove a zero-input abort");
|
|
442
|
+
}
|
|
443
|
+
// Validate the whole history for malformed/duplicate identities, then
|
|
444
|
+
// require the append-only copy of this exact generation. The top-level
|
|
445
|
+
// resolved record alone is insufficient because it can later be replaced.
|
|
446
|
+
terminalLedgerReceiptHistory(ledger);
|
|
447
|
+
const rawHistory = ledger.terminal_submission_receipts;
|
|
448
|
+
if (!Array.isArray(rawHistory)) {
|
|
449
|
+
throw new Error(`deferred foreground transfer ${transfer.transfer_id} resolved ledger ` +
|
|
450
|
+
"has no append-only abort receipt");
|
|
451
|
+
}
|
|
452
|
+
const ownReceipts = rawHistory.filter((receipt) => isRecord(receipt) &&
|
|
453
|
+
stringValue(receipt.message_id) === transfer.message_id);
|
|
454
|
+
if (ownReceipts.length !== 1) {
|
|
455
|
+
throw new Error(`deferred foreground transfer ${transfer.transfer_id} resolved ledger ` +
|
|
456
|
+
"does not have one exact append-only abort receipt");
|
|
457
|
+
}
|
|
458
|
+
const ownReceipt = ownReceipts[0];
|
|
459
|
+
assertDeferredForegroundLedgerAuthority(ports, {
|
|
460
|
+
storeDir,
|
|
461
|
+
terminal,
|
|
462
|
+
transfer,
|
|
463
|
+
// A receipt uses `aborted`, while the same exact owner fields at the
|
|
464
|
+
// top-level use `resolved`. Validate its authority in that same domain.
|
|
465
|
+
ledger: { ...ownReceipt, status: "resolved" },
|
|
466
|
+
statePath
|
|
467
|
+
});
|
|
468
|
+
if (ownReceipt.status !== "aborted" ||
|
|
469
|
+
ownReceipt.safe_to_retry !== true ||
|
|
470
|
+
stringValue(ownReceipt.aborted_at) !== abortedAt ||
|
|
471
|
+
stringValue(ownReceipt.resolved_at) !== resolvedAt ||
|
|
472
|
+
DEFERRED_INPUT_EVIDENCE_FIELDS.some((field) => ownReceipt[field] !== undefined)) {
|
|
473
|
+
throw new Error(`deferred foreground transfer ${transfer.transfer_id} append-only ` +
|
|
474
|
+
"receipt does not prove a zero-input abort");
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
export function deferredCodexDurableInputNotStartedAt(_ports, scope, transfer) {
|
|
478
|
+
if (transfer.status !== "dispatch_started" ||
|
|
479
|
+
transfer.input_stage !== "dispatch_started" ||
|
|
480
|
+
!transfer.state_path) {
|
|
481
|
+
return undefined;
|
|
482
|
+
}
|
|
483
|
+
assertActiveTurnRoute(scope, transfer);
|
|
484
|
+
const conversation = loadState(path.resolve(transfer.state_path));
|
|
485
|
+
const submission = terminalBridgeSubmission(conversation);
|
|
486
|
+
const abortedAt = stringValue(submission?.aborted_at);
|
|
487
|
+
if (submission?.status !== "aborted" ||
|
|
488
|
+
submission.safe_to_retry !== true ||
|
|
489
|
+
!abortedAt ||
|
|
490
|
+
!Number.isFinite(Date.parse(abortedAt)) ||
|
|
491
|
+
stringValue(submission.text_injected_at) !== undefined ||
|
|
492
|
+
stringValue(submission.enter_dispatched_at) !== undefined ||
|
|
493
|
+
stringValue(submission.agent_accepted_at) !== undefined) {
|
|
494
|
+
return undefined;
|
|
495
|
+
}
|
|
496
|
+
return abortedAt;
|
|
497
|
+
}
|
|
498
|
+
export function abortPreparedDeferredForegroundTurn(ports, input) {
|
|
499
|
+
const { options, scope, storeDir, terminal, transfer, boundary, terminalInputNotStartedAt } = input;
|
|
500
|
+
assertActiveTurnRoute(scope, transfer);
|
|
501
|
+
const dispatchIntentWasProvedNotStarted = assertAbortInputProof(transfer, terminalInputNotStartedAt);
|
|
502
|
+
const observedLedger = ports.ledger.load(terminal.terminalControl);
|
|
503
|
+
const previousDispatchStillCurrent = ports.ledger.previousSnapshotMatches({
|
|
504
|
+
transfer,
|
|
505
|
+
terminalControl: terminal.terminalControl,
|
|
506
|
+
ledger: observedLedger
|
|
507
|
+
});
|
|
508
|
+
const ledger = deferredTransferLedger(transfer, observedLedger, previousDispatchStillCurrent);
|
|
509
|
+
if (abortBeforeTurnPreparation(ports, input, ledger))
|
|
510
|
+
return;
|
|
511
|
+
const statePath = canonicalDeferredTurnStatePath(storeDir, transfer);
|
|
512
|
+
if (abortMissingDeferredTurn(ports, input, {
|
|
513
|
+
ledger,
|
|
514
|
+
statePath,
|
|
515
|
+
dispatchIntentWasProvedNotStarted
|
|
516
|
+
}))
|
|
517
|
+
return;
|
|
518
|
+
persistDeferredTurnAbortReceipts(ports, input, {
|
|
519
|
+
ledger,
|
|
520
|
+
dispatchIntentWasProvedNotStarted
|
|
521
|
+
});
|
|
522
|
+
if (cliEnv().AKK_TEST_EXIT_AFTER_DEFERRED_PREINPUT_ABORT_RECEIPTS === "1") {
|
|
523
|
+
// Both exact zero-input receipts are durable. A second writer may finish
|
|
524
|
+
// only the Store rollback and must never replay terminal input.
|
|
525
|
+
cliExit(86);
|
|
526
|
+
}
|
|
527
|
+
ports.application.abortBeforeInput({
|
|
528
|
+
options,
|
|
529
|
+
scope,
|
|
530
|
+
boundary,
|
|
531
|
+
reason: "recovered exact prepared Turn with zero terminal input",
|
|
532
|
+
terminalInputNotStartedAt
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
function assertAbortInputProof(transfer, terminalInputNotStartedAt) {
|
|
536
|
+
const dispatchIntentWasProvedNotStarted = transfer.status === "dispatch_started" &&
|
|
537
|
+
transfer.input_stage === "dispatch_started" &&
|
|
538
|
+
terminalInputNotStartedAt !== undefined &&
|
|
539
|
+
Number.isFinite(Date.parse(terminalInputNotStartedAt));
|
|
540
|
+
if (terminalInputNotStartedAt !== undefined &&
|
|
541
|
+
!dispatchIntentWasProvedNotStarted) {
|
|
542
|
+
throw new Error(`deferred foreground transfer ${transfer.transfer_id} cannot attach ` +
|
|
543
|
+
"terminal-input-not-started proof to its current stage");
|
|
544
|
+
}
|
|
545
|
+
return dispatchIntentWasProvedNotStarted;
|
|
546
|
+
}
|
|
547
|
+
function deferredTransferLedger(transfer, observedLedger, previousDispatchStillCurrent) {
|
|
548
|
+
// Before this transfer publishes its own ordinary dispatch ledger, the
|
|
549
|
+
// terminal may still carry the exact resolved history that was snapshot into
|
|
550
|
+
// the immutable transfer receipt. Treat only that exact fingerprint (or the
|
|
551
|
+
// exact persisted `none`) as no new dispatch; every other change must prove
|
|
552
|
+
// this transfer's own ledger below or fail closed.
|
|
553
|
+
if (!previousDispatchStillCurrent && !observedLedger) {
|
|
554
|
+
throw new Error(`deferred foreground transfer ${transfer.transfer_id} previous dispatch ` +
|
|
555
|
+
"snapshot disappeared before its own ledger was published");
|
|
556
|
+
}
|
|
557
|
+
return previousDispatchStillCurrent
|
|
558
|
+
? undefined
|
|
559
|
+
: observedLedger;
|
|
560
|
+
}
|
|
561
|
+
function abortBeforeTurnPreparation(ports, input, ledger) {
|
|
562
|
+
const { options, scope, transfer, boundary } = input;
|
|
563
|
+
if (!transfer.state_path) {
|
|
564
|
+
if (ledger) {
|
|
565
|
+
throw new Error(`deferred foreground transfer ${transfer.transfer_id} has a dispatch ` +
|
|
566
|
+
"before its Turn authority was prepared");
|
|
567
|
+
}
|
|
568
|
+
ports.application.abortBeforeInput({
|
|
569
|
+
options,
|
|
570
|
+
scope,
|
|
571
|
+
boundary,
|
|
572
|
+
reason: "recovered before target Turn preparation"
|
|
573
|
+
});
|
|
574
|
+
return true;
|
|
575
|
+
}
|
|
576
|
+
return false;
|
|
577
|
+
}
|
|
578
|
+
function abortMissingDeferredTurn(ports, input, facts) {
|
|
579
|
+
const { options, scope, storeDir, terminal, transfer, boundary } = input;
|
|
580
|
+
const { ledger, statePath, dispatchIntentWasProvedNotStarted } = facts;
|
|
581
|
+
if (!fs.existsSync(statePath)) {
|
|
582
|
+
if (dispatchIntentWasProvedNotStarted) {
|
|
583
|
+
throw new Error(`deferred foreground transfer ${transfer.transfer_id} lost its Turn ` +
|
|
584
|
+
"before terminal-input-not-started proof became durable");
|
|
585
|
+
}
|
|
586
|
+
if (ledger) {
|
|
587
|
+
// runTerminalControlSend publishes the exact prepared terminal ledger
|
|
588
|
+
// before its Turn state. A crash in that narrow window still proves
|
|
589
|
+
// zero input, provided every immutable ledger field matches this
|
|
590
|
+
// transfer. Resolve that owner before aborting/restoring the Sessions.
|
|
591
|
+
assertDeferredForegroundLedgerAuthority(ports, {
|
|
592
|
+
storeDir,
|
|
593
|
+
terminal,
|
|
594
|
+
transfer,
|
|
595
|
+
ledger,
|
|
596
|
+
statePath
|
|
597
|
+
});
|
|
598
|
+
if (!["prepared", "resolved"].includes(String(ledger.status))) {
|
|
599
|
+
throw new Error(`deferred foreground transfer ${transfer.transfer_id} has ` +
|
|
600
|
+
`${String(ledger.status)} transport evidence but no exact Turn state`);
|
|
601
|
+
}
|
|
602
|
+
if (ledger.status === "prepared") {
|
|
603
|
+
const abortedAt = cliNow().toISOString();
|
|
604
|
+
ports.ledger.save(terminal.terminalControl, {
|
|
605
|
+
...ledger,
|
|
606
|
+
status: "resolved",
|
|
607
|
+
aborted_at: abortedAt,
|
|
608
|
+
safe_to_retry: true,
|
|
609
|
+
resolved_at: abortedAt,
|
|
610
|
+
dispatcher_pid: null,
|
|
611
|
+
reason: "deferred dispatcher exited after ledger prepare and before Turn persistence"
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
else {
|
|
615
|
+
assertDeferredForegroundResolvedZeroInputLedger(ports, {
|
|
616
|
+
storeDir,
|
|
617
|
+
terminal,
|
|
618
|
+
transfer,
|
|
619
|
+
ledger,
|
|
620
|
+
statePath
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
if (cliEnv().AKK_TEST_EXIT_AFTER_DEFERRED_LEDGER_ABORT_WITHOUT_STATE === "1") {
|
|
624
|
+
// The zero-input top-level and append-only ledger receipts are both
|
|
625
|
+
// durable, while the transfer still fences both Sessions. A second
|
|
626
|
+
// writer must accept this exact resolved form and finish Store-only
|
|
627
|
+
// rollback without creating or replaying a Turn.
|
|
628
|
+
cliExit(86);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
ports.application.abortBeforeInput({
|
|
632
|
+
options,
|
|
633
|
+
scope,
|
|
634
|
+
boundary,
|
|
635
|
+
reason: ledger
|
|
636
|
+
? "recovered exact prepared ledger before target Turn state became durable"
|
|
637
|
+
: "recovered before target Turn state became durable"
|
|
638
|
+
});
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
641
|
+
return false;
|
|
642
|
+
}
|
|
643
|
+
function persistDeferredTurnAbortReceipts(ports, input, facts) {
|
|
644
|
+
const { storeDir, terminal, transfer, terminalInputNotStartedAt } = input;
|
|
645
|
+
const authority = loadDeferredForegroundTurnAuthority(ports, {
|
|
646
|
+
storeDir,
|
|
647
|
+
terminal,
|
|
648
|
+
transfer,
|
|
649
|
+
scope: input.scope
|
|
650
|
+
});
|
|
651
|
+
const submissionStatus = String(authority.submission.status);
|
|
652
|
+
assertAbortLedger(ports, input, authority, facts.ledger, submissionStatus);
|
|
653
|
+
const durableNotStartedAt = reconcileTurnAbortProof(transfer, authority.submission, submissionStatus, terminalInputNotStartedAt, facts.dispatchIntentWasProvedNotStarted);
|
|
654
|
+
persistTurnAbort(ports, transfer, authority, submissionStatus, durableNotStartedAt);
|
|
655
|
+
assertResolvedAbortLedgerProof(transfer, facts.ledger, durableNotStartedAt, facts.dispatchIntentWasProvedNotStarted);
|
|
656
|
+
persistAbortLedger(ports, terminal, facts.ledger, durableNotStartedAt);
|
|
657
|
+
}
|
|
658
|
+
function assertAbortLedger(ports, input, authority, ledger, submissionStatus) {
|
|
659
|
+
const { storeDir, terminal, transfer } = input;
|
|
660
|
+
if (!ledger && submissionStatus !== "aborted") {
|
|
661
|
+
throw new Error(`deferred foreground transfer ${transfer.transfer_id} has prepared Turn ` +
|
|
662
|
+
"state without its exact terminal ledger");
|
|
663
|
+
}
|
|
664
|
+
if (!ledger)
|
|
665
|
+
return;
|
|
666
|
+
assertDeferredForegroundLedgerAuthority(ports, {
|
|
667
|
+
storeDir,
|
|
668
|
+
terminal,
|
|
669
|
+
transfer,
|
|
670
|
+
ledger,
|
|
671
|
+
statePath: authority.statePath,
|
|
672
|
+
expectedMessageBodyHash: stringValue(authority.submission.message_body_hash)
|
|
673
|
+
});
|
|
674
|
+
if (!["prepared", "resolved"].includes(String(ledger.status))) {
|
|
675
|
+
throw new Error(`deferred foreground transfer ${transfer.transfer_id} has ` +
|
|
676
|
+
`${String(ledger.status)} transport evidence and cannot be aborted`);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
function reconcileTurnAbortProof(transfer, submission, submissionStatus, terminalInputNotStartedAt, dispatchIntentWasProvedNotStarted) {
|
|
680
|
+
if (!dispatchIntentWasProvedNotStarted || submissionStatus !== "aborted") {
|
|
681
|
+
return terminalInputNotStartedAt;
|
|
682
|
+
}
|
|
683
|
+
const existingAbortedAt = stringValue(submission.aborted_at);
|
|
684
|
+
if (submission.safe_to_retry !== true || !existingAbortedAt ||
|
|
685
|
+
!Number.isFinite(Date.parse(existingAbortedAt)) ||
|
|
686
|
+
stringValue(submission.text_injected_at) !== undefined ||
|
|
687
|
+
stringValue(submission.enter_dispatched_at) !== undefined ||
|
|
688
|
+
stringValue(submission.agent_accepted_at) !== undefined ||
|
|
689
|
+
(terminalInputNotStartedAt !== undefined &&
|
|
690
|
+
terminalInputNotStartedAt !== existingAbortedAt)) {
|
|
691
|
+
throw new Error(`deferred foreground transfer ${transfer.transfer_id} has ` +
|
|
692
|
+
"inconsistent terminal-input-not-started Turn proof");
|
|
693
|
+
}
|
|
694
|
+
return existingAbortedAt;
|
|
695
|
+
}
|
|
696
|
+
function persistTurnAbort(ports, transfer, authority, submissionStatus, durableNotStartedAt) {
|
|
697
|
+
if (submissionStatus !== "prepared") {
|
|
698
|
+
if (submissionStatus === "aborted")
|
|
699
|
+
return;
|
|
700
|
+
throw new Error(`deferred foreground transfer ${transfer.transfer_id} Turn has ` +
|
|
701
|
+
`${submissionStatus} input evidence and cannot be aborted`);
|
|
702
|
+
}
|
|
703
|
+
const abortedAt = durableNotStartedAt ?? cliNow().toISOString();
|
|
704
|
+
const aborted = ports.turn.withSubmission({
|
|
705
|
+
conversation: {
|
|
706
|
+
...authority.conversation,
|
|
707
|
+
status: "failed",
|
|
708
|
+
failed_at: abortedAt,
|
|
709
|
+
failure_reason: "dispatcher exited before the deferred terminal-input boundary",
|
|
710
|
+
updated_at: abortedAt
|
|
711
|
+
},
|
|
712
|
+
messageId: transfer.message_id,
|
|
713
|
+
messageType: "task",
|
|
714
|
+
requestText: String(authority.takeover.terminal_bridge_request_text ?? ""),
|
|
715
|
+
status: "aborted",
|
|
716
|
+
preparedAt: stringValue(authority.submission.prepared_at) ?? abortedAt,
|
|
717
|
+
abortedAt,
|
|
718
|
+
error: "dispatcher exited before terminal input",
|
|
719
|
+
safeToRetry: true
|
|
720
|
+
});
|
|
721
|
+
saveState(authority.statePath, aborted);
|
|
722
|
+
}
|
|
723
|
+
function assertResolvedAbortLedgerProof(transfer, ledger, durableNotStartedAt, dispatchIntentWasProvedNotStarted) {
|
|
724
|
+
if (!dispatchIntentWasProvedNotStarted || ledger?.status !== "resolved") {
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
if (ledger.safe_to_retry !== true ||
|
|
728
|
+
stringValue(ledger.aborted_at) !== durableNotStartedAt ||
|
|
729
|
+
stringValue(ledger.text_injected_at) !== undefined ||
|
|
730
|
+
stringValue(ledger.enter_dispatched_at) !== undefined ||
|
|
731
|
+
stringValue(ledger.agent_accepted_at) !== undefined) {
|
|
732
|
+
throw new Error(`deferred foreground transfer ${transfer.transfer_id} has ` +
|
|
733
|
+
"inconsistent terminal-input-not-started ledger proof");
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
function persistAbortLedger(ports, terminal, ledger, durableNotStartedAt) {
|
|
737
|
+
if (!ledger || ledger.status === "resolved")
|
|
738
|
+
return;
|
|
739
|
+
const abortedAt = durableNotStartedAt ?? cliNow().toISOString();
|
|
740
|
+
ports.ledger.save(terminal.terminalControl, {
|
|
741
|
+
...ledger,
|
|
742
|
+
status: "resolved",
|
|
743
|
+
aborted_at: abortedAt,
|
|
744
|
+
safe_to_retry: true,
|
|
745
|
+
resolved_at: abortedAt,
|
|
746
|
+
dispatcher_pid: null,
|
|
747
|
+
reason: "deferred dispatcher exited before the durable terminal-input boundary"
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
export async function recoverAcceptedDeferredForegroundDispatch(ports, input) {
|
|
751
|
+
const { options, scope, storeDir, terminal, transfer, boundary } = input;
|
|
752
|
+
assertActiveTurnRoute(scope, transfer);
|
|
753
|
+
path.resolve(required(transfer.state_path, "deferred foreground Turn state path is unavailable"));
|
|
754
|
+
const authority = loadDeferredForegroundTurnAuthority(ports, {
|
|
755
|
+
storeDir,
|
|
756
|
+
terminal,
|
|
757
|
+
transfer,
|
|
758
|
+
scope
|
|
759
|
+
});
|
|
760
|
+
const ledger = required(ports.ledger.load(terminal.terminalControl), `deferred foreground transfer ${transfer.transfer_id} has no exact ` +
|
|
761
|
+
"terminal dispatch ledger for acceptance recovery");
|
|
762
|
+
assertAcceptanceRecoveryAuthority(ports, input, authority, ledger);
|
|
763
|
+
const recovered = await observeRecoveredAcceptance(ports, input, authority.anchor);
|
|
764
|
+
if (!recovered)
|
|
765
|
+
return false;
|
|
766
|
+
validateAcceptedLedger(ledger, transfer, recovered.identity);
|
|
767
|
+
const acceptedAt = cliNow().toISOString();
|
|
768
|
+
const acceptedConversation = acceptedDeferredConversation(ports, authority, transfer, recovered, acceptedAt);
|
|
769
|
+
let acceptedStatePersisted = false;
|
|
770
|
+
// Turn acceptance is stronger than every Session/ledger write that follows.
|
|
771
|
+
saveState(authority.statePath, acceptedConversation);
|
|
772
|
+
acceptedStatePersisted = true;
|
|
773
|
+
if (cliEnv().AKK_TEST_EXIT_AFTER_DEFERRED_ACCEPTED_TURN === "1")
|
|
774
|
+
cliExit(86);
|
|
775
|
+
return commitRecoveredAcceptance(ports, input, {
|
|
776
|
+
authority,
|
|
777
|
+
ledger,
|
|
778
|
+
recovered,
|
|
779
|
+
acceptedAt,
|
|
780
|
+
acceptedConversation,
|
|
781
|
+
acceptedStatePersisted
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
function assertAcceptanceRecoveryAuthority(ports, input, authority, ledger) {
|
|
785
|
+
const { storeDir, terminal, transfer } = input;
|
|
786
|
+
assertDeferredForegroundLedgerAuthority(ports, {
|
|
787
|
+
storeDir,
|
|
788
|
+
terminal,
|
|
789
|
+
transfer,
|
|
790
|
+
ledger,
|
|
791
|
+
statePath: authority.statePath,
|
|
792
|
+
expectedMessageBodyHash: stringValue(authority.submission.message_body_hash)
|
|
793
|
+
});
|
|
794
|
+
ports.authority.assertFrozen({
|
|
795
|
+
storeDir,
|
|
796
|
+
transfer,
|
|
797
|
+
terminalControl: terminal.terminalControl
|
|
798
|
+
});
|
|
799
|
+
if (!DEFERRED_ACCEPTANCE_RECOVERY_STATUSES.has(String(ledger.status))) {
|
|
800
|
+
throw new Error(`deferred foreground transfer ${transfer.transfer_id} has invalid ` +
|
|
801
|
+
`${String(ledger.status)} dispatch evidence for acceptance recovery`);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
async function observeRecoveredAcceptance(ports, input, anchor) {
|
|
805
|
+
const { options, terminal, transfer } = input;
|
|
806
|
+
if (anchor.version === 3) {
|
|
807
|
+
const inventory = await ports.native.inventory({
|
|
808
|
+
options,
|
|
809
|
+
pid: transfer.process_pid,
|
|
810
|
+
cwd: terminal.terminalControl.currentPath
|
|
811
|
+
});
|
|
812
|
+
const result = detectCodexCandidateSetRolloutAcceptance({
|
|
813
|
+
anchor,
|
|
814
|
+
currentInventory: inventory,
|
|
815
|
+
requestHash: transfer.request_hash
|
|
816
|
+
});
|
|
817
|
+
if (result.status === "uncertain") {
|
|
818
|
+
throw new Error(`Codex candidate-set recovery is uncertain (${result.code}): ` +
|
|
819
|
+
result.reason);
|
|
820
|
+
}
|
|
821
|
+
return result.status === "accepted"
|
|
822
|
+
? { identity: result.identity, evidence: result.evidence }
|
|
823
|
+
: undefined;
|
|
824
|
+
}
|
|
825
|
+
const identity = await ports.native.identity({
|
|
826
|
+
options,
|
|
827
|
+
agent: "codex",
|
|
828
|
+
pid: transfer.process_pid,
|
|
829
|
+
cwd: terminal.terminalControl.currentPath
|
|
830
|
+
});
|
|
831
|
+
if (!identity)
|
|
832
|
+
return undefined;
|
|
833
|
+
const evidence = detectCodexRolloutAcceptance({
|
|
834
|
+
anchor,
|
|
835
|
+
currentIdentity: identity,
|
|
836
|
+
requestHash: transfer.request_hash
|
|
837
|
+
});
|
|
838
|
+
return evidence ? { identity, evidence } : undefined;
|
|
839
|
+
}
|
|
840
|
+
function validateAcceptedLedger(ledger, transfer, identity) {
|
|
841
|
+
if (ledger.status !== "agent_accepted")
|
|
842
|
+
return;
|
|
843
|
+
validateTerminalSubmissionAcceptanceEvidence(ledger.acceptance_evidence, {
|
|
844
|
+
source: "codex_rollout",
|
|
845
|
+
nativeThreadId: identity.sessionId,
|
|
846
|
+
requestHash: transfer.request_hash
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
function acceptedDeferredConversation(ports, authority, transfer, recovered, acceptedAt) {
|
|
850
|
+
return ports.turn.withSubmission({
|
|
851
|
+
conversation: ports.turn.withIdentity(authority.conversation, recovered.identity),
|
|
852
|
+
messageId: transfer.message_id,
|
|
853
|
+
messageType: "task",
|
|
854
|
+
requestText: String(authority.takeover.terminal_bridge_request_text ?? ""),
|
|
855
|
+
status: "agent_accepted",
|
|
856
|
+
preparedAt: stringValue(authority.submission.prepared_at) ??
|
|
857
|
+
transfer.prepared_at,
|
|
858
|
+
textInjectedAt: stringValue(authority.submission.text_injected_at) ??
|
|
859
|
+
transfer.text_injected_at ?? transfer.dispatch_started_at,
|
|
860
|
+
enterDispatchedAt: stringValue(authority.submission.enter_dispatched_at) ??
|
|
861
|
+
transfer.enter_dispatched_at ?? transfer.dispatch_started_at,
|
|
862
|
+
agentAcceptedAt: acceptedAt,
|
|
863
|
+
acceptanceEvidence: recovered.evidence
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
async function commitRecoveredAcceptance(ports, input, facts) {
|
|
867
|
+
let transferCommitted = false;
|
|
868
|
+
try {
|
|
869
|
+
await ports.application.commit({
|
|
870
|
+
options: input.options,
|
|
871
|
+
scope: input.scope,
|
|
872
|
+
boundary: input.boundary,
|
|
873
|
+
identity: facts.recovered.identity,
|
|
874
|
+
acceptedAt: facts.acceptedAt
|
|
875
|
+
});
|
|
876
|
+
transferCommitted = true;
|
|
877
|
+
ports.authority.assertTurnIdentity({
|
|
878
|
+
conversation: facts.acceptedConversation,
|
|
879
|
+
currentIdentity: facts.recovered.identity,
|
|
880
|
+
operation: "recover deferred foreground binding for"
|
|
881
|
+
});
|
|
882
|
+
saveRecoveredAcceptanceLedger(ports, input, facts);
|
|
883
|
+
return true;
|
|
884
|
+
}
|
|
885
|
+
catch (error) {
|
|
886
|
+
recordRecoveredAcceptanceFailure(ports, input, facts, transferCommitted, error);
|
|
887
|
+
throw error;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
function saveRecoveredAcceptanceLedger(ports, input, facts) {
|
|
891
|
+
const { terminal, transfer } = input;
|
|
892
|
+
ports.ledger.save(terminal.terminalControl, {
|
|
893
|
+
...facts.ledger,
|
|
894
|
+
...ports.ledger.bindingFields(facts.acceptedConversation),
|
|
895
|
+
status: "agent_accepted",
|
|
896
|
+
generation_id: transfer.message_id,
|
|
897
|
+
conversation_id: facts.acceptedConversation.conversation_id,
|
|
898
|
+
session_id: transfer.target_session_id,
|
|
899
|
+
turn_id: transfer.turn_id,
|
|
900
|
+
message_id: transfer.message_id,
|
|
901
|
+
message_type: "task",
|
|
902
|
+
request_hash: transfer.request_hash,
|
|
903
|
+
prepared_at: transfer.prepared_at,
|
|
904
|
+
text_injected_at: transfer.text_injected_at ?? transfer.dispatch_started_at,
|
|
905
|
+
enter_dispatched_at: transfer.enter_dispatched_at ?? transfer.dispatch_started_at,
|
|
906
|
+
agent_accepted_at: facts.acceptedAt,
|
|
907
|
+
acceptance_evidence: facts.recovered.evidence,
|
|
908
|
+
dispatcher_pid: null,
|
|
909
|
+
state_path: facts.authority.statePath,
|
|
910
|
+
event_log_path: facts.authority.logPath,
|
|
911
|
+
callback_expected: Boolean(facts.acceptedConversation.gateway_method),
|
|
912
|
+
reason: "recovered exact deferred foreground request acceptance"
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
function recordRecoveredAcceptanceFailure(ports, input, facts, transferCommitted, error) {
|
|
916
|
+
if (transferCommitted && !facts.acceptedStatePersisted) {
|
|
917
|
+
saveMissingAcceptedStateFailure(ports, input, facts, error);
|
|
918
|
+
}
|
|
919
|
+
else if (transferCommitted) {
|
|
920
|
+
saveAcceptedBookkeepingFailure(ports, input, facts, error);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
function saveMissingAcceptedStateFailure(ports, input, facts, error) {
|
|
924
|
+
const uncertainAt = cliNow().toISOString();
|
|
925
|
+
const reason = "deferred Session transfer resolved but exact Turn identity " +
|
|
926
|
+
`bookkeeping failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
927
|
+
const uncertainConversation = ports.turn.withSubmission({
|
|
928
|
+
conversation: {
|
|
929
|
+
...facts.authority.conversation,
|
|
930
|
+
status: "stalled",
|
|
931
|
+
stalled_at: uncertainAt,
|
|
932
|
+
stalled_reason: reason,
|
|
933
|
+
updated_at: uncertainAt
|
|
934
|
+
},
|
|
935
|
+
messageId: input.transfer.message_id,
|
|
936
|
+
messageType: "task",
|
|
937
|
+
requestText: String(facts.authority.takeover.terminal_bridge_request_text ?? ""),
|
|
938
|
+
status: "uncertain",
|
|
939
|
+
preparedAt: stringValue(facts.authority.submission.prepared_at) ??
|
|
940
|
+
input.transfer.prepared_at,
|
|
941
|
+
textInjectedAt: stringValue(facts.authority.submission.text_injected_at) ??
|
|
942
|
+
input.transfer.text_injected_at ?? input.transfer.dispatch_started_at,
|
|
943
|
+
enterDispatchedAt: stringValue(facts.authority.submission.enter_dispatched_at) ??
|
|
944
|
+
input.transfer.enter_dispatched_at ?? input.transfer.dispatch_started_at,
|
|
945
|
+
uncertainAt,
|
|
946
|
+
error: reason,
|
|
947
|
+
lastProvenStage: "enter_dispatched",
|
|
948
|
+
safeToRetry: false
|
|
949
|
+
});
|
|
950
|
+
saveState(facts.authority.statePath, uncertainConversation);
|
|
951
|
+
ports.ledger.save(input.terminal.terminalControl, {
|
|
952
|
+
...facts.ledger,
|
|
953
|
+
...ports.ledger.bindingFields(uncertainConversation),
|
|
954
|
+
status: "uncertain",
|
|
955
|
+
uncertain_at: uncertainAt,
|
|
956
|
+
dispatcher_pid: null,
|
|
957
|
+
callback_expected: false,
|
|
958
|
+
reason
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
function saveAcceptedBookkeepingFailure(ports, input, facts, error) {
|
|
962
|
+
const stalledAt = cliNow().toISOString();
|
|
963
|
+
const reason = "deferred transfer resolved, but exact accepted Turn bookkeeping " +
|
|
964
|
+
`failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
965
|
+
saveState(facts.authority.statePath, {
|
|
966
|
+
...facts.acceptedConversation,
|
|
967
|
+
status: "stalled",
|
|
968
|
+
stalled_at: stalledAt,
|
|
969
|
+
stalled_reason: reason,
|
|
970
|
+
updated_at: stalledAt
|
|
971
|
+
});
|
|
972
|
+
try {
|
|
973
|
+
ports.ledger.save(input.terminal.terminalControl, {
|
|
974
|
+
...facts.ledger,
|
|
975
|
+
...ports.ledger.bindingFields(facts.acceptedConversation),
|
|
976
|
+
status: "uncertain",
|
|
977
|
+
uncertain_at: stalledAt,
|
|
978
|
+
last_proven_stage: "agent_accepted",
|
|
979
|
+
acceptance_evidence: facts.recovered.evidence,
|
|
980
|
+
dispatcher_pid: null,
|
|
981
|
+
callback_expected: false,
|
|
982
|
+
reason
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
catch {
|
|
986
|
+
// The exact accepted Turn remains authoritative if ledger bookkeeping fails.
|
|
987
|
+
}
|
|
988
|
+
runtimeLog("error", "deferred_acceptance_ledger_bookkeeping_failed", {
|
|
989
|
+
transfer_id: input.transfer.transfer_id,
|
|
990
|
+
turn_id: input.transfer.turn_id,
|
|
991
|
+
state_path: facts.authority.statePath,
|
|
992
|
+
error: error instanceof Error ? error.message : String(error)
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
export async function persistCommittedDeferredForegroundTurnAcceptance(ports, input) {
|
|
996
|
+
const { options, scope, storeDir, terminal, transfer } = input;
|
|
997
|
+
const current = scope.loadTransfer(transfer.transfer_id);
|
|
998
|
+
assertActiveTurnRoute(scope, current);
|
|
999
|
+
const identity = committedDeferredIdentity(current, transfer.transfer_id);
|
|
1000
|
+
path.resolve(required(current.state_path, "deferred foreground Turn state path is unavailable"));
|
|
1001
|
+
const authority = loadDeferredForegroundTurnAuthority(ports, {
|
|
1002
|
+
storeDir,
|
|
1003
|
+
terminal,
|
|
1004
|
+
transfer: current,
|
|
1005
|
+
scope
|
|
1006
|
+
});
|
|
1007
|
+
const ledger = required(ports.ledger.load(terminal.terminalControl), `deferred foreground transfer ${current.transfer_id} has no exact ` +
|
|
1008
|
+
"terminal dispatch ledger for committed recovery");
|
|
1009
|
+
assertCommittedRecoveryLedger(ports, input, current, authority, ledger);
|
|
1010
|
+
const acceptance = await observeCommittedAcceptance(ports, { ...input, transfer: current }, authority.anchor, identity);
|
|
1011
|
+
validateAcceptedLedger(ledger, current, identity);
|
|
1012
|
+
const acceptedConversation = acceptedDeferredConversation(ports, authority, current, { identity, evidence: acceptance }, current.agent_accepted_at);
|
|
1013
|
+
saveState(authority.statePath, acceptedConversation);
|
|
1014
|
+
saveCommittedAcceptanceLedger(ports, {
|
|
1015
|
+
terminal,
|
|
1016
|
+
transfer: current,
|
|
1017
|
+
authority,
|
|
1018
|
+
ledger,
|
|
1019
|
+
acceptance,
|
|
1020
|
+
acceptedConversation
|
|
1021
|
+
});
|
|
1022
|
+
return { conversation: acceptedConversation, identity };
|
|
1023
|
+
}
|
|
1024
|
+
function committedDeferredIdentity(transfer, expectedTransferId) {
|
|
1025
|
+
const binding = transfer.target_accepted_binding;
|
|
1026
|
+
if (transfer.status !== "committed" || !binding ||
|
|
1027
|
+
!transfer.target_native_thread_id || !transfer.agent_accepted_at) {
|
|
1028
|
+
throw new Error(`deferred foreground transfer ${expectedTransferId} has no exact ` +
|
|
1029
|
+
"committed acceptance authority");
|
|
1030
|
+
}
|
|
1031
|
+
const rollout = binding.native_process.rollout;
|
|
1032
|
+
const processUuid = binding.native_process.process_uuid;
|
|
1033
|
+
const processBirth = binding.native_process.process_birth;
|
|
1034
|
+
if (binding.native_thread_id !== transfer.target_native_thread_id ||
|
|
1035
|
+
processUuid !== transfer.process_uuid ||
|
|
1036
|
+
processBirth !== transfer.process_birth ||
|
|
1037
|
+
!isCompleteNativeRollout(rollout)) {
|
|
1038
|
+
throw new Error(`deferred foreground transfer ${expectedTransferId} committed binding ` +
|
|
1039
|
+
"is incomplete");
|
|
1040
|
+
}
|
|
1041
|
+
return {
|
|
1042
|
+
sessionId: transfer.target_native_thread_id,
|
|
1043
|
+
processUuid,
|
|
1044
|
+
processBirth,
|
|
1045
|
+
rollout,
|
|
1046
|
+
evidence: binding.native_process.evidence
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
function assertCommittedRecoveryLedger(ports, input, current, authority, ledger) {
|
|
1050
|
+
assertDeferredForegroundLedgerAuthority(ports, {
|
|
1051
|
+
storeDir: input.storeDir,
|
|
1052
|
+
terminal: input.terminal,
|
|
1053
|
+
transfer: current,
|
|
1054
|
+
ledger,
|
|
1055
|
+
statePath: authority.statePath,
|
|
1056
|
+
expectedMessageBodyHash: stringValue(authority.submission.message_body_hash)
|
|
1057
|
+
});
|
|
1058
|
+
if (!DEFERRED_ACCEPTANCE_RECOVERY_STATUSES.has(String(ledger.status))) {
|
|
1059
|
+
throw new Error(`deferred foreground transfer ${current.transfer_id} has invalid ` +
|
|
1060
|
+
`${String(ledger.status)} dispatch evidence for committed recovery`);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
async function observeCommittedAcceptance(ports, input, anchor, identity) {
|
|
1064
|
+
let acceptance;
|
|
1065
|
+
if (anchor.version === 3) {
|
|
1066
|
+
const inventory = await ports.native.inventory({
|
|
1067
|
+
options: input.options,
|
|
1068
|
+
pid: input.transfer.process_pid,
|
|
1069
|
+
cwd: input.terminal.terminalControl.currentPath
|
|
1070
|
+
});
|
|
1071
|
+
const result = detectCodexCandidateSetRolloutAcceptance({
|
|
1072
|
+
anchor,
|
|
1073
|
+
currentInventory: inventory,
|
|
1074
|
+
requestHash: input.transfer.request_hash
|
|
1075
|
+
});
|
|
1076
|
+
if (result.status === "uncertain") {
|
|
1077
|
+
throw new Error(`Codex committed candidate acceptance is uncertain (${result.code}): ` +
|
|
1078
|
+
result.reason);
|
|
1079
|
+
}
|
|
1080
|
+
if (result.status === "accepted") {
|
|
1081
|
+
if (result.identity.sessionId !== identity.sessionId) {
|
|
1082
|
+
throw new Error("committed candidate acceptance resolved to a different native thread");
|
|
1083
|
+
}
|
|
1084
|
+
acceptance = result.evidence;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
else {
|
|
1088
|
+
acceptance = detectCodexRolloutAcceptance({
|
|
1089
|
+
anchor,
|
|
1090
|
+
currentIdentity: identity,
|
|
1091
|
+
requestHash: input.transfer.request_hash
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
if (!acceptance) {
|
|
1095
|
+
throw new Error(`deferred foreground transfer ${input.transfer.transfer_id} cannot re-prove ` +
|
|
1096
|
+
"its exact request acceptance");
|
|
1097
|
+
}
|
|
1098
|
+
return acceptance;
|
|
1099
|
+
}
|
|
1100
|
+
function saveCommittedAcceptanceLedger(ports, facts) {
|
|
1101
|
+
const { terminal, transfer, authority, ledger, acceptance, acceptedConversation } = facts;
|
|
1102
|
+
ports.ledger.save(terminal.terminalControl, {
|
|
1103
|
+
...ledger,
|
|
1104
|
+
...ports.ledger.bindingFields(acceptedConversation),
|
|
1105
|
+
status: "agent_accepted",
|
|
1106
|
+
generation_id: transfer.message_id,
|
|
1107
|
+
conversation_id: acceptedConversation.conversation_id,
|
|
1108
|
+
session_id: transfer.target_session_id,
|
|
1109
|
+
turn_id: transfer.turn_id,
|
|
1110
|
+
message_id: transfer.message_id,
|
|
1111
|
+
message_type: "task",
|
|
1112
|
+
request_hash: transfer.request_hash,
|
|
1113
|
+
prepared_at: transfer.prepared_at,
|
|
1114
|
+
text_injected_at: transfer.text_injected_at ?? transfer.dispatch_started_at,
|
|
1115
|
+
enter_dispatched_at: transfer.enter_dispatched_at ?? transfer.dispatch_started_at,
|
|
1116
|
+
agent_accepted_at: transfer.agent_accepted_at,
|
|
1117
|
+
acceptance_evidence: acceptance,
|
|
1118
|
+
dispatcher_pid: null,
|
|
1119
|
+
state_path: authority.statePath,
|
|
1120
|
+
event_log_path: authority.logPath,
|
|
1121
|
+
callback_expected: Boolean(acceptedConversation.gateway_method),
|
|
1122
|
+
reason: "recovered committed deferred foreground request acceptance"
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
//# sourceMappingURL=deferred-foreground-recovery-cli-adapter.js.map
|