@scotthuang/agent-knock-knock 0.13.2 → 0.13.4
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 +36 -0
- package/README.md +27 -6
- package/dist/src/callback-outbox-policy.js +27 -6
- package/dist/src/callback-outbox-policy.js.map +1 -1
- package/dist/src/callback-outbox-service.d.ts +4 -30
- package/dist/src/callback-outbox-service.js +21 -154
- package/dist/src/callback-outbox-service.js.map +1 -1
- package/dist/src/callback-outbox-settlement.js +13 -8
- package/dist/src/callback-outbox-settlement.js.map +1 -1
- package/dist/src/callback-route-service.d.ts +46 -0
- package/dist/src/callback-route-service.js +155 -0
- package/dist/src/callback-route-service.js.map +1 -0
- package/dist/src/claude-terminal-agent-adapter.js +4 -0
- package/dist/src/claude-terminal-agent-adapter.js.map +1 -1
- package/dist/src/cli-core.js +9 -1
- package/dist/src/cli-core.js.map +1 -1
- package/dist/src/codex-process-incarnation.d.ts +8 -0
- package/dist/src/codex-process-incarnation.js +36 -0
- package/dist/src/codex-process-incarnation.js.map +1 -0
- package/dist/src/codex-terminal-agent-adapter.js +4 -0
- package/dist/src/codex-terminal-agent-adapter.js.map +1 -1
- package/dist/src/deferred-foreground-conflict-policy.d.ts +22 -0
- package/dist/src/deferred-foreground-conflict-policy.js +52 -0
- package/dist/src/deferred-foreground-conflict-policy.js.map +1 -0
- package/dist/src/durable-notification-kernel.d.ts +94 -0
- package/dist/src/durable-notification-kernel.js +68 -0
- package/dist/src/durable-notification-kernel.js.map +1 -0
- package/dist/src/herdr-socket-transport.d.ts +43 -0
- package/dist/src/herdr-socket-transport.js +170 -0
- package/dist/src/herdr-socket-transport.js.map +1 -0
- package/dist/src/herdr-terminal-control-provider.d.ts +2 -45
- package/dist/src/herdr-terminal-control-provider.js +8 -173
- package/dist/src/herdr-terminal-control-provider.js.map +1 -1
- package/dist/src/host-adapter-capabilities.d.ts +35 -0
- package/dist/src/host-adapter-capabilities.js +146 -0
- package/dist/src/host-adapter-capabilities.js.map +1 -0
- package/dist/src/host-adapter.d.ts +9 -3
- package/dist/src/host-adapter.js +33 -9
- package/dist/src/host-adapter.js.map +1 -1
- package/dist/src/host-bridge-tools.d.ts +3 -4
- package/dist/src/host-bridge-tools.js +49 -92
- package/dist/src/host-bridge-tools.js.map +1 -1
- package/dist/src/host-bridge.js +5 -5
- package/dist/src/host-bridge.js.map +1 -1
- package/dist/src/host-monitor-reconciliation.d.ts +6 -0
- package/dist/src/host-monitor-reconciliation.js +96 -0
- package/dist/src/host-monitor-reconciliation.js.map +1 -0
- package/dist/src/managed-session.js +2 -18
- package/dist/src/managed-session.js.map +1 -1
- package/dist/src/native-thread-lifecycle-cli-adapter.d.ts +13 -0
- package/dist/src/native-thread-lifecycle-cli-adapter.js +635 -3
- package/dist/src/native-thread-lifecycle-cli-adapter.js.map +1 -1
- package/dist/src/openclaw-list-projection.d.ts +2 -0
- package/dist/src/openclaw-list-projection.js +3 -0
- package/dist/src/openclaw-list-projection.js.map +1 -0
- package/dist/src/openclaw-model-facing-field-policy.d.ts +2 -0
- package/dist/src/openclaw-model-facing-field-policy.js +3 -0
- package/dist/src/openclaw-model-facing-field-policy.js.map +1 -0
- package/dist/src/openclaw-plugin-command-adapter.d.ts +6 -29
- package/dist/src/openclaw-plugin-command-adapter.js +7 -2370
- package/dist/src/openclaw-plugin-command-adapter.js.map +1 -1
- package/dist/src/openclaw-plugin-helpers.d.ts +2 -121
- package/dist/src/openclaw-plugin-helpers.js +2 -1164
- package/dist/src/openclaw-plugin-helpers.js.map +1 -1
- package/dist/src/openclaw-plugin-schemas.d.ts +2 -510
- package/dist/src/openclaw-plugin-schemas.js +2 -519
- package/dist/src/openclaw-plugin-schemas.js.map +1 -1
- package/dist/src/openclaw-plugin-supervisor.d.ts +4 -5
- package/dist/src/openclaw-plugin-supervisor.js +5 -94
- package/dist/src/openclaw-plugin-supervisor.js.map +1 -1
- package/dist/src/openclaw-plugin-value-helpers.d.ts +2 -0
- package/dist/src/openclaw-plugin-value-helpers.js +3 -0
- package/dist/src/openclaw-plugin-value-helpers.js.map +1 -0
- package/dist/src/openclaw-private-authority-offers.d.ts +4 -39
- package/dist/src/openclaw-private-authority-offers.js +3 -258
- package/dist/src/openclaw-private-authority-offers.js.map +1 -1
- package/dist/src/semantic-private-authority-offers.d.ts +41 -0
- package/dist/src/semantic-private-authority-offers.js +261 -0
- package/dist/src/semantic-private-authority-offers.js.map +1 -0
- package/dist/src/semantic-tool-arguments.d.ts +9 -0
- package/dist/src/semantic-tool-arguments.js +64 -0
- package/dist/src/semantic-tool-arguments.js.map +1 -0
- package/dist/src/semantic-tool-catalog.d.ts +48 -0
- package/dist/src/semantic-tool-catalog.js +105 -0
- package/dist/src/semantic-tool-catalog.js.map +1 -0
- package/dist/src/semantic-tool-command-helpers.d.ts +145 -0
- package/dist/src/semantic-tool-command-helpers.js +1272 -0
- package/dist/src/semantic-tool-command-helpers.js.map +1 -0
- package/dist/src/semantic-tool-list-projection.d.ts +10 -0
- package/dist/src/semantic-tool-list-projection.js +497 -0
- package/dist/src/semantic-tool-list-projection.js.map +1 -0
- package/dist/src/semantic-tool-model-facing-field-policy.d.ts +3 -0
- package/dist/src/semantic-tool-model-facing-field-policy.js +66 -0
- package/dist/src/semantic-tool-model-facing-field-policy.js.map +1 -0
- package/dist/src/semantic-tool-presentation.d.ts +37 -0
- package/dist/src/semantic-tool-presentation.js +680 -0
- package/dist/src/semantic-tool-presentation.js.map +1 -0
- package/dist/src/semantic-tool-private-authority.d.ts +39 -0
- package/dist/src/semantic-tool-private-authority.js +602 -0
- package/dist/src/semantic-tool-private-authority.js.map +1 -0
- package/dist/src/semantic-tool-relay.d.ts +16 -0
- package/dist/src/semantic-tool-relay.js +175 -0
- package/dist/src/semantic-tool-relay.js.map +1 -0
- package/dist/src/semantic-tool-runtime.d.ts +10 -0
- package/dist/src/semantic-tool-runtime.js +1157 -0
- package/dist/src/semantic-tool-runtime.js.map +1 -0
- package/dist/src/semantic-tool-schemas.d.ts +563 -0
- package/dist/src/semantic-tool-schemas.js +573 -0
- package/dist/src/semantic-tool-schemas.js.map +1 -0
- package/dist/src/semantic-tool-value-helpers.d.ts +5 -0
- package/dist/src/semantic-tool-value-helpers.js +27 -0
- package/dist/src/semantic-tool-value-helpers.js.map +1 -0
- package/dist/src/store-event-presentation.d.ts +17 -0
- package/dist/src/store-event-presentation.js +33 -0
- package/dist/src/store-event-presentation.js.map +1 -0
- package/dist/src/store.d.ts +3 -16
- package/dist/src/store.js +2 -30
- package/dist/src/store.js.map +1 -1
- package/dist/src/terminal-action-projection.d.ts +3 -17
- package/dist/src/terminal-action-projection.js +13 -41
- package/dist/src/terminal-action-projection.js.map +1 -1
- package/dist/src/terminal-agent-adapter.d.ts +7 -0
- package/dist/src/terminal-agent-adapter.js +8 -0
- package/dist/src/terminal-agent-adapter.js.map +1 -1
- package/dist/src/terminal-agent-bridge.d.ts +35 -332
- package/dist/src/terminal-agent-bridge.js +293 -2798
- package/dist/src/terminal-agent-bridge.js.map +1 -1
- package/dist/src/terminal-approval-cli-adapter.d.ts +17 -0
- package/dist/src/terminal-approval-cli-adapter.js +921 -0
- package/dist/src/terminal-approval-cli-adapter.js.map +1 -0
- package/dist/src/terminal-command-cli-adapter.d.ts +2 -429
- package/dist/src/terminal-command-cli-adapter.js +344 -6061
- package/dist/src/terminal-command-cli-adapter.js.map +1 -1
- package/dist/src/terminal-command-cli-ports.d.ts +443 -0
- package/dist/src/terminal-command-cli-ports.js +2 -0
- package/dist/src/terminal-command-cli-ports.js.map +1 -0
- package/dist/src/terminal-command-dispatch-preparation.d.ts +81 -0
- package/dist/src/terminal-command-dispatch-preparation.js +209 -0
- package/dist/src/terminal-command-dispatch-preparation.js.map +1 -0
- package/dist/src/terminal-command-dispatch-transport.d.ts +35 -0
- package/dist/src/terminal-command-dispatch-transport.js +859 -0
- package/dist/src/terminal-command-dispatch-transport.js.map +1 -0
- package/dist/src/terminal-command-foreground-proof.d.ts +32 -0
- package/dist/src/terminal-command-foreground-proof.js +72 -0
- package/dist/src/terminal-command-foreground-proof.js.map +1 -0
- package/dist/src/terminal-command-send-preflight.d.ts +41 -0
- package/dist/src/terminal-command-send-preflight.js +86 -0
- package/dist/src/terminal-command-send-preflight.js.map +1 -0
- package/dist/src/terminal-composer-classifier.d.ts +55 -0
- package/dist/src/terminal-composer-classifier.js +638 -0
- package/dist/src/terminal-composer-classifier.js.map +1 -0
- package/dist/src/terminal-control-ref.d.ts +23 -0
- package/dist/src/terminal-control-ref.js +32 -0
- package/dist/src/terminal-control-ref.js.map +1 -1
- package/dist/src/terminal-dispatch-recovery-authority.d.ts +56 -0
- package/dist/src/terminal-dispatch-recovery-authority.js +251 -0
- package/dist/src/terminal-dispatch-recovery-authority.js.map +1 -0
- package/dist/src/terminal-dispatch-recovery-cli-adapter.d.ts +1 -9
- package/dist/src/terminal-dispatch-recovery-cli-adapter.js +5 -240
- package/dist/src/terminal-dispatch-recovery-cli-adapter.js.map +1 -1
- package/dist/src/terminal-handoff-cli-adapter.d.ts +1 -11
- package/dist/src/terminal-handoff-cli-adapter.js +2 -39
- package/dist/src/terminal-handoff-cli-adapter.js.map +1 -1
- package/dist/src/terminal-human-explicit-send-cli-adapter.d.ts +25 -0
- package/dist/src/terminal-human-explicit-send-cli-adapter.js +903 -0
- package/dist/src/terminal-human-explicit-send-cli-adapter.js.map +1 -0
- package/dist/src/terminal-identity-authority-cli-adapter.d.ts +1 -6
- package/dist/src/terminal-identity-authority-cli-adapter.js +3 -33
- package/dist/src/terminal-identity-authority-cli-adapter.js.map +1 -1
- package/dist/src/terminal-interaction-cli-adapter.js +209 -210
- package/dist/src/terminal-interaction-cli-adapter.js.map +1 -1
- package/dist/src/terminal-interaction-core.d.ts +0 -2
- package/dist/src/terminal-interaction-core.js +4 -3
- package/dist/src/terminal-interaction-core.js.map +1 -1
- package/dist/src/terminal-interaction-protocol.d.ts +21 -0
- package/dist/src/terminal-interaction-protocol.js +74 -0
- package/dist/src/terminal-interaction-protocol.js.map +1 -1
- package/dist/src/terminal-interaction-response-bridge.d.ts +126 -0
- package/dist/src/terminal-interaction-response-bridge.js +579 -0
- package/dist/src/terminal-interaction-response-bridge.js.map +1 -0
- package/dist/src/terminal-interaction-response-transaction.d.ts +44 -0
- package/dist/src/terminal-interaction-response-transaction.js +56 -0
- package/dist/src/terminal-interaction-response-transaction.js.map +1 -0
- package/dist/src/terminal-list-action-policy.d.ts +43 -0
- package/dist/src/terminal-list-action-policy.js +164 -0
- package/dist/src/terminal-list-action-policy.js.map +1 -0
- package/dist/src/terminal-list-cli-adapter.d.ts +6 -36
- package/dist/src/terminal-list-cli-adapter.js +314 -1711
- package/dist/src/terminal-list-cli-adapter.js.map +1 -1
- package/dist/src/terminal-list-facts.d.ts +129 -0
- package/dist/src/terminal-list-facts.js +179 -0
- package/dist/src/terminal-list-facts.js.map +1 -0
- package/dist/src/terminal-list-inventory.d.ts +59 -0
- package/dist/src/terminal-list-inventory.js +194 -0
- package/dist/src/terminal-list-inventory.js.map +1 -0
- package/dist/src/terminal-list-ownership-service.d.ts +118 -0
- package/dist/src/terminal-list-ownership-service.js +1003 -0
- package/dist/src/terminal-list-ownership-service.js.map +1 -0
- package/dist/src/terminal-list-projection-service.d.ts +49 -0
- package/dist/src/terminal-list-projection-service.js +407 -0
- package/dist/src/terminal-list-projection-service.js.map +1 -0
- package/dist/src/terminal-list-renderer.d.ts +12 -11
- package/dist/src/terminal-list-renderer.js +237 -104
- package/dist/src/terminal-list-renderer.js.map +1 -1
- package/dist/src/terminal-managed-send-cli-adapter.d.ts +33 -0
- package/dist/src/terminal-managed-send-cli-adapter.js +994 -0
- package/dist/src/terminal-managed-send-cli-adapter.js.map +1 -0
- package/dist/src/terminal-managed-turn-list-action-policy.d.ts +47 -0
- package/dist/src/terminal-managed-turn-list-action-policy.js +64 -0
- package/dist/src/terminal-managed-turn-list-action-policy.js.map +1 -0
- package/dist/src/terminal-model-control-action-policy.d.ts +60 -0
- package/dist/src/terminal-model-control-action-policy.js +96 -0
- package/dist/src/terminal-model-control-action-policy.js.map +1 -0
- package/dist/src/terminal-model-control-availability.d.ts +68 -0
- package/dist/src/terminal-model-control-availability.js +157 -0
- package/dist/src/terminal-model-control-availability.js.map +1 -0
- package/dist/src/terminal-model-control-bridge.d.ts +48 -0
- package/dist/src/terminal-model-control-bridge.js +259 -0
- package/dist/src/terminal-model-control-bridge.js.map +1 -0
- package/dist/src/terminal-model-control-profile.d.ts +55 -0
- package/dist/src/terminal-model-control-profile.js +137 -0
- package/dist/src/terminal-model-control-profile.js.map +1 -0
- package/dist/src/terminal-model-control-subject.d.ts +34 -0
- package/dist/src/terminal-model-control-subject.js +58 -0
- package/dist/src/terminal-model-control-subject.js.map +1 -0
- package/dist/src/terminal-model-control-transaction.d.ts +19 -0
- package/dist/src/terminal-model-control-transaction.js +39 -0
- package/dist/src/terminal-model-control-transaction.js.map +1 -0
- package/dist/src/terminal-model-control.d.ts +331 -0
- package/dist/src/terminal-model-control.js +1686 -0
- package/dist/src/terminal-model-control.js.map +1 -0
- package/dist/src/terminal-monitor-application-service.js +50 -17
- package/dist/src/terminal-monitor-application-service.js.map +1 -1
- package/dist/src/terminal-native-inspection-bridge.d.ts +211 -0
- package/dist/src/terminal-native-inspection-bridge.js +1127 -0
- package/dist/src/terminal-native-inspection-bridge.js.map +1 -0
- package/dist/src/terminal-runtime-cli-adapter.d.ts +2 -0
- package/dist/src/terminal-runtime-cli-adapter.js +59 -17
- package/dist/src/terminal-runtime-cli-adapter.js.map +1 -1
- package/dist/src/terminal-submission-retry-application.d.ts +24 -0
- package/dist/src/terminal-submission-retry-application.js +402 -0
- package/dist/src/terminal-submission-retry-application.js.map +1 -0
- package/dist/src/terminal-submission-retry-ports.d.ts +30 -0
- package/dist/src/terminal-submission-retry-ports.js +2 -0
- package/dist/src/terminal-submission-retry-ports.js.map +1 -0
- package/dist/src/terminal-submission-retry-reconciliation.d.ts +236 -0
- package/dist/src/terminal-submission-retry-reconciliation.js +1130 -0
- package/dist/src/terminal-submission-retry-reconciliation.js.map +1 -0
- package/dist/src/terminal-submission-retry-transport.d.ts +58 -0
- package/dist/src/terminal-submission-retry-transport.js +675 -0
- package/dist/src/terminal-submission-retry-transport.js.map +1 -0
- package/dist/src/terminal-text-submission-bridge.d.ts +200 -0
- package/dist/src/terminal-text-submission-bridge.js +632 -0
- package/dist/src/terminal-text-submission-bridge.js.map +1 -0
- package/dist/src/terminal-user-explicit-send-policy.d.ts +29 -0
- package/dist/src/terminal-user-explicit-send-policy.js +46 -0
- package/dist/src/terminal-user-explicit-send-policy.js.map +1 -0
- package/dist/src/terminal-watch-cli-adapter.js +91 -143
- package/dist/src/terminal-watch-cli-adapter.js.map +1 -1
- package/dist/src/terminal-watch-codec.d.ts +9 -0
- package/dist/src/terminal-watch-codec.js +1051 -0
- package/dist/src/terminal-watch-codec.js.map +1 -0
- package/dist/src/terminal-watch-interaction-response-store.d.ts +19 -0
- package/dist/src/terminal-watch-interaction-response-store.js +87 -0
- package/dist/src/terminal-watch-interaction-response-store.js.map +1 -0
- package/dist/src/terminal-watch-record.d.ts +319 -0
- package/dist/src/terminal-watch-record.js +742 -0
- package/dist/src/terminal-watch-record.js.map +1 -0
- package/dist/src/terminal-watch-service.js +48 -27
- package/dist/src/terminal-watch-service.js.map +1 -1
- package/dist/src/terminal-watch-store.d.ts +3 -279
- package/dist/src/terminal-watch-store.js +5 -1779
- package/dist/src/terminal-watch-store.js.map +1 -1
- package/docs/quickstart-herdr.md +1 -1
- package/docs/quickstart-tmux.md +27 -2
- package/openclaw.plugin.json +13 -1
- package/package.json +7 -2
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +54 -11
|
@@ -0,0 +1,921 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { evaluateApprovalPolicy } from "./approval-policy.js";
|
|
5
|
+
import { executorForConversation, sessionIdForConversation, turnIdForConversation } from "./protocol.js";
|
|
6
|
+
import { appendEvent, loadState, pathsForConversationDir, saveState, withStoreWriterLeaseAsync } from "./store.js";
|
|
7
|
+
import { isTerminalApprovalDecision } from "./terminal-agent-adapter.js";
|
|
8
|
+
import { terminalControlsShareIncarnation } from "./terminal-authority-policy.js";
|
|
9
|
+
import { terminalScopedCodexApprovalPromptSnapshot } from "./terminal-scoped-approval-authority.js";
|
|
10
|
+
import { claudeTranscriptApprovalIdentity, terminalMonitorDeadlineAt as deadlineAt, validTerminalMonitorTimestampMs as validTimestampMs } from "./terminal-monitor-decision-policy.js";
|
|
11
|
+
import * as monitorOwner from "./terminal-monitor-ownership-policy.js";
|
|
12
|
+
import { sameCanonicalStatePath } from "./terminal-dispatch-ledger-codec.js";
|
|
13
|
+
import { isRecord, nonBlankString as stringValue } from "./value-guards.js";
|
|
14
|
+
const approvalContext = new AsyncLocalStorage();
|
|
15
|
+
function approvalRuntime() {
|
|
16
|
+
const runtime = approvalContext.getStore();
|
|
17
|
+
if (!runtime) {
|
|
18
|
+
throw new Error("Terminal approval runtime is unavailable");
|
|
19
|
+
}
|
|
20
|
+
return runtime;
|
|
21
|
+
}
|
|
22
|
+
function rawPort(name) {
|
|
23
|
+
return ((...arguments_) => {
|
|
24
|
+
const operation = approvalRuntime().ports[name];
|
|
25
|
+
return operation(...arguments_);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
const acquireFileLock = rawPort("acquireFileLock");
|
|
29
|
+
const acquireTerminalBridgeSendLock = rawPort("acquireTerminalBridgeSendLock");
|
|
30
|
+
const assertExpectedHandoffTokenUsesExactTerminalSelector = rawPort("assertExpectedHandoffTokenUsesExactTerminalSelector");
|
|
31
|
+
const assertManagedTerminalDispatchOwner = rawPort("assertManagedTerminalDispatchOwner");
|
|
32
|
+
const createTerminalAgentBridge = rawPort("createTerminalAgentBridge");
|
|
33
|
+
const ensureTerminalBridgeMonitorAfterApproval = rawPort("ensureTerminalBridgeMonitorAfterApproval");
|
|
34
|
+
const loadConversationFromOptions = rawPort("loadConversationFromOptions");
|
|
35
|
+
const migrateLegacyTerminalAgentIdentity = rawPort("migrateLegacyTerminalAgentIdentity");
|
|
36
|
+
const parseJsonOption = rawPort("parseJsonOption");
|
|
37
|
+
const positiveMinutes = rawPort("positiveMinutes");
|
|
38
|
+
const resolveTerminalConversationFromOptions = rawPort("resolveTerminalConversationFromOptions");
|
|
39
|
+
const storeDirFromOptions = rawPort("storeDirFromOptions");
|
|
40
|
+
const terminalBridgeEnabled = rawPort("terminalBridgeEnabled");
|
|
41
|
+
const terminalControlFromTakeover = rawPort("terminalControlFromTakeover");
|
|
42
|
+
const terminalDurableRequestForConversation = rawPort("terminalDurableRequestForConversation");
|
|
43
|
+
const terminalRuntimeIdentityForConversation = rawPort("terminalRuntimeIdentityForConversation");
|
|
44
|
+
const terminalListCliFacade = new Proxy({}, {
|
|
45
|
+
get(_target, property) {
|
|
46
|
+
const operation = approvalRuntime().ports.terminalList[property];
|
|
47
|
+
return typeof operation === "function"
|
|
48
|
+
? operation.bind(approvalRuntime().ports.terminalList)
|
|
49
|
+
: operation;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
const cliNow = () => approvalRuntime().runtime.now();
|
|
53
|
+
const cliNowMs = () => approvalRuntime().runtime.nowMs();
|
|
54
|
+
const runtimeLog = (level, event, fields) => approvalRuntime().runtime.log(level, event, fields);
|
|
55
|
+
const printJson = (value) => approvalRuntime().runtime.printJson(value);
|
|
56
|
+
async function runApproveInContext(options) {
|
|
57
|
+
const decision = terminalApprovalDecisionFromOptions(options);
|
|
58
|
+
const terminalConversation = await resolveTerminalConversationFromOptions(options);
|
|
59
|
+
if (terminalConversation) {
|
|
60
|
+
assertExpectedHandoffTokenUsesExactTerminalSelector({
|
|
61
|
+
options,
|
|
62
|
+
terminal: terminalConversation
|
|
63
|
+
});
|
|
64
|
+
if (options.autoApproved === true) {
|
|
65
|
+
throw new Error("automatic approval requires an exact managed Turn and cannot use a raw terminal selector");
|
|
66
|
+
}
|
|
67
|
+
if (decision !== "approve_once") {
|
|
68
|
+
throw new Error("terminal-scoped approval supports approve_once only; use an exact managed Turn to reject");
|
|
69
|
+
}
|
|
70
|
+
await runTerminalConversationApprove({
|
|
71
|
+
options,
|
|
72
|
+
terminal: terminalConversation
|
|
73
|
+
});
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const loaded = loadConversationFromOptions(options);
|
|
77
|
+
const { statePath, logPath } = loaded;
|
|
78
|
+
assertAutoApprovalCallbackRoute({
|
|
79
|
+
options,
|
|
80
|
+
conversation: loaded.conversation,
|
|
81
|
+
statePath
|
|
82
|
+
});
|
|
83
|
+
const conversation = await migrateLegacyTerminalAgentIdentity({
|
|
84
|
+
...loaded,
|
|
85
|
+
options
|
|
86
|
+
});
|
|
87
|
+
const nativeTakeover = isRecord(conversation.native_session_takeover)
|
|
88
|
+
? conversation.native_session_takeover
|
|
89
|
+
: undefined;
|
|
90
|
+
const terminalControl = terminalControlFromTakeover(nativeTakeover);
|
|
91
|
+
if (!terminalControl) {
|
|
92
|
+
throw new Error(`conversation ${conversation.conversation_id} is not controlled through a terminal`);
|
|
93
|
+
}
|
|
94
|
+
const autoApproved = options.autoApproved === true;
|
|
95
|
+
if (autoApproved && decision !== "approve_once") {
|
|
96
|
+
throw new Error("automatic approval can only dispatch approve_once");
|
|
97
|
+
}
|
|
98
|
+
const callbackAuthority = autoApprovalCallbackAuthorityFromOptions(options);
|
|
99
|
+
if (!["waiting_for_agent", "waiting_for_openclaw"].includes(conversation.status) &&
|
|
100
|
+
!(autoApproved && callbackAuthority)) {
|
|
101
|
+
throw new Error(`cannot approve ${conversation.conversation_id}; conversation is ${conversation.status}`);
|
|
102
|
+
}
|
|
103
|
+
const executor = executorForConversation(conversation);
|
|
104
|
+
const monitoredApproval = isRecord(nativeTakeover?.["terminal_bridge_approval"])
|
|
105
|
+
? nativeTakeover.terminal_bridge_approval
|
|
106
|
+
: undefined;
|
|
107
|
+
const suppliedExpectedFingerprint = stringValue(options.expectedApprovalFingerprint);
|
|
108
|
+
const expectedFingerprint = suppliedExpectedFingerprint ??
|
|
109
|
+
monitoredApprovalFingerprint(monitoredApproval, decision);
|
|
110
|
+
const claudeScreenApproval = executor.kind === "claude";
|
|
111
|
+
if (claudeScreenApproval) {
|
|
112
|
+
const monitoredState = isRecord(monitoredApproval?.approval_state)
|
|
113
|
+
? monitoredApproval.approval_state
|
|
114
|
+
: undefined;
|
|
115
|
+
const pendingDispatch = isRecord(nativeTakeover?.terminal_bridge_approval_dispatch)
|
|
116
|
+
? nativeTakeover.terminal_bridge_approval_dispatch
|
|
117
|
+
: undefined;
|
|
118
|
+
const lastApprovalFingerprint = stringValue(nativeTakeover?.terminal_bridge_last_approval_fingerprint);
|
|
119
|
+
const lastApprovalMessageId = stringValue(nativeTakeover?.terminal_bridge_last_approval_message_id);
|
|
120
|
+
const currentMessageId = stringValue(nativeTakeover?.terminal_bridge_message_id);
|
|
121
|
+
const approvalResolvedAt = validTimestampMs(nativeTakeover?.terminal_bridge_approval_resolved_at);
|
|
122
|
+
if (autoApproved &&
|
|
123
|
+
callbackAuthority === undefined &&
|
|
124
|
+
monitoredApproval === undefined &&
|
|
125
|
+
pendingDispatch === undefined &&
|
|
126
|
+
conversation.status === "waiting_for_agent" &&
|
|
127
|
+
suppliedExpectedFingerprint !== undefined &&
|
|
128
|
+
suppliedExpectedFingerprint === lastApprovalFingerprint &&
|
|
129
|
+
lastApprovalMessageId !== undefined &&
|
|
130
|
+
lastApprovalMessageId === currentMessageId &&
|
|
131
|
+
approvalResolvedAt !== undefined) {
|
|
132
|
+
const monitor = ensureTerminalBridgeMonitorAfterApproval({
|
|
133
|
+
conversation,
|
|
134
|
+
statePath,
|
|
135
|
+
logPath,
|
|
136
|
+
terminalControl,
|
|
137
|
+
options,
|
|
138
|
+
reason: "approval_already_resolved"
|
|
139
|
+
});
|
|
140
|
+
printJson({
|
|
141
|
+
conversation,
|
|
142
|
+
approved: false,
|
|
143
|
+
already_approved: true,
|
|
144
|
+
blocked: false,
|
|
145
|
+
reason: "Claude screen approval fingerprint was already consumed",
|
|
146
|
+
terminal_control: terminalControl,
|
|
147
|
+
monitor_pid: monitor.monitorPid ?? null,
|
|
148
|
+
monitor_handoff_pid: monitor.handoffWatchdog?.pid ?? null
|
|
149
|
+
});
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
// A provenance-bound callback whose approval is already absent must reach
|
|
153
|
+
// the terminal+state lock. Only the persisted callback message and the
|
|
154
|
+
// locked consumed-approval receipt may classify it as an idempotent replay.
|
|
155
|
+
if (callbackAuthority === undefined || monitoredApproval !== undefined) {
|
|
156
|
+
const notifiedAt = validTimestampMs(monitoredApproval?.notified_at);
|
|
157
|
+
if (conversation.status !== "waiting_for_openclaw" ||
|
|
158
|
+
monitoredState?.decision_mode !== "keys" ||
|
|
159
|
+
!stringValue(monitoredApproval?.fingerprint)) {
|
|
160
|
+
printJson({
|
|
161
|
+
conversation,
|
|
162
|
+
approved: false,
|
|
163
|
+
blocked: true,
|
|
164
|
+
reason: "Claude screen approval requires a current managed-turn approval notification",
|
|
165
|
+
terminal_control: terminalControl
|
|
166
|
+
});
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (notifiedAt === undefined ||
|
|
170
|
+
cliNowMs() - notifiedAt > approvalRuntime().defaults.claudeScreenApprovalTtlMs) {
|
|
171
|
+
printJson({
|
|
172
|
+
conversation,
|
|
173
|
+
approved: false,
|
|
174
|
+
blocked: true,
|
|
175
|
+
reason: "Claude screen approval expired; inspect and resolve the terminal manually",
|
|
176
|
+
terminal_control: terminalControl
|
|
177
|
+
});
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (!suppliedExpectedFingerprint ||
|
|
181
|
+
expectedFingerprint !== monitoredApprovalFingerprint(monitoredApproval, decision)) {
|
|
182
|
+
printJson({
|
|
183
|
+
conversation,
|
|
184
|
+
approved: false,
|
|
185
|
+
blocked: true,
|
|
186
|
+
reason: "Claude screen approval requires the latest notified fingerprint",
|
|
187
|
+
terminal_control: terminalControl
|
|
188
|
+
});
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (pendingDispatch?.state === "reserved" &&
|
|
192
|
+
pendingDispatch.terminal_bridge_message_id ===
|
|
193
|
+
nativeTakeover?.terminal_bridge_message_id) {
|
|
194
|
+
printJson({
|
|
195
|
+
conversation,
|
|
196
|
+
approved: false,
|
|
197
|
+
blocked: true,
|
|
198
|
+
reason: "a previous Claude approval dispatch has an uncertain outcome; inspect and resolve the terminal manually",
|
|
199
|
+
terminal_control: terminalControl
|
|
200
|
+
});
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (expectedFingerprint ===
|
|
204
|
+
stringValue(nativeTakeover?.terminal_bridge_last_approval_fingerprint)) {
|
|
205
|
+
printJson({
|
|
206
|
+
conversation,
|
|
207
|
+
approved: false,
|
|
208
|
+
blocked: true,
|
|
209
|
+
reason: "Claude screen approval fingerprint was already consumed",
|
|
210
|
+
terminal_control: terminalControl
|
|
211
|
+
});
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return runManagedApprovalDispatch({
|
|
217
|
+
options, conversation, statePath, logPath,
|
|
218
|
+
nativeTakeover: nativeTakeover,
|
|
219
|
+
terminalControl, executor, monitoredApproval, expectedFingerprint,
|
|
220
|
+
autoApproved, claudeScreenApproval, decision
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
function terminalApprovalDecisionFromOptions(options) {
|
|
224
|
+
const value = options.decision ?? "approve_once";
|
|
225
|
+
if (!isTerminalApprovalDecision(value)) {
|
|
226
|
+
throw new Error("--decision must be one of: approve_once, reject");
|
|
227
|
+
}
|
|
228
|
+
return value;
|
|
229
|
+
}
|
|
230
|
+
function monitoredApprovalFingerprint(approval, decision) {
|
|
231
|
+
if (decision === "approve_once") {
|
|
232
|
+
return stringValue(approval?.fingerprint);
|
|
233
|
+
}
|
|
234
|
+
const state = isRecord(approval?.approval_state)
|
|
235
|
+
? approval.approval_state
|
|
236
|
+
: undefined;
|
|
237
|
+
const choices = Array.isArray(state?.choices) ? state.choices : [];
|
|
238
|
+
const choice = choices.find((candidate) => isRecord(candidate) && candidate.decision === decision);
|
|
239
|
+
return isRecord(choice) ? stringValue(choice.fingerprint) : undefined;
|
|
240
|
+
}
|
|
241
|
+
function approvalPolicyCandidateForInspection({ agent, currentTerminalControl, inspection, fingerprint }) {
|
|
242
|
+
const evidence = inspection.approval.approvable
|
|
243
|
+
? inspection.approval.policyEvidence
|
|
244
|
+
: undefined;
|
|
245
|
+
return {
|
|
246
|
+
agent,
|
|
247
|
+
kind: evidence?.kind ?? inspection.approval.promptKind ?? "unknown",
|
|
248
|
+
decisionMode: inspection.approval.approvable
|
|
249
|
+
? inspection.approval.action.mode ?? "keys"
|
|
250
|
+
: undefined,
|
|
251
|
+
command: evidence?.command ?? inspection.approval.command,
|
|
252
|
+
cwd: evidence?.cwd ?? inspection.approval.cwd ?? currentTerminalControl.currentPath,
|
|
253
|
+
fingerprint: fingerprint ?? "",
|
|
254
|
+
terminalTarget: currentTerminalControl.target,
|
|
255
|
+
...(evidence?.source === "claude_transcript"
|
|
256
|
+
? {
|
|
257
|
+
evidenceSource: "claude_transcript",
|
|
258
|
+
evidenceFingerprint: evidence.evidenceFingerprint
|
|
259
|
+
}
|
|
260
|
+
: {})
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
async function runManagedApprovalDispatch({ options, conversation, statePath, logPath, nativeTakeover, terminalControl, executor, monitoredApproval, expectedFingerprint, autoApproved, claudeScreenApproval, decision }) {
|
|
264
|
+
const policyRuleId = stringValue(options.policyRuleId);
|
|
265
|
+
const policyFingerprint = stringValue(options.policyFingerprint);
|
|
266
|
+
const autoApprovalPolicy = autoApproved
|
|
267
|
+
? parseJsonOption(options.autoApprovalPolicyJson, "--auto-approval-policy-json")
|
|
268
|
+
: undefined;
|
|
269
|
+
let executorPolicyDecision;
|
|
270
|
+
const releaseTerminalLock = acquireTerminalBridgeSendLock(storeDirFromOptions(options), terminalControl, { timeoutMs: 30000 });
|
|
271
|
+
let terminalLockReleased = false;
|
|
272
|
+
const releaseApprovalTerminalLock = () => {
|
|
273
|
+
if (!terminalLockReleased) {
|
|
274
|
+
terminalLockReleased = true;
|
|
275
|
+
releaseTerminalLock();
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
let releaseStateLock;
|
|
279
|
+
let approvalDispatchReserved = false;
|
|
280
|
+
const releaseApprovalStateLock = () => {
|
|
281
|
+
if (releaseStateLock) {
|
|
282
|
+
const release = releaseStateLock;
|
|
283
|
+
releaseStateLock = undefined;
|
|
284
|
+
release();
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
const writerStoreDir = pathsForConversationDir(path.dirname(statePath)).storeDir;
|
|
288
|
+
const runApprovalWithStateLock = async () => {
|
|
289
|
+
let approval;
|
|
290
|
+
let lockedConversation = conversation;
|
|
291
|
+
const currentConversation = loadState(statePath);
|
|
292
|
+
const currentTakeover = isRecord(currentConversation.native_session_takeover)
|
|
293
|
+
? currentConversation.native_session_takeover
|
|
294
|
+
: undefined;
|
|
295
|
+
const currentControl = terminalControlFromTakeover(currentTakeover);
|
|
296
|
+
const currentApproval = isRecord(currentTakeover?.terminal_bridge_approval)
|
|
297
|
+
? currentTakeover.terminal_bridge_approval
|
|
298
|
+
: undefined;
|
|
299
|
+
const callbackAuthorityState = assertAutoApprovalCallbackAuthority({
|
|
300
|
+
options,
|
|
301
|
+
conversation: currentConversation,
|
|
302
|
+
statePath,
|
|
303
|
+
takeover: currentTakeover,
|
|
304
|
+
approval: currentApproval,
|
|
305
|
+
expectedFingerprint
|
|
306
|
+
});
|
|
307
|
+
if (!currentControl ||
|
|
308
|
+
!terminalControlsShareIncarnation(currentControl, terminalControl) ||
|
|
309
|
+
(callbackAuthorityState !== "already_approved" &&
|
|
310
|
+
(currentConversation.status !== conversation.status ||
|
|
311
|
+
currentTakeover?.terminal_bridge_message_id !==
|
|
312
|
+
nativeTakeover?.terminal_bridge_message_id ||
|
|
313
|
+
(claudeScreenApproval &&
|
|
314
|
+
monitoredApprovalFingerprint(currentApproval, decision) !==
|
|
315
|
+
monitoredApprovalFingerprint(monitoredApproval, decision))))) {
|
|
316
|
+
throw new Error("approval state changed while waiting for terminal control; refresh status and retry");
|
|
317
|
+
}
|
|
318
|
+
lockedConversation = currentConversation;
|
|
319
|
+
if (callbackAuthorityState === "already_approved") {
|
|
320
|
+
releaseApprovalStateLock();
|
|
321
|
+
printJson({
|
|
322
|
+
conversation: lockedConversation,
|
|
323
|
+
approved: false,
|
|
324
|
+
already_approved: true,
|
|
325
|
+
blocked: false,
|
|
326
|
+
reason: "automatic approval callback was already handled",
|
|
327
|
+
terminal_control: currentControl
|
|
328
|
+
});
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
assertManagedTerminalDispatchOwner({
|
|
332
|
+
storeDir: writerStoreDir,
|
|
333
|
+
conversation: currentConversation,
|
|
334
|
+
terminalControl: currentControl,
|
|
335
|
+
action: "approve"
|
|
336
|
+
});
|
|
337
|
+
const currentRuntimeIdentity = terminalRuntimeIdentityForConversation(currentConversation, currentControl);
|
|
338
|
+
approval = await createTerminalAgentBridge(options).approve(executor.kind, currentControl, {
|
|
339
|
+
decision,
|
|
340
|
+
expectedFingerprint,
|
|
341
|
+
scrollbackLines: Number(options.scrollbackLines ?? 120),
|
|
342
|
+
runtime: currentRuntimeIdentity,
|
|
343
|
+
managedRequest: terminalDurableRequestForConversation(currentConversation, currentControl),
|
|
344
|
+
requiredDecisionMode: autoApproved && executor.kind === "claude" ? "keys" : undefined,
|
|
345
|
+
authorize: autoApproved
|
|
346
|
+
? ({ agent, terminalControl: currentTerminalControl, inspection, fingerprint }) => {
|
|
347
|
+
if (!autoApprovalPolicy) {
|
|
348
|
+
return {
|
|
349
|
+
approved: false,
|
|
350
|
+
reason: "automatic approval requires an executor-side policy"
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
const candidate = approvalPolicyCandidateForInspection({
|
|
354
|
+
agent,
|
|
355
|
+
currentTerminalControl,
|
|
356
|
+
inspection,
|
|
357
|
+
fingerprint
|
|
358
|
+
});
|
|
359
|
+
executorPolicyDecision = evaluateApprovalPolicy({
|
|
360
|
+
policy: autoApprovalPolicy,
|
|
361
|
+
candidate
|
|
362
|
+
});
|
|
363
|
+
if (executorPolicyDecision.action !== "approve") {
|
|
364
|
+
return {
|
|
365
|
+
approved: false,
|
|
366
|
+
reason: `executor-side auto-approval policy rejected the current request: ${executorPolicyDecision.reason}`
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
if (policyRuleId && executorPolicyDecision.ruleId !== policyRuleId) {
|
|
370
|
+
return {
|
|
371
|
+
approved: false,
|
|
372
|
+
reason: "executor-side auto-approval rule changed before execution"
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
if (policyFingerprint &&
|
|
376
|
+
executorPolicyDecision.policyFingerprint !== policyFingerprint) {
|
|
377
|
+
return {
|
|
378
|
+
approved: false,
|
|
379
|
+
reason: "executor-side auto-approval policy changed before execution"
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
return { approved: true };
|
|
383
|
+
}
|
|
384
|
+
: undefined,
|
|
385
|
+
beforeKeyDispatch: claudeScreenApproval
|
|
386
|
+
? ({ fingerprint, terminalControl: dispatchControl, inspection, keys }) => {
|
|
387
|
+
if (autoApproved) {
|
|
388
|
+
if (!autoApprovalPolicy) {
|
|
389
|
+
throw new Error("automatic approval requires an executor-side policy before dispatch");
|
|
390
|
+
}
|
|
391
|
+
const freshPolicyDecision = evaluateApprovalPolicy({
|
|
392
|
+
policy: autoApprovalPolicy,
|
|
393
|
+
candidate: approvalPolicyCandidateForInspection({
|
|
394
|
+
agent: executor.kind,
|
|
395
|
+
currentTerminalControl: dispatchControl,
|
|
396
|
+
inspection,
|
|
397
|
+
fingerprint
|
|
398
|
+
})
|
|
399
|
+
});
|
|
400
|
+
if (freshPolicyDecision.action !== "approve") {
|
|
401
|
+
throw new Error(`executor-side auto-approval policy rejected the recaptured request: ${freshPolicyDecision.reason}`);
|
|
402
|
+
}
|
|
403
|
+
if (executorPolicyDecision?.ruleId &&
|
|
404
|
+
freshPolicyDecision.ruleId !== executorPolicyDecision.ruleId) {
|
|
405
|
+
throw new Error("executor-side auto-approval rule changed after recapture");
|
|
406
|
+
}
|
|
407
|
+
if (policyRuleId && freshPolicyDecision.ruleId !== policyRuleId) {
|
|
408
|
+
throw new Error("executor-side auto-approval rule changed before dispatch");
|
|
409
|
+
}
|
|
410
|
+
if (policyFingerprint &&
|
|
411
|
+
freshPolicyDecision.policyFingerprint !== policyFingerprint) {
|
|
412
|
+
throw new Error("executor-side auto-approval policy changed before dispatch");
|
|
413
|
+
}
|
|
414
|
+
executorPolicyDecision = freshPolicyDecision;
|
|
415
|
+
}
|
|
416
|
+
if (approvalDispatchReserved) {
|
|
417
|
+
throw new Error("Claude approval dispatch was already reserved");
|
|
418
|
+
}
|
|
419
|
+
approvalDispatchReserved = true;
|
|
420
|
+
if (!releaseStateLock) {
|
|
421
|
+
throw new Error("approval state lock was released before terminal dispatch");
|
|
422
|
+
}
|
|
423
|
+
const latestConversation = loadState(statePath);
|
|
424
|
+
const latestTakeover = isRecord(latestConversation.native_session_takeover)
|
|
425
|
+
? latestConversation.native_session_takeover
|
|
426
|
+
: undefined;
|
|
427
|
+
const latestControl = terminalControlFromTakeover(latestTakeover);
|
|
428
|
+
const latestApproval = isRecord(latestTakeover?.terminal_bridge_approval)
|
|
429
|
+
? latestTakeover.terminal_bridge_approval
|
|
430
|
+
: undefined;
|
|
431
|
+
const latestNotifiedAt = validTimestampMs(latestApproval?.notified_at);
|
|
432
|
+
const latestApprovalState = isRecord(latestApproval?.approval_state)
|
|
433
|
+
? latestApproval.approval_state
|
|
434
|
+
: undefined;
|
|
435
|
+
const latestPolicyEvidence = isRecord(latestApprovalState?.policy_evidence)
|
|
436
|
+
? latestApprovalState.policy_evidence
|
|
437
|
+
: undefined;
|
|
438
|
+
const recapturedPolicyEvidence = inspection.approval.approvable
|
|
439
|
+
? inspection.approval.policyEvidence
|
|
440
|
+
: undefined;
|
|
441
|
+
const latestDispatch = isRecord(latestTakeover?.terminal_bridge_approval_dispatch)
|
|
442
|
+
? latestTakeover.terminal_bridge_approval_dispatch
|
|
443
|
+
: undefined;
|
|
444
|
+
if (!latestTakeover ||
|
|
445
|
+
latestConversation.status !== "waiting_for_openclaw" ||
|
|
446
|
+
latestTakeover.terminal_bridge_message_id !==
|
|
447
|
+
nativeTakeover?.terminal_bridge_message_id ||
|
|
448
|
+
monitoredApprovalFingerprint(latestApproval, decision) !==
|
|
449
|
+
fingerprint ||
|
|
450
|
+
latestNotifiedAt === undefined ||
|
|
451
|
+
cliNowMs() - latestNotifiedAt > approvalRuntime().defaults.claudeScreenApprovalTtlMs ||
|
|
452
|
+
expectedFingerprint !== fingerprint ||
|
|
453
|
+
!terminalControlsShareIncarnation(latestControl, dispatchControl) ||
|
|
454
|
+
(autoApproved &&
|
|
455
|
+
(latestPolicyEvidence?.source !== "claude_transcript" ||
|
|
456
|
+
latestPolicyEvidence.evidence_fingerprint !==
|
|
457
|
+
recapturedPolicyEvidence?.evidenceFingerprint))) {
|
|
458
|
+
throw new Error("approval state changed before terminal dispatch; refresh status and retry");
|
|
459
|
+
}
|
|
460
|
+
if (latestDispatch?.state === "reserved" &&
|
|
461
|
+
latestDispatch.terminal_bridge_message_id ===
|
|
462
|
+
latestTakeover.terminal_bridge_message_id) {
|
|
463
|
+
throw new Error("a previous Claude approval dispatch has an uncertain outcome; inspect and resolve the terminal manually");
|
|
464
|
+
}
|
|
465
|
+
const reservedAt = cliNow().toISOString();
|
|
466
|
+
const reservedConversation = {
|
|
467
|
+
...latestConversation,
|
|
468
|
+
native_session_takeover: {
|
|
469
|
+
...latestTakeover,
|
|
470
|
+
terminal_bridge_approval_dispatch: {
|
|
471
|
+
state: "reserved",
|
|
472
|
+
attempt_id: randomUUID(),
|
|
473
|
+
decision,
|
|
474
|
+
fingerprint,
|
|
475
|
+
keys,
|
|
476
|
+
terminal_target: dispatchControl.target,
|
|
477
|
+
terminal_bridge_message_id: latestTakeover.terminal_bridge_message_id,
|
|
478
|
+
reserved_at: reservedAt
|
|
479
|
+
}
|
|
480
|
+
},
|
|
481
|
+
updated_at: reservedAt
|
|
482
|
+
};
|
|
483
|
+
saveState(statePath, reservedConversation);
|
|
484
|
+
lockedConversation = reservedConversation;
|
|
485
|
+
}
|
|
486
|
+
: undefined
|
|
487
|
+
});
|
|
488
|
+
const actualFingerprint = approval.fingerprint;
|
|
489
|
+
const effectivePolicyRuleId = executorPolicyDecision?.ruleId ?? policyRuleId;
|
|
490
|
+
const effectivePolicyFingerprint = executorPolicyDecision?.policyFingerprint ?? policyFingerprint;
|
|
491
|
+
if (approval.decisionDispatched !== true) {
|
|
492
|
+
releaseApprovalStateLock();
|
|
493
|
+
if (autoApproved) {
|
|
494
|
+
appendEvent(logPath, {
|
|
495
|
+
ts: cliNow().toISOString(),
|
|
496
|
+
conversation_id: conversation.conversation_id,
|
|
497
|
+
event: "terminal_auto_approval_decision",
|
|
498
|
+
action: "rejected",
|
|
499
|
+
reason: approval.reason,
|
|
500
|
+
terminal_control: terminalControl,
|
|
501
|
+
expected_fingerprint: expectedFingerprint,
|
|
502
|
+
actual_fingerprint: actualFingerprint,
|
|
503
|
+
policy_rule_id: effectivePolicyRuleId,
|
|
504
|
+
policy_fingerprint: effectivePolicyFingerprint
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
printJson({
|
|
508
|
+
conversation,
|
|
509
|
+
approved: false,
|
|
510
|
+
decision,
|
|
511
|
+
decision_dispatched: false,
|
|
512
|
+
blocked: approval.blocked,
|
|
513
|
+
reason: approval.reason,
|
|
514
|
+
terminal_control: terminalControl,
|
|
515
|
+
expected_approval_fingerprint: expectedFingerprint,
|
|
516
|
+
actual_approval_fingerprint: actualFingerprint,
|
|
517
|
+
screen_excerpt: approval.screenExcerpt
|
|
518
|
+
});
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
appendEvent(logPath, {
|
|
522
|
+
ts: cliNow().toISOString(),
|
|
523
|
+
conversation_id: conversation.conversation_id,
|
|
524
|
+
event: "terminal_approval_send",
|
|
525
|
+
decision,
|
|
526
|
+
terminal_control: terminalControl,
|
|
527
|
+
key: approval.key,
|
|
528
|
+
keys: approval.keys,
|
|
529
|
+
label: approval.label,
|
|
530
|
+
decision_mode: approval.decisionMode,
|
|
531
|
+
request_id: approval.requestId,
|
|
532
|
+
approval_fingerprint: actualFingerprint,
|
|
533
|
+
auto_approved: autoApproved,
|
|
534
|
+
policy_rule_id: effectivePolicyRuleId,
|
|
535
|
+
policy_fingerprint: effectivePolicyFingerprint
|
|
536
|
+
});
|
|
537
|
+
if (autoApproved) {
|
|
538
|
+
appendEvent(logPath, {
|
|
539
|
+
ts: cliNow().toISOString(),
|
|
540
|
+
conversation_id: conversation.conversation_id,
|
|
541
|
+
event: "terminal_auto_approval_decision",
|
|
542
|
+
action: "approved",
|
|
543
|
+
terminal_control: terminalControl,
|
|
544
|
+
approval_fingerprint: actualFingerprint,
|
|
545
|
+
policy_rule_id: effectivePolicyRuleId,
|
|
546
|
+
policy_fingerprint: effectivePolicyFingerprint
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
runtimeLog("info", "terminal_approval_send", {
|
|
550
|
+
conversation_id: conversation.conversation_id,
|
|
551
|
+
terminal_target: terminalControl.target,
|
|
552
|
+
decision,
|
|
553
|
+
key: approval.key,
|
|
554
|
+
keys: approval.keys,
|
|
555
|
+
label: approval.label,
|
|
556
|
+
decision_mode: approval.decisionMode,
|
|
557
|
+
request_id: approval.requestId,
|
|
558
|
+
approval_fingerprint: actualFingerprint,
|
|
559
|
+
auto_approved: autoApproved,
|
|
560
|
+
policy_rule_id: effectivePolicyRuleId,
|
|
561
|
+
policy_fingerprint: effectivePolicyFingerprint
|
|
562
|
+
});
|
|
563
|
+
const nativeTakeoverForUpdate = isRecord(lockedConversation.native_session_takeover)
|
|
564
|
+
? { ...lockedConversation.native_session_takeover }
|
|
565
|
+
: {};
|
|
566
|
+
const resolvedApproval = isRecord(nativeTakeoverForUpdate.terminal_bridge_approval)
|
|
567
|
+
? nativeTakeoverForUpdate.terminal_bridge_approval
|
|
568
|
+
: undefined;
|
|
569
|
+
const resolvedApprovalScreenDigest = stringValue(resolvedApproval?.screen_digest);
|
|
570
|
+
const resolvedApprovalState = isRecord(resolvedApproval?.approval_state)
|
|
571
|
+
? resolvedApproval.approval_state
|
|
572
|
+
: undefined;
|
|
573
|
+
const resolvedTranscriptIdentity = claudeTranscriptApprovalIdentity(resolvedApprovalState);
|
|
574
|
+
const approvalResolvedAt = cliNow().toISOString();
|
|
575
|
+
const agentTimeoutMinutes = Number(options.agentTimeoutMinutes ??
|
|
576
|
+
nativeTakeoverForUpdate.terminal_bridge_inactivity_timeout_minutes ??
|
|
577
|
+
approvalRuntime().defaults.agentTimeoutMinutes);
|
|
578
|
+
const agentHardTimeoutMinutes = positiveMinutes(options.agentHardTimeoutMinutes ??
|
|
579
|
+
nativeTakeoverForUpdate.terminal_bridge_hard_timeout_minutes ??
|
|
580
|
+
approvalRuntime().defaults.agentHardTimeoutMinutes, "--agent-hard-timeout-minutes");
|
|
581
|
+
const nextNativeTakeover = {
|
|
582
|
+
...nativeTakeoverForUpdate,
|
|
583
|
+
terminal_bridge_approval: undefined,
|
|
584
|
+
terminal_bridge_approval_dispatch: undefined,
|
|
585
|
+
terminal_bridge_approval_resolved_at: approvalResolvedAt,
|
|
586
|
+
terminal_bridge_last_approval_fingerprint: actualFingerprint,
|
|
587
|
+
terminal_bridge_last_approval_decision: decision,
|
|
588
|
+
terminal_bridge_last_approval_screen_digest: resolvedApprovalScreenDigest,
|
|
589
|
+
terminal_bridge_last_approval_request_id: resolvedTranscriptIdentity?.requestId,
|
|
590
|
+
terminal_bridge_last_approval_evidence_fingerprint: resolvedTranscriptIdentity?.evidenceFingerprint,
|
|
591
|
+
terminal_bridge_last_approval_prompt_cleared_at: undefined,
|
|
592
|
+
terminal_bridge_last_approval_at: approvalResolvedAt,
|
|
593
|
+
terminal_bridge_last_approval_message_id: nativeTakeoverForUpdate.terminal_bridge_message_id,
|
|
594
|
+
terminal_bridge_monitor_lock_version: monitorOwner.LOCK_VERSION,
|
|
595
|
+
terminal_bridge_monitor_started_at: approvalResolvedAt,
|
|
596
|
+
terminal_bridge_last_activity_at: approvalResolvedAt,
|
|
597
|
+
terminal_bridge_last_activity_reason: "approval resolved",
|
|
598
|
+
terminal_bridge_inactivity_timeout_minutes: agentTimeoutMinutes,
|
|
599
|
+
terminal_bridge_hard_timeout_minutes: agentHardTimeoutMinutes,
|
|
600
|
+
terminal_bridge_inactivity_deadline_at: deadlineAt(approvalResolvedAt, agentTimeoutMinutes),
|
|
601
|
+
terminal_bridge_hard_deadline_at: deadlineAt(stringValue(nativeTakeoverForUpdate.terminal_bridge_started_at) ?? approvalResolvedAt, agentHardTimeoutMinutes)
|
|
602
|
+
};
|
|
603
|
+
delete nextNativeTakeover.terminal_bridge_approval;
|
|
604
|
+
delete nextNativeTakeover.terminal_bridge_approval_dispatch;
|
|
605
|
+
delete nextNativeTakeover.terminal_bridge_last_approval_prompt_cleared_at;
|
|
606
|
+
const nextConversation = {
|
|
607
|
+
...lockedConversation,
|
|
608
|
+
status: terminalBridgeEnabled(lockedConversation)
|
|
609
|
+
? "waiting_for_agent"
|
|
610
|
+
: lockedConversation.status,
|
|
611
|
+
native_session_takeover: nextNativeTakeover,
|
|
612
|
+
updated_at: approvalResolvedAt
|
|
613
|
+
};
|
|
614
|
+
saveState(statePath, nextConversation);
|
|
615
|
+
releaseApprovalStateLock();
|
|
616
|
+
const bridgeMonitor = ensureTerminalBridgeMonitorAfterApproval({
|
|
617
|
+
conversation: nextConversation,
|
|
618
|
+
statePath,
|
|
619
|
+
logPath,
|
|
620
|
+
terminalControl,
|
|
621
|
+
options
|
|
622
|
+
});
|
|
623
|
+
printJson({
|
|
624
|
+
conversation: nextConversation,
|
|
625
|
+
approved: decision === "approve_once",
|
|
626
|
+
rejected: decision === "reject",
|
|
627
|
+
decision,
|
|
628
|
+
decision_dispatched: true,
|
|
629
|
+
terminal_control: terminalControl,
|
|
630
|
+
key: approval.key,
|
|
631
|
+
keys: approval.keys,
|
|
632
|
+
label: approval.label,
|
|
633
|
+
decision_mode: approval.decisionMode,
|
|
634
|
+
request_id: approval.requestId,
|
|
635
|
+
approval_fingerprint: actualFingerprint,
|
|
636
|
+
auto_approved: autoApproved,
|
|
637
|
+
policy_rule_id: effectivePolicyRuleId,
|
|
638
|
+
policy_fingerprint: effectivePolicyFingerprint,
|
|
639
|
+
monitor_pid: bridgeMonitor.monitorPid ?? null,
|
|
640
|
+
monitor_handoff_pid: bridgeMonitor.handoffWatchdog?.pid ?? null
|
|
641
|
+
});
|
|
642
|
+
};
|
|
643
|
+
try {
|
|
644
|
+
return await withStoreWriterLeaseAsync(writerStoreDir, async () => {
|
|
645
|
+
releaseStateLock = acquireFileLock(`${statePath}.lock`);
|
|
646
|
+
try {
|
|
647
|
+
return await runApprovalWithStateLock();
|
|
648
|
+
}
|
|
649
|
+
finally {
|
|
650
|
+
releaseApprovalStateLock();
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
finally {
|
|
655
|
+
try {
|
|
656
|
+
releaseApprovalStateLock();
|
|
657
|
+
}
|
|
658
|
+
finally {
|
|
659
|
+
releaseApprovalTerminalLock();
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
function assertAutoApprovalCallbackAuthority(input) {
|
|
664
|
+
if (input.options.autoApproved !== true) {
|
|
665
|
+
return "not_callback";
|
|
666
|
+
}
|
|
667
|
+
const expected = autoApprovalCallbackAuthorityFromOptions(input.options);
|
|
668
|
+
if (!expected) {
|
|
669
|
+
return "not_callback";
|
|
670
|
+
}
|
|
671
|
+
assertAutoApprovalCallbackRoute({
|
|
672
|
+
options: input.options,
|
|
673
|
+
conversation: input.conversation,
|
|
674
|
+
statePath: input.statePath
|
|
675
|
+
});
|
|
676
|
+
const delivery = isRecord(input.conversation.callback_delivery)
|
|
677
|
+
? input.conversation.callback_delivery
|
|
678
|
+
: undefined;
|
|
679
|
+
const callbackMessage = isRecord(delivery?.message)
|
|
680
|
+
? delivery.message
|
|
681
|
+
: undefined;
|
|
682
|
+
const callbackMetadata = isRecord(callbackMessage?.metadata)
|
|
683
|
+
? callbackMessage.metadata
|
|
684
|
+
: undefined;
|
|
685
|
+
const callbackCandidate = isRecord(callbackMetadata?.approval_candidate)
|
|
686
|
+
? callbackMetadata.approval_candidate
|
|
687
|
+
: undefined;
|
|
688
|
+
const callbackTerminalStatus = isRecord(callbackMetadata?.terminal_status)
|
|
689
|
+
? callbackMetadata.terminal_status
|
|
690
|
+
: undefined;
|
|
691
|
+
const callbackApprovalState = isRecord(callbackTerminalStatus?.approval_state)
|
|
692
|
+
? callbackTerminalStatus.approval_state
|
|
693
|
+
: undefined;
|
|
694
|
+
const persistedApprovalState = isRecord(input.approval?.approval_state)
|
|
695
|
+
? input.approval.approval_state
|
|
696
|
+
: undefined;
|
|
697
|
+
const callbackFingerprints = [
|
|
698
|
+
stringValue(callbackMetadata?.approval_fingerprint),
|
|
699
|
+
stringValue(callbackCandidate?.fingerprint),
|
|
700
|
+
stringValue(callbackApprovalState?.fingerprint)
|
|
701
|
+
];
|
|
702
|
+
const commonAuthorityMismatch = delivery?.kind !== "approval_notification" ||
|
|
703
|
+
stringValue(callbackMessage?.id) !== expected.messageId ||
|
|
704
|
+
stringValue(callbackMessage?.conversation_id) !== expected.conversationId ||
|
|
705
|
+
stringValue(callbackMessage?.session_id) !== expected.sessionId ||
|
|
706
|
+
stringValue(callbackMessage?.turn_id) !== expected.turnId ||
|
|
707
|
+
!input.expectedFingerprint ||
|
|
708
|
+
!/^[a-f0-9]{64}$/u.test(input.expectedFingerprint) ||
|
|
709
|
+
callbackFingerprints.some((value) => value !== input.expectedFingerprint) ||
|
|
710
|
+
input.takeover?.terminal_bridge !== true;
|
|
711
|
+
if (commonAuthorityMismatch) {
|
|
712
|
+
throw new Error("automatic approval callback no longer matches the locked Turn state; refresh status and retry");
|
|
713
|
+
}
|
|
714
|
+
if (input.approval) {
|
|
715
|
+
const currentFingerprints = [
|
|
716
|
+
stringValue(input.approval.fingerprint),
|
|
717
|
+
stringValue(persistedApprovalState?.fingerprint)
|
|
718
|
+
];
|
|
719
|
+
if (input.conversation.status !== "waiting_for_openclaw" ||
|
|
720
|
+
currentFingerprints.some((value) => value !== input.expectedFingerprint) ||
|
|
721
|
+
stringValue(input.approval.callback_message_id) !== expected.messageId) {
|
|
722
|
+
throw new Error("automatic approval callback no longer matches the locked Turn state; refresh status and retry");
|
|
723
|
+
}
|
|
724
|
+
return "current";
|
|
725
|
+
}
|
|
726
|
+
if (input.takeover?.terminal_bridge_approval !== undefined ||
|
|
727
|
+
input.takeover?.terminal_bridge_approval_dispatch !== undefined ||
|
|
728
|
+
!isPostApprovalCallbackReplayStatus(input.conversation.status) ||
|
|
729
|
+
stringValue(input.takeover?.terminal_bridge_message_id) !==
|
|
730
|
+
expected.messageId ||
|
|
731
|
+
stringValue(input.takeover?.terminal_bridge_last_approval_message_id) !==
|
|
732
|
+
expected.messageId ||
|
|
733
|
+
stringValue(input.takeover?.terminal_bridge_last_approval_fingerprint) !==
|
|
734
|
+
input.expectedFingerprint ||
|
|
735
|
+
validTimestampMs(input.takeover?.terminal_bridge_approval_resolved_at) === undefined) {
|
|
736
|
+
throw new Error("automatic approval callback no longer matches the locked Turn receipt; refresh status and retry");
|
|
737
|
+
}
|
|
738
|
+
return "already_approved";
|
|
739
|
+
}
|
|
740
|
+
function isPostApprovalCallbackReplayStatus(status) {
|
|
741
|
+
return [
|
|
742
|
+
"waiting_for_agent",
|
|
743
|
+
"running",
|
|
744
|
+
"idle",
|
|
745
|
+
"stalled",
|
|
746
|
+
"callback_pending",
|
|
747
|
+
"callback_failed",
|
|
748
|
+
"failed",
|
|
749
|
+
"closed",
|
|
750
|
+
"cancelled",
|
|
751
|
+
"cancelling"
|
|
752
|
+
].includes(status);
|
|
753
|
+
}
|
|
754
|
+
function autoApprovalCallbackAuthorityFromOptions(options) {
|
|
755
|
+
const candidate = {
|
|
756
|
+
conversationId: stringValue(options.expectedCallbackConversationId),
|
|
757
|
+
sessionId: stringValue(options.expectedCallbackSessionId),
|
|
758
|
+
turnId: stringValue(options.expectedCallbackTurnId),
|
|
759
|
+
messageId: stringValue(options.expectedCallbackMessageId),
|
|
760
|
+
openclawSession: stringValue(options.expectedCallbackOpenclawSession)
|
|
761
|
+
};
|
|
762
|
+
const values = Object.values(candidate);
|
|
763
|
+
if (values.every((value) => value === undefined)) {
|
|
764
|
+
return undefined;
|
|
765
|
+
}
|
|
766
|
+
if (values.some((value) => value === undefined)) {
|
|
767
|
+
throw new Error("automatic approval callback identity is incomplete; no approval key was sent");
|
|
768
|
+
}
|
|
769
|
+
return candidate;
|
|
770
|
+
}
|
|
771
|
+
function assertAutoApprovalCallbackRoute(input) {
|
|
772
|
+
if (input.options.autoApproved !== true) {
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
const expected = autoApprovalCallbackAuthorityFromOptions(input.options);
|
|
776
|
+
if (!expected) {
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
const storedOpenClawSession = stringValue(input.conversation.gateway_session ?? input.conversation.openclaw_session);
|
|
780
|
+
if (input.conversation.conversation_id !== expected.conversationId ||
|
|
781
|
+
sessionIdForConversation(input.conversation) !== expected.sessionId ||
|
|
782
|
+
turnIdForConversation(input.conversation) !== expected.turnId ||
|
|
783
|
+
!sameCanonicalStatePath(input.conversation.state_path, input.statePath) ||
|
|
784
|
+
storedOpenClawSession !== expected.openclawSession) {
|
|
785
|
+
throw new Error("automatic approval callback does not match the selected Turn state; no state was changed");
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
async function runTerminalConversationApprove({ options, terminal }) {
|
|
789
|
+
const { conversationId, agent, terminalControl, pid } = terminal;
|
|
790
|
+
const storeDir = storeDirFromOptions(options);
|
|
791
|
+
const releaseTerminalLock = acquireTerminalBridgeSendLock(storeDir, terminalControl, { timeoutMs: 30000 });
|
|
792
|
+
try {
|
|
793
|
+
if (agent === "claude") {
|
|
794
|
+
printJson({
|
|
795
|
+
conversation_id: conversationId,
|
|
796
|
+
source: "terminal_control",
|
|
797
|
+
approved: false,
|
|
798
|
+
blocked: true,
|
|
799
|
+
reason: "Claude screen approval requires `send --background` so AKK can bind it to an active managed turn",
|
|
800
|
+
terminal_control: terminalControl
|
|
801
|
+
});
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
const suppliedTerminalToken = stringValue(options.expectedTerminalToken);
|
|
805
|
+
const initialResolution = await terminalListCliFacade.resolveTerminalScopedCodexApproval({
|
|
806
|
+
options,
|
|
807
|
+
terminal
|
|
808
|
+
});
|
|
809
|
+
if (initialResolution.state === "blocked") {
|
|
810
|
+
throw new Error(initialResolution.reason);
|
|
811
|
+
}
|
|
812
|
+
if (initialResolution.state === "unmanaged" && suppliedTerminalToken) {
|
|
813
|
+
throw new Error("--expected-terminal-token does not match an advertised terminal-scoped Codex approval");
|
|
814
|
+
}
|
|
815
|
+
if (initialResolution.state === "eligible" &&
|
|
816
|
+
suppliedTerminalToken !== initialResolution.boundary.token) {
|
|
817
|
+
throw new Error("terminal-scoped Codex approval token is missing or stale; refresh AKK list");
|
|
818
|
+
}
|
|
819
|
+
const runtime = {
|
|
820
|
+
pid,
|
|
821
|
+
cwd: terminalControl.currentPath,
|
|
822
|
+
conversationId,
|
|
823
|
+
terminalTarget: terminalControl.target
|
|
824
|
+
};
|
|
825
|
+
const approveCurrentPrompt = async (terminalScoped) => createTerminalAgentBridge(options).approve(agent, terminalControl, {
|
|
826
|
+
expectedFingerprint: stringValue(options.expectedApprovalFingerprint),
|
|
827
|
+
scrollbackLines: Number(options.scrollbackLines ?? 120),
|
|
828
|
+
runtime,
|
|
829
|
+
beforeKeyDispatch: terminalScoped
|
|
830
|
+
? async (context) => {
|
|
831
|
+
const approvalSnapshot = terminalScopedCodexApprovalPromptSnapshot({
|
|
832
|
+
approvable: true,
|
|
833
|
+
fingerprint: context.fingerprint,
|
|
834
|
+
keys: context.keys,
|
|
835
|
+
decision_mode: context.inspection.approval.approvable
|
|
836
|
+
? context.inspection.approval.action.mode ?? "keys"
|
|
837
|
+
: undefined,
|
|
838
|
+
request_id: context.inspection.approval.approvable
|
|
839
|
+
? context.inspection.approval.action.requestId
|
|
840
|
+
: undefined
|
|
841
|
+
});
|
|
842
|
+
const current = await terminalListCliFacade.resolveTerminalScopedCodexApproval({
|
|
843
|
+
options,
|
|
844
|
+
terminal,
|
|
845
|
+
approvalSnapshot
|
|
846
|
+
});
|
|
847
|
+
if (current.state !== "eligible" ||
|
|
848
|
+
current.boundary.token !== suppliedTerminalToken) {
|
|
849
|
+
throw new Error(current.state === "blocked"
|
|
850
|
+
? current.reason
|
|
851
|
+
: "terminal-scoped Codex approval authority changed before key dispatch");
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
: undefined
|
|
855
|
+
});
|
|
856
|
+
const terminalScoped = initialResolution.state === "eligible";
|
|
857
|
+
const approval = terminalScoped
|
|
858
|
+
? await withStoreWriterLeaseAsync(storeDir, async () => {
|
|
859
|
+
const current = await terminalListCliFacade.resolveTerminalScopedCodexApproval({
|
|
860
|
+
options,
|
|
861
|
+
terminal
|
|
862
|
+
});
|
|
863
|
+
if (current.state !== "eligible" ||
|
|
864
|
+
current.boundary.token !== suppliedTerminalToken) {
|
|
865
|
+
throw new Error(current.state === "blocked"
|
|
866
|
+
? current.reason
|
|
867
|
+
: "terminal-scoped Codex approval authority changed while waiting for Store control");
|
|
868
|
+
}
|
|
869
|
+
return approveCurrentPrompt(true);
|
|
870
|
+
})
|
|
871
|
+
: await approveCurrentPrompt(false);
|
|
872
|
+
if (!approval.approved) {
|
|
873
|
+
printJson({
|
|
874
|
+
conversation_id: conversationId,
|
|
875
|
+
source: "terminal_control",
|
|
876
|
+
approved: false,
|
|
877
|
+
blocked: approval.blocked,
|
|
878
|
+
reason: approval.reason,
|
|
879
|
+
terminal_control: terminalControl,
|
|
880
|
+
screen_excerpt: approval.screenExcerpt
|
|
881
|
+
});
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
runtimeLog("info", "terminal_approval_send", {
|
|
885
|
+
conversation_id: conversationId,
|
|
886
|
+
agent,
|
|
887
|
+
terminal_target: terminalControl.target,
|
|
888
|
+
key: approval.key,
|
|
889
|
+
keys: approval.keys,
|
|
890
|
+
label: approval.label,
|
|
891
|
+
decision_mode: approval.decisionMode,
|
|
892
|
+
request_id: approval.requestId
|
|
893
|
+
});
|
|
894
|
+
printJson({
|
|
895
|
+
conversation_id: conversationId,
|
|
896
|
+
source: "terminal_control",
|
|
897
|
+
approved: true,
|
|
898
|
+
terminal_control: terminalControl,
|
|
899
|
+
key: approval.key,
|
|
900
|
+
keys: approval.keys,
|
|
901
|
+
label: approval.label,
|
|
902
|
+
approval_fingerprint: approval.fingerprint,
|
|
903
|
+
decision_mode: approval.decisionMode,
|
|
904
|
+
request_id: approval.requestId,
|
|
905
|
+
terminal_scoped: terminalScoped,
|
|
906
|
+
...(terminalScoped
|
|
907
|
+
? {
|
|
908
|
+
durable_dispatch_receipt: false,
|
|
909
|
+
uncertain_outcome_recovery: "refresh status and inspect the live prompt; do not retry blindly"
|
|
910
|
+
}
|
|
911
|
+
: {})
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
finally {
|
|
915
|
+
releaseTerminalLock();
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
export async function runTerminalApproval(dependencies, options) {
|
|
919
|
+
return approvalContext.run(dependencies, () => runApproveInContext(options));
|
|
920
|
+
}
|
|
921
|
+
//# sourceMappingURL=terminal-approval-cli-adapter.js.map
|