@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,1059 @@
|
|
|
1
|
+
// Raw CLI composition for renew, cancel, and close maintenance commands.
|
|
2
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { withCanonicalMutationLocks } from "./mutation-transaction.js";
|
|
5
|
+
import { budgetAction, executorForConversation, sessionIdForConversation, isSessionSendBlockingStatus, turnIdForConversation } from "./protocol.js";
|
|
6
|
+
import { appendEvent, loadState, logPathForStatePath, pathsForConversationDir, saveState, withStoreWriterLeaseAsync } from "./store.js";
|
|
7
|
+
import { loadManagedSession, tryLoadManagedSession } from "./session-store.js";
|
|
8
|
+
import { TerminalControlUnavailableError } from "./terminal-control-provider.js";
|
|
9
|
+
import { terminalControlsShareIncarnation } from "./terminal-authority-policy.js";
|
|
10
|
+
import { terminalControlEvidenceMatches } from "./terminal-control-ref.js";
|
|
11
|
+
import { terminalControlFromTakeover } from "./terminal-runtime-cli-adapter.js";
|
|
12
|
+
import { terminalBridgeSubmission } from "./terminal-dispatch-receipt.js";
|
|
13
|
+
import { terminalDispatchLedgerLooksLifecycle } from "./terminal-dispatch-ledger-codec.js";
|
|
14
|
+
import { loadDeferredForegroundTransfer } from "./deferred-foreground-transfer.js";
|
|
15
|
+
import { isFinalDeferredForegroundTransferStatus } from "./deferred-foreground-transfer-policy.js";
|
|
16
|
+
import { isRecoverableTerminalDispatchStatus } from "./terminal-dispatch-policy.js";
|
|
17
|
+
import { decideVerifiedDeadAgentProcess } from "./verified-dead-agent-policy.js";
|
|
18
|
+
import { terminalMonitorDeadlineAt as deadlineAt } from "./terminal-monitor-decision-policy.js";
|
|
19
|
+
import { writeCliJson as printJson } from "./cli-command-runtime.js";
|
|
20
|
+
import { cliCwd, cliEnv, cliExit, cliNow, cliNowMs, cliRuntimeLog as runtimeLog } from "./cli-runtime-context.js";
|
|
21
|
+
import { isRecord, nonBlankString as stringValue } from "./value-guards.js";
|
|
22
|
+
const terminalMaintenanceContext = new AsyncLocalStorage();
|
|
23
|
+
function terminalMaintenanceRuntime() {
|
|
24
|
+
const runtime = terminalMaintenanceContext.getStore();
|
|
25
|
+
if (!runtime) {
|
|
26
|
+
throw new Error("Terminal maintenance facade runtime is unavailable");
|
|
27
|
+
}
|
|
28
|
+
return runtime;
|
|
29
|
+
}
|
|
30
|
+
function contextualPort(group, name) {
|
|
31
|
+
return ((...arguments_) => {
|
|
32
|
+
const operation = terminalMaintenanceRuntime().dependencies[group][name];
|
|
33
|
+
return operation(...arguments_);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function required(value, message) {
|
|
37
|
+
if (value === undefined || value === "")
|
|
38
|
+
throw new Error(message);
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
const loadConversationFromOptions = contextualPort("runtime", "loadConversation");
|
|
42
|
+
const storeDirFromOptions = contextualPort("runtime", "storeDir");
|
|
43
|
+
const createTerminalControlProvider = contextualPort("runtime", "createControlProvider");
|
|
44
|
+
const createTerminalAgentBridge = contextualPort("runtime", "createBridge");
|
|
45
|
+
const startTerminalBridgeMonitorForConversation = contextualPort("runtime", "startMonitor");
|
|
46
|
+
const positiveMinutes = contextualPort("runtime", "positiveMinutes");
|
|
47
|
+
const textSummary = contextualPort("runtime", "textSummary");
|
|
48
|
+
const resolveTerminalConversationFromOptions = contextualPort("identity", "resolveTerminalConversationFromOptions");
|
|
49
|
+
const migrateLegacyTerminalAgentIdentity = contextualPort("identity", "migrateLegacyTerminalAgentIdentity");
|
|
50
|
+
const terminalRuntimeIdentityForConversation = contextualPort("identity", "terminalRuntimeIdentityForConversation");
|
|
51
|
+
const codexAllowedCompanionSetForManagedSession = contextualPort("identity", "codexAllowedCompanionSetForManagedSession");
|
|
52
|
+
const managedBindingConflictKindForResolvedTerminal = contextualPort("identity", "managedBindingConflictKindForResolvedTerminal");
|
|
53
|
+
const observeBoundTerminalAgentProcess = contextualPort("identity", "observeBoundTerminalAgentProcess");
|
|
54
|
+
const resolveCurrentNativeAgentSessionIdentity = contextualPort("identity", "resolveNativeIdentity");
|
|
55
|
+
const observeCurrentNativeAgentSessionIdentity = contextualPort("identity", "observeNativeIdentity");
|
|
56
|
+
const assertConversationHasNoNonterminalDeferredForegroundTransfer = contextualPort("authority", "assertConversationHasNoNonterminalDeferredForegroundTransfer");
|
|
57
|
+
const assertTerminalHasNoNonterminalDeferredForegroundTransfer = contextualPort("authority", "assertTerminalHasNoNonterminalDeferredForegroundTransfer");
|
|
58
|
+
const observedExternalHandoffIdentity = contextualPort("authority", "observedExternalHandoffIdentity");
|
|
59
|
+
const observedHandoffAuthorityToken = contextualPort("authority", "observedHandoffAuthorityToken");
|
|
60
|
+
const observedHandoffTargetResolution = contextualPort("authority", "observedHandoffTargetResolution");
|
|
61
|
+
const activeTurnHandoffDecisionToken = contextualPort("authority", "activeTurnHandoffDecisionToken");
|
|
62
|
+
const assertTurnBindingCurrent = contextualPort("authority", "assertTurnBindingCurrent");
|
|
63
|
+
const assertManagedTerminalDispatchOwner = contextualPort("authority", "assertManagedTerminalDispatchOwner");
|
|
64
|
+
const loadTerminalDispatchLedgerOwner = contextualPort("authority", "loadTerminalDispatchLedgerOwner");
|
|
65
|
+
const terminalListCliFacade = Object.freeze({
|
|
66
|
+
managedSessionHasUnresolvedNativeTransition: contextualPort("authority", "hasUnresolvedNativeTransition")
|
|
67
|
+
});
|
|
68
|
+
const nativeThreadLifecycleFacade = Object.freeze({
|
|
69
|
+
assertExclusive: contextualPort("authority", "assertExclusive")
|
|
70
|
+
});
|
|
71
|
+
const acquireFileLock = contextualPort("repository", "acquireFileLock");
|
|
72
|
+
const acquireTerminalBridgeSendLock = contextualPort("repository", "acquireTerminalLock");
|
|
73
|
+
const withStoreWriterLease = contextualPort("repository", "withStoreWriterLease");
|
|
74
|
+
const resolveTerminalBridgeDispatchLedger = contextualPort("repository", "resolveDispatch");
|
|
75
|
+
const terminalWriterMutationLocks = contextualPort("repository", "terminalWriterLocks");
|
|
76
|
+
const terminalWriterStateMutationLocks = contextualPort("repository", "terminalWriterStateLocks");
|
|
77
|
+
const mutationConversationStore = Object.freeze({
|
|
78
|
+
load: contextualPort("repository", "conversationLoad"),
|
|
79
|
+
save: contextualPort("repository", "conversationSave"),
|
|
80
|
+
appendEvent: contextualPort("repository", "conversationAppendEvent")
|
|
81
|
+
});
|
|
82
|
+
const mutationManagedSessions = Object.freeze({
|
|
83
|
+
load: contextualPort("repository", "sessionLoad")
|
|
84
|
+
});
|
|
85
|
+
const mutationDispatchLedger = Object.freeze({
|
|
86
|
+
load: contextualPort("repository", "ledgerLoad"),
|
|
87
|
+
save: contextualPort("repository", "ledgerSave"),
|
|
88
|
+
resolve: contextualPort("repository", "ledgerResolve"),
|
|
89
|
+
reconcileIncarnation: contextualPort("repository", "ledgerReconcileIncarnation"),
|
|
90
|
+
reconcile: contextualPort("repository", "ledgerReconcile")
|
|
91
|
+
});
|
|
92
|
+
const isVerifiedDeadTerminalAgentProcess = contextualPort("repository", "isVerifiedDead");
|
|
93
|
+
const exactVerifiedDeadTerminalAgentProcessAuthority = contextualPort("repository", "exactVerifiedDeadAuthority");
|
|
94
|
+
const ensureVerifiedDeadTerminalAgentProcessEvent = contextualPort("repository", "ensureVerifiedDeadEvent");
|
|
95
|
+
const ensureVerifiedDeadConversationClosedEvent = contextualPort("repository", "ensureVerifiedDeadClosedEvent");
|
|
96
|
+
const assertVerifiedDeadTerminalBridgeDispatchAuthority = contextualPort("repository", "assertVerifiedDeadDispatch");
|
|
97
|
+
const resolveVerifiedDeadTerminalBridgeDispatchLedger = contextualPort("repository", "resolveVerifiedDeadDispatch");
|
|
98
|
+
async function runRenew(options) {
|
|
99
|
+
const loaded = loadConversationFromOptions(options);
|
|
100
|
+
const { statePath, logPath } = loaded;
|
|
101
|
+
const conversation = await migrateLegacyTerminalAgentIdentity({
|
|
102
|
+
...loaded,
|
|
103
|
+
options
|
|
104
|
+
});
|
|
105
|
+
if (conversation.status === "closed") {
|
|
106
|
+
throw new Error(`cannot renew ${conversation.conversation_id}; conversation is closed`);
|
|
107
|
+
}
|
|
108
|
+
if (conversation.status !== "stalled") {
|
|
109
|
+
throw new Error(`cannot renew ${conversation.conversation_id}; conversation is ${conversation.status}, not stalled`);
|
|
110
|
+
}
|
|
111
|
+
if (isVerifiedDeadTerminalAgentProcess(conversation)) {
|
|
112
|
+
throw new Error(`cannot renew ${conversation.conversation_id}; its bound terminal agent ` +
|
|
113
|
+
"process is verified dead. Close this orphaned Turn instead.");
|
|
114
|
+
}
|
|
115
|
+
if (terminalBridgeSubmission(conversation)?.status === "uncertain") {
|
|
116
|
+
throw new Error(`cannot renew ${conversation.conversation_id}; its terminal submission ` +
|
|
117
|
+
"is uncertain and cannot be attributed by monitoring. Inspect the pane " +
|
|
118
|
+
"and explicitly close the Turn to abandon the unresolved result.");
|
|
119
|
+
}
|
|
120
|
+
const nativeTakeover = isRecord(conversation.native_session_takeover)
|
|
121
|
+
? conversation.native_session_takeover
|
|
122
|
+
: undefined;
|
|
123
|
+
const terminalControl = terminalControlFromTakeover(nativeTakeover);
|
|
124
|
+
if (!terminalControl || nativeTakeover?.["terminal_bridge"] !== true) {
|
|
125
|
+
throw new Error(`cannot renew ${conversation.conversation_id}; conversation is not a terminal bridge task`);
|
|
126
|
+
}
|
|
127
|
+
const terminalProvider = createTerminalControlProvider(options);
|
|
128
|
+
try {
|
|
129
|
+
await terminalProvider.resolve(terminalProvider.endpoint(terminalControl));
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
throw new Error(`cannot renew ${conversation.conversation_id}; terminal ${terminalControl.target} is no longer available`);
|
|
133
|
+
}
|
|
134
|
+
const expectedMessageId = stringValue(nativeTakeover?.terminal_bridge_message_id);
|
|
135
|
+
const expectedStartedAt = stringValue(nativeTakeover?.terminal_bridge_started_at);
|
|
136
|
+
let renewed = conversation;
|
|
137
|
+
let renewedTerminalControl = terminalControl;
|
|
138
|
+
let inactivityTimeoutMinutes = 0;
|
|
139
|
+
let hardTimeoutMinutes = 0;
|
|
140
|
+
const releaseStateLock = acquireFileLock(`${statePath}.lock`);
|
|
141
|
+
try {
|
|
142
|
+
const current = loadState(statePath);
|
|
143
|
+
if (current.status !== "stalled") {
|
|
144
|
+
throw new Error(`cannot renew ${current.conversation_id}; conversation is ${current.status}, not stalled`);
|
|
145
|
+
}
|
|
146
|
+
if (isVerifiedDeadTerminalAgentProcess(current)) {
|
|
147
|
+
throw new Error(`cannot renew ${current.conversation_id}; its bound terminal agent ` +
|
|
148
|
+
"process is verified dead");
|
|
149
|
+
}
|
|
150
|
+
if (terminalBridgeSubmission(current)?.status === "uncertain") {
|
|
151
|
+
throw new Error(`cannot renew ${current.conversation_id}; its terminal submission is uncertain`);
|
|
152
|
+
}
|
|
153
|
+
const currentTakeover = isRecord(current.native_session_takeover)
|
|
154
|
+
? current.native_session_takeover
|
|
155
|
+
: undefined;
|
|
156
|
+
const currentControl = terminalControlFromTakeover(currentTakeover);
|
|
157
|
+
if (!currentControl || currentTakeover?.terminal_bridge !== true) {
|
|
158
|
+
throw new Error(`cannot renew ${current.conversation_id}; conversation is not a terminal bridge task`);
|
|
159
|
+
}
|
|
160
|
+
if (current.conversation_id !== conversation.conversation_id ||
|
|
161
|
+
!terminalControlsShareIncarnation(currentControl, terminalControl) ||
|
|
162
|
+
stringValue(currentTakeover.terminal_bridge_message_id) !== expectedMessageId ||
|
|
163
|
+
stringValue(currentTakeover.terminal_bridge_started_at) !== expectedStartedAt) {
|
|
164
|
+
throw new Error("conversation changed while waiting to renew; refresh status and retry");
|
|
165
|
+
}
|
|
166
|
+
assertTurnBindingCurrent(current, "renew");
|
|
167
|
+
renewedTerminalControl = currentControl;
|
|
168
|
+
inactivityTimeoutMinutes = positiveMinutes(options.minutes ??
|
|
169
|
+
options.agentTimeoutMinutes ??
|
|
170
|
+
currentTakeover.terminal_bridge_inactivity_timeout_minutes ??
|
|
171
|
+
terminalMaintenanceRuntime().dependencies.runtime
|
|
172
|
+
.defaultAgentTimeoutMinutes, "--minutes");
|
|
173
|
+
hardTimeoutMinutes = positiveMinutes(currentTakeover.terminal_bridge_hard_timeout_minutes ??
|
|
174
|
+
terminalMaintenanceRuntime().dependencies.runtime
|
|
175
|
+
.defaultAgentHardTimeoutMinutes, "--agent-hard-timeout-minutes");
|
|
176
|
+
const startedAt = stringValue(currentTakeover.terminal_bridge_started_at);
|
|
177
|
+
const startedAtMs = startedAt ? Date.parse(startedAt) : NaN;
|
|
178
|
+
if (Number.isFinite(startedAtMs) &&
|
|
179
|
+
cliNowMs() - startedAtMs >= hardTimeoutMinutes * 60 * 1000) {
|
|
180
|
+
throw new Error(`cannot renew ${current.conversation_id}; terminal bridge hard lifetime of ${hardTimeoutMinutes} minutes has elapsed`);
|
|
181
|
+
}
|
|
182
|
+
const now = cliNow().toISOString();
|
|
183
|
+
const hardDeadline = deadlineAt(startedAt ?? now, hardTimeoutMinutes) ??
|
|
184
|
+
new Date(cliNowMs() + hardTimeoutMinutes * 60 * 1000).toISOString();
|
|
185
|
+
const inactivityDeadline = deadlineAt(now, inactivityTimeoutMinutes) ??
|
|
186
|
+
new Date(cliNowMs() + inactivityTimeoutMinutes * 60 * 1000).toISOString();
|
|
187
|
+
renewed = {
|
|
188
|
+
...current,
|
|
189
|
+
status: "waiting_for_agent",
|
|
190
|
+
native_session_takeover: {
|
|
191
|
+
...currentTakeover,
|
|
192
|
+
terminal_bridge_monitor_lock_version: terminalMaintenanceRuntime().dependencies.runtime.monitorLockVersion,
|
|
193
|
+
terminal_bridge_monitor_started_at: now,
|
|
194
|
+
terminal_bridge_last_activity_at: now,
|
|
195
|
+
terminal_bridge_inactivity_timeout_minutes: inactivityTimeoutMinutes,
|
|
196
|
+
terminal_bridge_hard_timeout_minutes: hardTimeoutMinutes,
|
|
197
|
+
terminal_bridge_inactivity_deadline_at: inactivityDeadline,
|
|
198
|
+
terminal_bridge_hard_deadline_at: hardDeadline,
|
|
199
|
+
terminal_bridge_renewed_at: now
|
|
200
|
+
},
|
|
201
|
+
updated_at: now
|
|
202
|
+
};
|
|
203
|
+
Reflect.deleteProperty(renewed, "stalled_at");
|
|
204
|
+
Reflect.deleteProperty(renewed, "stalled_reason");
|
|
205
|
+
Reflect.deleteProperty(renewed, "stalled_notification_sent_at");
|
|
206
|
+
Reflect.deleteProperty(renewed, "stalled_notification_message_id");
|
|
207
|
+
saveState(statePath, renewed);
|
|
208
|
+
appendEvent(logPath, {
|
|
209
|
+
ts: now,
|
|
210
|
+
conversation_id: current.conversation_id,
|
|
211
|
+
event: "terminal_bridge_renewed",
|
|
212
|
+
previous_status: current.status,
|
|
213
|
+
terminal_control: currentControl,
|
|
214
|
+
agent_timeout_minutes: inactivityTimeoutMinutes,
|
|
215
|
+
agent_hard_timeout_minutes: hardTimeoutMinutes,
|
|
216
|
+
last_activity_at: now
|
|
217
|
+
});
|
|
218
|
+
runtimeLog("info", "terminal_bridge_renewed", {
|
|
219
|
+
conversation_id: current.conversation_id,
|
|
220
|
+
terminal_target: currentControl.target,
|
|
221
|
+
agent_timeout_minutes: inactivityTimeoutMinutes,
|
|
222
|
+
agent_hard_timeout_minutes: hardTimeoutMinutes
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
finally {
|
|
226
|
+
releaseStateLock();
|
|
227
|
+
}
|
|
228
|
+
const monitor = startTerminalBridgeMonitorForConversation({
|
|
229
|
+
conversation: renewed,
|
|
230
|
+
statePath,
|
|
231
|
+
logPath,
|
|
232
|
+
options: {
|
|
233
|
+
...options,
|
|
234
|
+
agentTimeoutMinutes: inactivityTimeoutMinutes,
|
|
235
|
+
agentHardTimeoutMinutes: hardTimeoutMinutes
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
if (monitor) {
|
|
239
|
+
appendEvent(logPath, {
|
|
240
|
+
ts: cliNow().toISOString(),
|
|
241
|
+
conversation_id: renewed.conversation_id,
|
|
242
|
+
event: "terminal_bridge_monitor_launch",
|
|
243
|
+
pid: monitor.pid ?? null,
|
|
244
|
+
terminal_control: renewedTerminalControl,
|
|
245
|
+
reason: "renewal",
|
|
246
|
+
agent_timeout_minutes: inactivityTimeoutMinutes,
|
|
247
|
+
agent_hard_timeout_minutes: hardTimeoutMinutes
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
printJson({
|
|
251
|
+
conversation: renewed,
|
|
252
|
+
renewed: true,
|
|
253
|
+
terminal_control: renewedTerminalControl,
|
|
254
|
+
agent_timeout_minutes: inactivityTimeoutMinutes,
|
|
255
|
+
agent_hard_timeout_minutes: hardTimeoutMinutes,
|
|
256
|
+
monitor_pid: monitor?.pid ?? null
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
async function runCancel(options) {
|
|
260
|
+
const terminalConversation = await resolveTerminalConversationFromOptions(options);
|
|
261
|
+
if (terminalConversation) {
|
|
262
|
+
await runTerminalConversationCancel({
|
|
263
|
+
options,
|
|
264
|
+
conversationId: terminalConversation.conversationId,
|
|
265
|
+
agent: terminalConversation.agent,
|
|
266
|
+
terminalControl: terminalConversation.terminalControl,
|
|
267
|
+
pid: terminalConversation.pid
|
|
268
|
+
});
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const loaded = loadConversationFromOptions(options);
|
|
272
|
+
const { statePath, logPath } = loaded;
|
|
273
|
+
const conversation = await migrateLegacyTerminalAgentIdentity({
|
|
274
|
+
...loaded,
|
|
275
|
+
options
|
|
276
|
+
});
|
|
277
|
+
if (!["waiting_for_agent", "waiting_for_openclaw"].includes(conversation.status)) {
|
|
278
|
+
throw new Error(`cannot cancel ${conversation.conversation_id}; conversation is ${conversation.status}`);
|
|
279
|
+
}
|
|
280
|
+
const nativeTakeover = isRecord(conversation.native_session_takeover)
|
|
281
|
+
? conversation.native_session_takeover
|
|
282
|
+
: undefined;
|
|
283
|
+
const terminalControl = terminalControlFromTakeover(nativeTakeover);
|
|
284
|
+
if (terminalControl) {
|
|
285
|
+
await runTerminalControlCancel({
|
|
286
|
+
options,
|
|
287
|
+
statePath,
|
|
288
|
+
logPath,
|
|
289
|
+
agent: executorForConversation(conversation).kind,
|
|
290
|
+
terminalControl
|
|
291
|
+
});
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
throw new Error(`conversation ${conversation.conversation_id} is not attached to a live terminal`);
|
|
295
|
+
}
|
|
296
|
+
async function runTerminalConversationCancel({ options, conversationId, agent, terminalControl, pid }) {
|
|
297
|
+
const storeDir = storeDirFromOptions(options);
|
|
298
|
+
await withCanonicalMutationLocks(terminalWriterMutationLocks(storeDir, terminalControl), async () => {
|
|
299
|
+
assertTerminalHasNoNonterminalDeferredForegroundTransfer({
|
|
300
|
+
storeDir,
|
|
301
|
+
pid,
|
|
302
|
+
terminalControl,
|
|
303
|
+
action: "cancel"
|
|
304
|
+
});
|
|
305
|
+
const cancellation = await createTerminalAgentBridge(options).cancel(agent, terminalControl, {
|
|
306
|
+
runtime: {
|
|
307
|
+
pid,
|
|
308
|
+
cwd: terminalControl.currentPath,
|
|
309
|
+
terminalTarget: terminalControl.target
|
|
310
|
+
},
|
|
311
|
+
scrollbackLines: Number(options.scrollbackLines ?? 120)
|
|
312
|
+
});
|
|
313
|
+
runtimeLog("info", "terminal_cancel_requested", {
|
|
314
|
+
conversation_id: conversationId,
|
|
315
|
+
agent,
|
|
316
|
+
terminal_target: terminalControl.target,
|
|
317
|
+
key: cancellation.key,
|
|
318
|
+
keys: cancellation.keys,
|
|
319
|
+
denied_approval: cancellation.deniedApproval,
|
|
320
|
+
request_id: cancellation.requestId,
|
|
321
|
+
cancel_requested: cancellation.cancelRequested,
|
|
322
|
+
reason: cancellation.reason
|
|
323
|
+
});
|
|
324
|
+
printJson({
|
|
325
|
+
conversation_id: conversationId,
|
|
326
|
+
source: "terminal_control",
|
|
327
|
+
cancel_requested: cancellation.cancelRequested,
|
|
328
|
+
reason: cancellation.reason,
|
|
329
|
+
terminal_control: terminalControl,
|
|
330
|
+
key: cancellation.key,
|
|
331
|
+
keys: cancellation.keys,
|
|
332
|
+
denied_approval: cancellation.deniedApproval,
|
|
333
|
+
request_id: cancellation.requestId
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
async function runTerminalControlCancel({ options, statePath, logPath, agent, terminalControl }) {
|
|
338
|
+
const releaseTerminalLock = acquireTerminalBridgeSendLock(storeDirFromOptions(options), terminalControl, { timeoutMs: 30000 });
|
|
339
|
+
let releaseStateLock;
|
|
340
|
+
try {
|
|
341
|
+
releaseStateLock = acquireFileLock(`${statePath}.lock`);
|
|
342
|
+
const writerStoreDir = pathsForConversationDir(path.dirname(statePath)).storeDir;
|
|
343
|
+
return await withStoreWriterLease(writerStoreDir, async () => {
|
|
344
|
+
const currentConversation = loadState(statePath);
|
|
345
|
+
if (!["waiting_for_agent", "waiting_for_openclaw"].includes(currentConversation.status)) {
|
|
346
|
+
throw new Error(`cannot cancel ${currentConversation.conversation_id}; conversation is ${currentConversation.status}`);
|
|
347
|
+
}
|
|
348
|
+
const currentTakeover = isRecord(currentConversation.native_session_takeover)
|
|
349
|
+
? currentConversation.native_session_takeover
|
|
350
|
+
: undefined;
|
|
351
|
+
const currentControl = terminalControlFromTakeover(currentTakeover);
|
|
352
|
+
if (!currentControl ||
|
|
353
|
+
!terminalControlsShareIncarnation(currentControl, terminalControl)) {
|
|
354
|
+
throw new Error("terminal control changed while waiting to cancel; refresh status and retry");
|
|
355
|
+
}
|
|
356
|
+
assertManagedTerminalDispatchOwner({
|
|
357
|
+
storeDir: writerStoreDir,
|
|
358
|
+
conversation: currentConversation,
|
|
359
|
+
terminalControl: currentControl,
|
|
360
|
+
action: "cancel"
|
|
361
|
+
});
|
|
362
|
+
const cancellation = await createTerminalAgentBridge(options).cancel(agent, currentControl, {
|
|
363
|
+
runtime: terminalRuntimeIdentityForConversation(currentConversation, currentControl),
|
|
364
|
+
scrollbackLines: Number(options.scrollbackLines ?? 120)
|
|
365
|
+
});
|
|
366
|
+
if (!cancellation.cancelRequested) {
|
|
367
|
+
printJson({
|
|
368
|
+
conversation: currentConversation,
|
|
369
|
+
cancel_requested: false,
|
|
370
|
+
reason: cancellation.reason,
|
|
371
|
+
terminal_control: currentControl,
|
|
372
|
+
budget: budgetAction(currentConversation)
|
|
373
|
+
});
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
const now = cliNow().toISOString();
|
|
377
|
+
appendEvent(logPath, {
|
|
378
|
+
ts: now,
|
|
379
|
+
conversation_id: currentConversation.conversation_id,
|
|
380
|
+
event: "terminal_cancel_requested",
|
|
381
|
+
terminal_control: currentControl,
|
|
382
|
+
key: cancellation.key,
|
|
383
|
+
keys: cancellation.keys,
|
|
384
|
+
denied_approval: cancellation.deniedApproval,
|
|
385
|
+
request_id: cancellation.requestId
|
|
386
|
+
});
|
|
387
|
+
runtimeLog("info", "terminal_cancel_requested", {
|
|
388
|
+
conversation_id: currentConversation.conversation_id,
|
|
389
|
+
agent,
|
|
390
|
+
terminal_target: currentControl.target,
|
|
391
|
+
key: cancellation.key,
|
|
392
|
+
keys: cancellation.keys,
|
|
393
|
+
denied_approval: cancellation.deniedApproval,
|
|
394
|
+
request_id: cancellation.requestId
|
|
395
|
+
});
|
|
396
|
+
const nextConversation = {
|
|
397
|
+
...currentConversation,
|
|
398
|
+
status: "cancelled",
|
|
399
|
+
cancelled_at: now,
|
|
400
|
+
terminal_cancel_requested_at: now,
|
|
401
|
+
updated_at: now
|
|
402
|
+
};
|
|
403
|
+
saveState(statePath, nextConversation);
|
|
404
|
+
printJson({
|
|
405
|
+
conversation: nextConversation,
|
|
406
|
+
cancel_requested: true,
|
|
407
|
+
terminal_control: currentControl,
|
|
408
|
+
key: cancellation.key,
|
|
409
|
+
keys: cancellation.keys,
|
|
410
|
+
denied_approval: cancellation.deniedApproval,
|
|
411
|
+
request_id: cancellation.requestId,
|
|
412
|
+
budget: budgetAction(nextConversation)
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
finally {
|
|
417
|
+
try {
|
|
418
|
+
releaseStateLock?.();
|
|
419
|
+
}
|
|
420
|
+
finally {
|
|
421
|
+
releaseTerminalLock();
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
async function runObservedHandoffClose({ options, statePath, logPath, initialConversation }) {
|
|
426
|
+
const expectedToken = required(stringValue(options.expectedHandoffToken), "--expected-handoff-token is required");
|
|
427
|
+
if (stringValue(options.expectedMessageId) ||
|
|
428
|
+
stringValue(options.expectedTransitionId)) {
|
|
429
|
+
throw new Error("--expected-handoff-token cannot be combined with dispatch or lifecycle recovery tokens");
|
|
430
|
+
}
|
|
431
|
+
if (stringValue(options.reason) !== "superseded_by_human_context_switch") {
|
|
432
|
+
throw new Error("a handoff close requires reason superseded_by_human_context_switch");
|
|
433
|
+
}
|
|
434
|
+
const storeDir = pathsForConversationDir(path.dirname(statePath)).storeDir;
|
|
435
|
+
const sourceSessionId = sessionIdForConversation(initialConversation);
|
|
436
|
+
const initialSource = loadManagedSession(storeDir, sourceSessionId);
|
|
437
|
+
if (!initialSource.binding || initialSource.status !== "bound") {
|
|
438
|
+
throw new Error("handoff source Session is no longer bound; refresh list");
|
|
439
|
+
}
|
|
440
|
+
const terminal = await createTerminalAgentBridge(options).resolveStoredTerminal(initialSource.agent, initialSource.binding.native_process.pid, initialSource.binding.terminal_control, { pid: initialSource.binding.native_process.pid });
|
|
441
|
+
await withCanonicalMutationLocks(terminalWriterStateMutationLocks(storeDir, terminal.terminalControl, statePath, logPath), async (scopes, resources) => {
|
|
442
|
+
const conversation = mutationConversationStore.load(scopes, resources);
|
|
443
|
+
const turnId = turnIdForConversation(conversation);
|
|
444
|
+
if (conversation.conversation_id !== initialConversation.conversation_id ||
|
|
445
|
+
sessionIdForConversation(conversation) !== sourceSessionId ||
|
|
446
|
+
!isSessionSendBlockingStatus(conversation.status)) {
|
|
447
|
+
throw new Error("active handoff Turn changed after it was listed; refresh AKK list");
|
|
448
|
+
}
|
|
449
|
+
const source = mutationManagedSessions.load(scopes, resources, sourceSessionId);
|
|
450
|
+
if (source.status !== "bound" ||
|
|
451
|
+
!source.binding ||
|
|
452
|
+
terminalListCliFacade.managedSessionHasUnresolvedNativeTransition(storeDir, source)) {
|
|
453
|
+
throw new Error("handoff source Session changed after it was listed; refresh AKK list");
|
|
454
|
+
}
|
|
455
|
+
const companions = codexAllowedCompanionSetForManagedSession({
|
|
456
|
+
storeDir,
|
|
457
|
+
session: source
|
|
458
|
+
});
|
|
459
|
+
const resolved = await resolveCurrentNativeAgentSessionIdentity({
|
|
460
|
+
options,
|
|
461
|
+
agent: terminal.agent,
|
|
462
|
+
pid: terminal.pid,
|
|
463
|
+
cwd: terminal.terminalControl.currentPath,
|
|
464
|
+
preferredSessionId: companions.primary
|
|
465
|
+
? source.binding.native_thread_id
|
|
466
|
+
: undefined,
|
|
467
|
+
allowedCompanionIdentity: companions.primary,
|
|
468
|
+
allowedAdditionalIdentities: companions.additional
|
|
469
|
+
});
|
|
470
|
+
const observed = await observedExternalHandoffIdentity({
|
|
471
|
+
options,
|
|
472
|
+
terminal,
|
|
473
|
+
sourceSession: source,
|
|
474
|
+
resolvedIdentity: resolved
|
|
475
|
+
});
|
|
476
|
+
if (!observed.identity ||
|
|
477
|
+
managedBindingConflictKindForResolvedTerminal({
|
|
478
|
+
storeDir,
|
|
479
|
+
session: source,
|
|
480
|
+
terminal,
|
|
481
|
+
identity: observed.identity
|
|
482
|
+
}) !== "live_external_thread_change") {
|
|
483
|
+
throw new Error("live native thread no longer matches the listed handoff; refresh AKK list");
|
|
484
|
+
}
|
|
485
|
+
const targetNativeThreadId = observed.identity.sessionId.toLowerCase();
|
|
486
|
+
const target = observedHandoffTargetResolution({
|
|
487
|
+
storeDir,
|
|
488
|
+
agent: terminal.agent,
|
|
489
|
+
workspace: terminal.terminalControl.currentPath ?? cliCwd(),
|
|
490
|
+
nativeThreadId: targetNativeThreadId,
|
|
491
|
+
sourceSessionId
|
|
492
|
+
});
|
|
493
|
+
if (target.status !== "eligible") {
|
|
494
|
+
throw new Error("handoff target Session changed after it was listed; refresh AKK list");
|
|
495
|
+
}
|
|
496
|
+
await nativeThreadLifecycleFacade.assertExclusive({
|
|
497
|
+
options,
|
|
498
|
+
agent: terminal.agent,
|
|
499
|
+
currentPid: terminal.pid,
|
|
500
|
+
nativeThreadId: targetNativeThreadId,
|
|
501
|
+
storeDir,
|
|
502
|
+
terminalControl: terminal.terminalControl,
|
|
503
|
+
excludedManagedSessionId: target.snapshot.state === "detached"
|
|
504
|
+
? target.snapshot.session_id
|
|
505
|
+
: undefined
|
|
506
|
+
});
|
|
507
|
+
const handoffToken = observedHandoffAuthorityToken({
|
|
508
|
+
terminal,
|
|
509
|
+
identity: observed.identity,
|
|
510
|
+
sourceSession: source,
|
|
511
|
+
target: target.snapshot
|
|
512
|
+
});
|
|
513
|
+
const takeover = isRecord(conversation.native_session_takeover)
|
|
514
|
+
? conversation.native_session_takeover
|
|
515
|
+
: undefined;
|
|
516
|
+
const expectedMessageId = stringValue(takeover?.terminal_bridge_message_id) ?? stringValue(terminalBridgeSubmission(conversation)?.message_id);
|
|
517
|
+
const ledger = mutationDispatchLedger.load(scopes, resources);
|
|
518
|
+
const exactDispatchGeneration = Boolean(expectedMessageId &&
|
|
519
|
+
ledger &&
|
|
520
|
+
!terminalDispatchLedgerLooksLifecycle(ledger) &&
|
|
521
|
+
stringValue(ledger.conversation_id) ===
|
|
522
|
+
conversation.conversation_id &&
|
|
523
|
+
stringValue(ledger.session_id) === sourceSessionId &&
|
|
524
|
+
stringValue(ledger.turn_id) === turnId &&
|
|
525
|
+
stringValue(ledger.message_id) === expectedMessageId);
|
|
526
|
+
const exactNoLedgerGeneration = Boolean(!expectedMessageId &&
|
|
527
|
+
(!ledger || ledger.status === "resolved"));
|
|
528
|
+
if (!exactDispatchGeneration && !exactNoLedgerGeneration) {
|
|
529
|
+
throw new Error("active handoff dispatch generation changed; refresh AKK list");
|
|
530
|
+
}
|
|
531
|
+
const freshToken = activeTurnHandoffDecisionToken({
|
|
532
|
+
handoffToken,
|
|
533
|
+
turn: conversation,
|
|
534
|
+
ledger
|
|
535
|
+
});
|
|
536
|
+
if (freshToken !== expectedToken) {
|
|
537
|
+
throw new Error("active handoff snapshot changed; refresh AKK list before closing");
|
|
538
|
+
}
|
|
539
|
+
const now = cliNow().toISOString();
|
|
540
|
+
const closed = {
|
|
541
|
+
...conversation,
|
|
542
|
+
status: "closed",
|
|
543
|
+
closed_at: now,
|
|
544
|
+
close_reason: "superseded_by_human_context_switch",
|
|
545
|
+
disposition: "superseded_by_human_context_switch",
|
|
546
|
+
updated_at: now
|
|
547
|
+
};
|
|
548
|
+
mutationConversationStore.save(scopes, resources, closed);
|
|
549
|
+
const dispatchResolved = expectedMessageId
|
|
550
|
+
? mutationDispatchLedger.resolve(scopes, resources, {
|
|
551
|
+
conversation: closed,
|
|
552
|
+
expectedMessageId,
|
|
553
|
+
reason: "Turn superseded by a verified human context switch"
|
|
554
|
+
})
|
|
555
|
+
: false;
|
|
556
|
+
if (expectedMessageId && !dispatchResolved) {
|
|
557
|
+
throw new Error("active handoff dispatch changed during close; inspect before retrying");
|
|
558
|
+
}
|
|
559
|
+
mutationConversationStore.appendEvent(scopes, resources, {
|
|
560
|
+
ts: now,
|
|
561
|
+
conversation_id: conversation.conversation_id,
|
|
562
|
+
event: "conversation_closed",
|
|
563
|
+
status: "closed",
|
|
564
|
+
reason: "superseded_by_human_context_switch",
|
|
565
|
+
disposition: "superseded_by_human_context_switch",
|
|
566
|
+
handoff_native_thread_id: targetNativeThreadId
|
|
567
|
+
});
|
|
568
|
+
runtimeLog("info", "conversation_closed", {
|
|
569
|
+
conversation_id: conversation.conversation_id,
|
|
570
|
+
status: "closed",
|
|
571
|
+
reason: "superseded_by_human_context_switch",
|
|
572
|
+
disposition: "superseded_by_human_context_switch",
|
|
573
|
+
state_path: statePath,
|
|
574
|
+
event_log_path: logPath
|
|
575
|
+
});
|
|
576
|
+
printJson({
|
|
577
|
+
conversation: closed,
|
|
578
|
+
closed: true,
|
|
579
|
+
terminal_dispatch_resolved: dispatchResolved,
|
|
580
|
+
handoff_disposition: "superseded_by_human_context_switch",
|
|
581
|
+
next_action: "refresh list and use its follow-current send"
|
|
582
|
+
});
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
async function runClose(options) {
|
|
586
|
+
const terminalConversation = await resolveTerminalConversationFromOptions(options);
|
|
587
|
+
if (terminalConversation) {
|
|
588
|
+
if (stringValue(options.expectedHandoffToken)) {
|
|
589
|
+
throw new Error("--expected-handoff-token cannot be used with raw terminal close");
|
|
590
|
+
}
|
|
591
|
+
await runTerminalDispatchClose({
|
|
592
|
+
options,
|
|
593
|
+
terminalConversation
|
|
594
|
+
});
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
const loaded = loadConversationFromOptions(options);
|
|
598
|
+
if (stringValue(options.expectedHandoffToken)) {
|
|
599
|
+
await runObservedHandoffClose({
|
|
600
|
+
options,
|
|
601
|
+
statePath: loaded.statePath,
|
|
602
|
+
logPath: loaded.logPath,
|
|
603
|
+
initialConversation: loaded.conversation
|
|
604
|
+
});
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
if (stringValue(options.reason) === "superseded_by_human_context_switch") {
|
|
608
|
+
throw new Error("reason superseded_by_human_context_switch requires the fresh " +
|
|
609
|
+
"expected_handoff_token advertised by AKK list");
|
|
610
|
+
}
|
|
611
|
+
const { statePath, logPath } = loaded;
|
|
612
|
+
const closeStoreDir = pathsForConversationDir(path.dirname(statePath)).storeDir;
|
|
613
|
+
const nativeTakeover = isRecord(loaded.conversation.native_session_takeover)
|
|
614
|
+
? loaded.conversation.native_session_takeover
|
|
615
|
+
: undefined;
|
|
616
|
+
const terminalControl = terminalControlFromTakeover(nativeTakeover);
|
|
617
|
+
const releaseTerminalLock = terminalControl
|
|
618
|
+
? acquireTerminalBridgeSendLock(closeStoreDir, terminalControl, { timeoutMs: 30000 })
|
|
619
|
+
: () => { };
|
|
620
|
+
const closeWithFreshState = async () => {
|
|
621
|
+
const releaseStateLock = acquireFileLock(`${statePath}.lock`);
|
|
622
|
+
try {
|
|
623
|
+
const conversation = loadState(statePath);
|
|
624
|
+
let verifiedDeadProcess;
|
|
625
|
+
assertConversationHasNoNonterminalDeferredForegroundTransfer({
|
|
626
|
+
storeDir: closeStoreDir,
|
|
627
|
+
conversation,
|
|
628
|
+
action: "close"
|
|
629
|
+
});
|
|
630
|
+
const currentTakeover = isRecord(conversation.native_session_takeover)
|
|
631
|
+
? conversation.native_session_takeover
|
|
632
|
+
: undefined;
|
|
633
|
+
const currentTerminalControl = terminalControlFromTakeover(currentTakeover);
|
|
634
|
+
if (terminalControl &&
|
|
635
|
+
!terminalControlsShareIncarnation(currentTerminalControl, terminalControl)) {
|
|
636
|
+
throw new Error("terminal control changed after the close action was listed; refresh AKK list");
|
|
637
|
+
}
|
|
638
|
+
if (conversation.status === "closed" && currentTerminalControl) {
|
|
639
|
+
const recoveredAuthority = exactVerifiedDeadTerminalAgentProcessAuthority({
|
|
640
|
+
conversation,
|
|
641
|
+
storeDir: closeStoreDir,
|
|
642
|
+
terminalControl: currentTerminalControl,
|
|
643
|
+
logPath
|
|
644
|
+
});
|
|
645
|
+
const recoveredDecision = decideVerifiedDeadAgentProcess({
|
|
646
|
+
persistedAuthority: recoveredAuthority
|
|
647
|
+
});
|
|
648
|
+
if (recoveredDecision.status === "invalid") {
|
|
649
|
+
throw new Error(`cannot finish close recovery for ${conversation.conversation_id}; ` +
|
|
650
|
+
recoveredDecision.reason);
|
|
651
|
+
}
|
|
652
|
+
if (recoveredDecision.status === "verified_dead") {
|
|
653
|
+
const audit = ensureVerifiedDeadTerminalAgentProcessEvent({
|
|
654
|
+
logPath,
|
|
655
|
+
proof: recoveredDecision.proof,
|
|
656
|
+
action: "managed_close"
|
|
657
|
+
});
|
|
658
|
+
const expectedMessageId = required(stringValue(currentTakeover?.terminal_bridge_message_id), "verified-dead close recovery has no terminal message id");
|
|
659
|
+
const dispatchLedgerResolved = resolveVerifiedDeadTerminalBridgeDispatchLedger({
|
|
660
|
+
terminalControl: currentTerminalControl,
|
|
661
|
+
conversation,
|
|
662
|
+
storeDir: closeStoreDir,
|
|
663
|
+
statePath,
|
|
664
|
+
logPath,
|
|
665
|
+
expectedMessageId,
|
|
666
|
+
reason: "conversation explicitly closed by request"
|
|
667
|
+
});
|
|
668
|
+
ensureVerifiedDeadConversationClosedEvent({
|
|
669
|
+
logPath,
|
|
670
|
+
conversation,
|
|
671
|
+
evidenceId: audit.evidenceId
|
|
672
|
+
});
|
|
673
|
+
printJson({
|
|
674
|
+
conversation,
|
|
675
|
+
closed: true,
|
|
676
|
+
recovered: true,
|
|
677
|
+
terminal_dispatch_resolved: dispatchLedgerResolved
|
|
678
|
+
});
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
if (terminalControl && currentTerminalControl) {
|
|
683
|
+
verifiedDeadProcess =
|
|
684
|
+
await assertGenericCloseDoesNotBypassObservedHandoff({
|
|
685
|
+
options,
|
|
686
|
+
storeDir: closeStoreDir,
|
|
687
|
+
conversation,
|
|
688
|
+
terminalControl: currentTerminalControl
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
const now = cliNow().toISOString();
|
|
692
|
+
const closeReason = stringValue(options.reason) ?? "closed by request";
|
|
693
|
+
const verifiedDeadAudit = verifiedDeadProcess
|
|
694
|
+
? ensureVerifiedDeadTerminalAgentProcessEvent({
|
|
695
|
+
logPath,
|
|
696
|
+
proof: verifiedDeadProcess,
|
|
697
|
+
action: "managed_close"
|
|
698
|
+
})
|
|
699
|
+
: undefined;
|
|
700
|
+
const verifiedDeadMessageId = verifiedDeadAudit
|
|
701
|
+
? required(stringValue(currentTakeover?.terminal_bridge_message_id), "verified-dead close has no terminal message id")
|
|
702
|
+
: undefined;
|
|
703
|
+
if (verifiedDeadAudit && currentTerminalControl) {
|
|
704
|
+
assertVerifiedDeadTerminalBridgeDispatchAuthority({
|
|
705
|
+
terminalControl: currentTerminalControl,
|
|
706
|
+
conversation,
|
|
707
|
+
storeDir: closeStoreDir,
|
|
708
|
+
statePath,
|
|
709
|
+
logPath,
|
|
710
|
+
expectedMessageId: verifiedDeadMessageId
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
const closed = {
|
|
714
|
+
...conversation,
|
|
715
|
+
status: "closed",
|
|
716
|
+
closed_at: now,
|
|
717
|
+
close_reason: closeReason,
|
|
718
|
+
...(verifiedDeadAudit
|
|
719
|
+
? {
|
|
720
|
+
terminal_agent_process_disposition: {
|
|
721
|
+
status: "verified_dead",
|
|
722
|
+
proof: verifiedDeadAudit.proof,
|
|
723
|
+
evidence_id: verifiedDeadAudit.evidenceId,
|
|
724
|
+
recorded_at: verifiedDeadAudit.recordedAt
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
: {}),
|
|
728
|
+
updated_at: now
|
|
729
|
+
};
|
|
730
|
+
saveState(statePath, closed);
|
|
731
|
+
if (verifiedDeadAudit &&
|
|
732
|
+
cliEnv().AKK_TEST_EXIT_AFTER_VERIFIED_DEAD_CLOSE_STATE === "1") {
|
|
733
|
+
cliExit(86);
|
|
734
|
+
}
|
|
735
|
+
let dispatchLedgerResolved = false;
|
|
736
|
+
let dispatchLedgerWarning;
|
|
737
|
+
if (currentTerminalControl) {
|
|
738
|
+
try {
|
|
739
|
+
dispatchLedgerResolved = verifiedDeadAudit
|
|
740
|
+
? resolveVerifiedDeadTerminalBridgeDispatchLedger({
|
|
741
|
+
terminalControl: currentTerminalControl,
|
|
742
|
+
conversation: closed,
|
|
743
|
+
storeDir: closeStoreDir,
|
|
744
|
+
statePath,
|
|
745
|
+
logPath,
|
|
746
|
+
expectedMessageId: verifiedDeadMessageId,
|
|
747
|
+
reason: "conversation explicitly closed by request"
|
|
748
|
+
})
|
|
749
|
+
: resolveTerminalBridgeDispatchLedger(currentTerminalControl, {
|
|
750
|
+
conversation: closed,
|
|
751
|
+
expectedMessageId: stringValue(currentTakeover?.terminal_bridge_message_id),
|
|
752
|
+
reason: "conversation explicitly closed by request"
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
catch (error) {
|
|
756
|
+
dispatchLedgerWarning =
|
|
757
|
+
error instanceof Error ? error.message : String(error);
|
|
758
|
+
runtimeLog("error", "terminal_dispatch_ledger_resolve_failed", {
|
|
759
|
+
conversation_id: closed.conversation_id,
|
|
760
|
+
terminal_target: currentTerminalControl.target,
|
|
761
|
+
error: dispatchLedgerWarning
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
if (verifiedDeadAudit) {
|
|
766
|
+
ensureVerifiedDeadConversationClosedEvent({
|
|
767
|
+
logPath,
|
|
768
|
+
conversation: closed,
|
|
769
|
+
evidenceId: verifiedDeadAudit.evidenceId
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
else {
|
|
773
|
+
appendEvent(logPath, {
|
|
774
|
+
ts: now,
|
|
775
|
+
conversation_id: conversation.conversation_id,
|
|
776
|
+
event: "conversation_closed",
|
|
777
|
+
status: "closed",
|
|
778
|
+
reason: closed.close_reason
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
runtimeLog("info", "conversation_closed", {
|
|
782
|
+
conversation_id: conversation.conversation_id,
|
|
783
|
+
status: "closed",
|
|
784
|
+
reason: closed.close_reason,
|
|
785
|
+
state_path: statePath,
|
|
786
|
+
event_log_path: logPath
|
|
787
|
+
});
|
|
788
|
+
printJson({
|
|
789
|
+
conversation: closed,
|
|
790
|
+
closed: true,
|
|
791
|
+
terminal_dispatch_resolved: dispatchLedgerResolved,
|
|
792
|
+
...(dispatchLedgerWarning
|
|
793
|
+
? {
|
|
794
|
+
terminal_dispatch_warning: textSummary(dispatchLedgerWarning),
|
|
795
|
+
do_not_retry: true
|
|
796
|
+
}
|
|
797
|
+
: {})
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
finally {
|
|
801
|
+
releaseStateLock();
|
|
802
|
+
}
|
|
803
|
+
};
|
|
804
|
+
try {
|
|
805
|
+
if (terminalControl) {
|
|
806
|
+
await withStoreWriterLeaseAsync(closeStoreDir, closeWithFreshState);
|
|
807
|
+
}
|
|
808
|
+
else {
|
|
809
|
+
await closeWithFreshState();
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
finally {
|
|
813
|
+
releaseTerminalLock();
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
async function assertGenericCloseDoesNotBypassObservedHandoff({ options, storeDir, conversation, terminalControl }) {
|
|
817
|
+
if (!isSessionSendBlockingStatus(conversation.status)) {
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
const sourceSession = tryLoadManagedSession(storeDir, sessionIdForConversation(conversation));
|
|
821
|
+
if (sourceSession?.status !== "bound" ||
|
|
822
|
+
!sourceSession.binding ||
|
|
823
|
+
!terminalControlsShareIncarnation(sourceSession.binding.terminal_control, terminalControl)) {
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
const storedProof = exactVerifiedDeadTerminalAgentProcessAuthority({
|
|
827
|
+
conversation,
|
|
828
|
+
storeDir,
|
|
829
|
+
terminalControl,
|
|
830
|
+
logPath: stringValue(conversation.event_log_path) ??
|
|
831
|
+
logPathForStatePath(required(stringValue(conversation.state_path), "managed Turn state path is unavailable"))
|
|
832
|
+
});
|
|
833
|
+
const persistedDecision = decideVerifiedDeadAgentProcess({
|
|
834
|
+
persistedAuthority: storedProof
|
|
835
|
+
});
|
|
836
|
+
if (persistedDecision.status === "verified_dead") {
|
|
837
|
+
return persistedDecision.proof;
|
|
838
|
+
}
|
|
839
|
+
if (persistedDecision.status === "invalid") {
|
|
840
|
+
throw new Error(`cannot close ${conversation.conversation_id}; ${persistedDecision.reason}`);
|
|
841
|
+
}
|
|
842
|
+
const takeover = isRecord(conversation.native_session_takeover)
|
|
843
|
+
? conversation.native_session_takeover
|
|
844
|
+
: undefined;
|
|
845
|
+
const pid = Number(takeover?.terminal_agent_pid);
|
|
846
|
+
if (!Number.isSafeInteger(pid) || pid <= 1) {
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
const bridge = createTerminalAgentBridge(options);
|
|
850
|
+
let terminal;
|
|
851
|
+
try {
|
|
852
|
+
terminal = await bridge.resolveStoredTerminal(sourceSession.agent, pid, terminalControl, { pid });
|
|
853
|
+
}
|
|
854
|
+
catch (error) {
|
|
855
|
+
if (error instanceof TerminalControlUnavailableError) {
|
|
856
|
+
// An unavailable terminal cannot prove a live A -> B handoff. Preserve the
|
|
857
|
+
// explicit Store-only close path used to retire unavailable Turn history.
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
const processObservation = await observeBoundTerminalAgentProcess({
|
|
861
|
+
options,
|
|
862
|
+
conversation,
|
|
863
|
+
terminalControl
|
|
864
|
+
});
|
|
865
|
+
const processDecision = decideVerifiedDeadAgentProcess({
|
|
866
|
+
persistedAuthority: { status: "absent" },
|
|
867
|
+
observation: processObservation
|
|
868
|
+
});
|
|
869
|
+
if (processDecision.status === "verified_dead") {
|
|
870
|
+
return processDecision.proof;
|
|
871
|
+
}
|
|
872
|
+
if (processDecision.status === "unverifiable") {
|
|
873
|
+
throw new Error(`cannot close ${conversation.conversation_id}; the bound agent process ` +
|
|
874
|
+
`death is unverifiable: ${processDecision.reason}`);
|
|
875
|
+
}
|
|
876
|
+
throw error;
|
|
877
|
+
}
|
|
878
|
+
const companions = sourceSession.agent === "codex"
|
|
879
|
+
? codexAllowedCompanionSetForManagedSession({
|
|
880
|
+
storeDir,
|
|
881
|
+
session: sourceSession
|
|
882
|
+
})
|
|
883
|
+
: { additional: [] };
|
|
884
|
+
const identityObservation = await observeCurrentNativeAgentSessionIdentity({
|
|
885
|
+
options,
|
|
886
|
+
agent: terminal.agent,
|
|
887
|
+
pid: terminal.pid,
|
|
888
|
+
cwd: terminal.terminalControl.currentPath,
|
|
889
|
+
preferredSessionId: companions.primary
|
|
890
|
+
? sourceSession.binding.native_thread_id
|
|
891
|
+
: undefined,
|
|
892
|
+
allowedCompanionIdentity: companions.primary,
|
|
893
|
+
allowedAdditionalIdentities: companions.additional
|
|
894
|
+
});
|
|
895
|
+
const resolvedIdentity = identityObservation.status === "resolved"
|
|
896
|
+
? identityObservation.identity
|
|
897
|
+
: undefined;
|
|
898
|
+
const observed = await observedExternalHandoffIdentity({
|
|
899
|
+
options,
|
|
900
|
+
terminal,
|
|
901
|
+
sourceSession,
|
|
902
|
+
resolvedIdentity,
|
|
903
|
+
requireSafeTerminal: false
|
|
904
|
+
});
|
|
905
|
+
if (managedBindingConflictKindForResolvedTerminal({
|
|
906
|
+
storeDir,
|
|
907
|
+
session: sourceSession,
|
|
908
|
+
terminal,
|
|
909
|
+
identity: observed.identity
|
|
910
|
+
}) === "live_external_thread_change") {
|
|
911
|
+
throw new Error(`terminal ${terminalControl.target} changed native thread after the ` +
|
|
912
|
+
"generic close action was listed; refresh AKK list and use only its " +
|
|
913
|
+
"fresh snapshot-bound handoff_decision");
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
async function runTerminalDispatchClose({ options, terminalConversation }) {
|
|
917
|
+
const terminalControl = terminalConversation.terminalControl;
|
|
918
|
+
const storeDir = storeDirFromOptions(options);
|
|
919
|
+
return withCanonicalMutationLocks(terminalWriterMutationLocks(storeDir, terminalControl), async (scopes, resources) => {
|
|
920
|
+
let ledger = mutationDispatchLedger.reconcileIncarnation(scopes, resources);
|
|
921
|
+
if (!ledger || ledger.status === "resolved") {
|
|
922
|
+
throw new Error(`terminal ${terminalControl.target} has no unresolved AKK dispatch fence`);
|
|
923
|
+
}
|
|
924
|
+
const deferredTransferId = stringValue(ledger.deferred_foreground_transfer_id);
|
|
925
|
+
if (deferredTransferId) {
|
|
926
|
+
const ledgerStatePath = path.resolve(required(stringValue(ledger.state_path), "deferred terminal dispatch state path is unavailable"));
|
|
927
|
+
const ledgerStoreDir = path.resolve(required(stringValue(ledger.store_dir), "deferred terminal dispatch Store is unavailable"));
|
|
928
|
+
const canonicalLedgerStoreDir = path.resolve(pathsForConversationDir(path.dirname(ledgerStatePath)).storeDir);
|
|
929
|
+
if (ledgerStoreDir !== path.resolve(storeDir) ||
|
|
930
|
+
canonicalLedgerStoreDir !== ledgerStoreDir) {
|
|
931
|
+
throw new Error(`terminal ${terminalControl.target} deferred dispatch belongs to ` +
|
|
932
|
+
"another or noncanonical Store; generic terminal close cannot resolve it");
|
|
933
|
+
}
|
|
934
|
+
const transfer = loadDeferredForegroundTransfer(storeDir, deferredTransferId);
|
|
935
|
+
if (transfer.state_path === undefined ||
|
|
936
|
+
path.resolve(transfer.state_path) !== ledgerStatePath ||
|
|
937
|
+
transfer.terminal_id !== terminalConversation.conversationId ||
|
|
938
|
+
!terminalControlEvidenceMatches(transfer.terminal_endpoint, terminalControl)) {
|
|
939
|
+
throw new Error(`terminal ${terminalControl.target} deferred dispatch authority ` +
|
|
940
|
+
"does not match its exact Store/terminal transfer");
|
|
941
|
+
}
|
|
942
|
+
if (!isFinalDeferredForegroundTransferStatus(transfer.status)) {
|
|
943
|
+
throw new Error(`terminal ${terminalControl.target} dispatch is fenced by deferred ` +
|
|
944
|
+
`foreground transfer ${transfer.transfer_id} (${transfer.status}); ` +
|
|
945
|
+
"generic terminal close cannot resolve it");
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
if (!isRecoverableTerminalDispatchStatus(String(ledger.status))) {
|
|
949
|
+
throw new Error(`terminal ${terminalControl.target} has an invalid dispatch status: ` +
|
|
950
|
+
String(ledger.status));
|
|
951
|
+
}
|
|
952
|
+
if (terminalDispatchLedgerLooksLifecycle(ledger)) {
|
|
953
|
+
const expectedTransitionId = required(stringValue(options.expectedTransitionId), "--expected-transition-id is required to recover a lifecycle transition");
|
|
954
|
+
const currentTransitionId = stringValue(ledger.transition_id);
|
|
955
|
+
if (!currentTransitionId ||
|
|
956
|
+
stringValue(ledger.generation_id) !== currentTransitionId ||
|
|
957
|
+
expectedTransitionId !== currentTransitionId) {
|
|
958
|
+
throw new Error("lifecycle transition identity changed; run AKK list again and use " +
|
|
959
|
+
"the current expected-transition-id");
|
|
960
|
+
}
|
|
961
|
+
ledger = await mutationDispatchLedger.reconcile(scopes, resources, options, terminalConversation, ledger, {
|
|
962
|
+
kind: "manual",
|
|
963
|
+
expectedTransitionId
|
|
964
|
+
});
|
|
965
|
+
if (ledger.status !== "resolved") {
|
|
966
|
+
printJson({
|
|
967
|
+
source: "terminal_control",
|
|
968
|
+
conversation_id: terminalConversation.conversationId,
|
|
969
|
+
terminal_control: terminalControl,
|
|
970
|
+
closed: false,
|
|
971
|
+
terminal_dispatch_resolved: false,
|
|
972
|
+
transition_id: expectedTransitionId,
|
|
973
|
+
previous_dispatch_status: ledger.status,
|
|
974
|
+
blocked: true,
|
|
975
|
+
do_not_retry: true,
|
|
976
|
+
reason: stringValue(ledger.reason) ??
|
|
977
|
+
"the live terminal cannot prove the recorded lifecycle outcome",
|
|
978
|
+
recovery_required: {
|
|
979
|
+
action: "inspect the pane and restore an exact recorded before/after native identity, then rerun the list-provided close action",
|
|
980
|
+
expected_transition_id: expectedTransitionId
|
|
981
|
+
},
|
|
982
|
+
coding_agent_stopped: false,
|
|
983
|
+
tmux_pane_closed: false
|
|
984
|
+
});
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
printJson({
|
|
988
|
+
source: "terminal_control",
|
|
989
|
+
conversation_id: terminalConversation.conversationId,
|
|
990
|
+
terminal_control: terminalControl,
|
|
991
|
+
closed: false,
|
|
992
|
+
terminal_dispatch_resolved: true,
|
|
993
|
+
transition_id: expectedTransitionId,
|
|
994
|
+
reason: stringValue(ledger.reason),
|
|
995
|
+
coding_agent_stopped: false,
|
|
996
|
+
tmux_pane_closed: false
|
|
997
|
+
});
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
const owner = loadTerminalDispatchLedgerOwner(ledger);
|
|
1001
|
+
if (owner) {
|
|
1002
|
+
throw new Error(`terminal ${terminalControl.target} dispatch is owned by AKK ` +
|
|
1003
|
+
`conversation ${owner.conversation_id} (${owner.status}); close that ` +
|
|
1004
|
+
"managed conversation instead");
|
|
1005
|
+
}
|
|
1006
|
+
const expectedMessageId = required(stringValue(options.expectedMessageId), "--expected-message-id is required to resolve an orphaned terminal dispatch");
|
|
1007
|
+
const ownerMessageId = stringValue(ledger.message_id);
|
|
1008
|
+
if (!ownerMessageId || expectedMessageId !== ownerMessageId) {
|
|
1009
|
+
throw new Error("terminal dispatch identity changed; run AKK list again and use the " +
|
|
1010
|
+
"current orphaned dispatch message id");
|
|
1011
|
+
}
|
|
1012
|
+
const resolvedAt = cliNow().toISOString();
|
|
1013
|
+
const reason = stringValue(options.reason) ??
|
|
1014
|
+
"terminal dispatch explicitly resolved after operator inspection";
|
|
1015
|
+
mutationDispatchLedger.save(scopes, resources, {
|
|
1016
|
+
...ledger,
|
|
1017
|
+
status: "resolved",
|
|
1018
|
+
resolved_at: resolvedAt,
|
|
1019
|
+
reason,
|
|
1020
|
+
resolved_by_terminal_conversation_id: terminalConversation.conversationId
|
|
1021
|
+
});
|
|
1022
|
+
runtimeLog("info", "terminal_dispatch_explicitly_resolved", {
|
|
1023
|
+
terminal_target: terminalControl.target,
|
|
1024
|
+
terminal_conversation_id: terminalConversation.conversationId,
|
|
1025
|
+
owner_conversation_id: stringValue(ledger.conversation_id),
|
|
1026
|
+
owner_message_id: stringValue(ledger.message_id),
|
|
1027
|
+
previous_status: ledger.status,
|
|
1028
|
+
reason
|
|
1029
|
+
});
|
|
1030
|
+
printJson({
|
|
1031
|
+
source: "terminal_control",
|
|
1032
|
+
conversation_id: terminalConversation.conversationId,
|
|
1033
|
+
terminal_control: terminalControl,
|
|
1034
|
+
closed: false,
|
|
1035
|
+
terminal_dispatch_resolved: true,
|
|
1036
|
+
previous_dispatch_status: ledger.status,
|
|
1037
|
+
owner_conversation_id: stringValue(ledger.conversation_id),
|
|
1038
|
+
owner_message_id: stringValue(ledger.message_id),
|
|
1039
|
+
reason,
|
|
1040
|
+
coding_agent_stopped: false,
|
|
1041
|
+
tmux_pane_closed: false
|
|
1042
|
+
});
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
const terminalMaintenanceOperations = {
|
|
1046
|
+
runRenew,
|
|
1047
|
+
runCancel,
|
|
1048
|
+
runClose
|
|
1049
|
+
};
|
|
1050
|
+
export function createTerminalMaintenanceCliFacade(dependencies) {
|
|
1051
|
+
const runtime = Object.freeze({ dependencies });
|
|
1052
|
+
const call = (operation) => terminalMaintenanceContext.run(runtime, operation);
|
|
1053
|
+
return Object.freeze({
|
|
1054
|
+
runRenew: (options) => call(() => runRenew(options)),
|
|
1055
|
+
runCancel: (options) => call(() => runCancel(options)),
|
|
1056
|
+
runClose: (options) => call(() => runClose(options))
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
//# sourceMappingURL=terminal-maintenance-cli-adapter.js.map
|