@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.
Files changed (278) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +27 -6
  3. package/dist/src/callback-outbox-policy.js +27 -6
  4. package/dist/src/callback-outbox-policy.js.map +1 -1
  5. package/dist/src/callback-outbox-service.d.ts +4 -30
  6. package/dist/src/callback-outbox-service.js +21 -154
  7. package/dist/src/callback-outbox-service.js.map +1 -1
  8. package/dist/src/callback-outbox-settlement.js +13 -8
  9. package/dist/src/callback-outbox-settlement.js.map +1 -1
  10. package/dist/src/callback-route-service.d.ts +46 -0
  11. package/dist/src/callback-route-service.js +155 -0
  12. package/dist/src/callback-route-service.js.map +1 -0
  13. package/dist/src/claude-terminal-agent-adapter.js +4 -0
  14. package/dist/src/claude-terminal-agent-adapter.js.map +1 -1
  15. package/dist/src/cli-core.js +9 -1
  16. package/dist/src/cli-core.js.map +1 -1
  17. package/dist/src/codex-process-incarnation.d.ts +8 -0
  18. package/dist/src/codex-process-incarnation.js +36 -0
  19. package/dist/src/codex-process-incarnation.js.map +1 -0
  20. package/dist/src/codex-terminal-agent-adapter.js +4 -0
  21. package/dist/src/codex-terminal-agent-adapter.js.map +1 -1
  22. package/dist/src/deferred-foreground-conflict-policy.d.ts +22 -0
  23. package/dist/src/deferred-foreground-conflict-policy.js +52 -0
  24. package/dist/src/deferred-foreground-conflict-policy.js.map +1 -0
  25. package/dist/src/durable-notification-kernel.d.ts +94 -0
  26. package/dist/src/durable-notification-kernel.js +68 -0
  27. package/dist/src/durable-notification-kernel.js.map +1 -0
  28. package/dist/src/herdr-socket-transport.d.ts +43 -0
  29. package/dist/src/herdr-socket-transport.js +170 -0
  30. package/dist/src/herdr-socket-transport.js.map +1 -0
  31. package/dist/src/herdr-terminal-control-provider.d.ts +2 -45
  32. package/dist/src/herdr-terminal-control-provider.js +8 -173
  33. package/dist/src/herdr-terminal-control-provider.js.map +1 -1
  34. package/dist/src/host-adapter-capabilities.d.ts +35 -0
  35. package/dist/src/host-adapter-capabilities.js +146 -0
  36. package/dist/src/host-adapter-capabilities.js.map +1 -0
  37. package/dist/src/host-adapter.d.ts +9 -3
  38. package/dist/src/host-adapter.js +33 -9
  39. package/dist/src/host-adapter.js.map +1 -1
  40. package/dist/src/host-bridge-tools.d.ts +3 -4
  41. package/dist/src/host-bridge-tools.js +49 -92
  42. package/dist/src/host-bridge-tools.js.map +1 -1
  43. package/dist/src/host-bridge.js +5 -5
  44. package/dist/src/host-bridge.js.map +1 -1
  45. package/dist/src/host-monitor-reconciliation.d.ts +6 -0
  46. package/dist/src/host-monitor-reconciliation.js +96 -0
  47. package/dist/src/host-monitor-reconciliation.js.map +1 -0
  48. package/dist/src/managed-session.js +2 -18
  49. package/dist/src/managed-session.js.map +1 -1
  50. package/dist/src/native-thread-lifecycle-cli-adapter.d.ts +13 -0
  51. package/dist/src/native-thread-lifecycle-cli-adapter.js +635 -3
  52. package/dist/src/native-thread-lifecycle-cli-adapter.js.map +1 -1
  53. package/dist/src/openclaw-list-projection.d.ts +2 -0
  54. package/dist/src/openclaw-list-projection.js +3 -0
  55. package/dist/src/openclaw-list-projection.js.map +1 -0
  56. package/dist/src/openclaw-model-facing-field-policy.d.ts +2 -0
  57. package/dist/src/openclaw-model-facing-field-policy.js +3 -0
  58. package/dist/src/openclaw-model-facing-field-policy.js.map +1 -0
  59. package/dist/src/openclaw-plugin-command-adapter.d.ts +6 -29
  60. package/dist/src/openclaw-plugin-command-adapter.js +7 -2370
  61. package/dist/src/openclaw-plugin-command-adapter.js.map +1 -1
  62. package/dist/src/openclaw-plugin-helpers.d.ts +2 -121
  63. package/dist/src/openclaw-plugin-helpers.js +2 -1164
  64. package/dist/src/openclaw-plugin-helpers.js.map +1 -1
  65. package/dist/src/openclaw-plugin-schemas.d.ts +2 -510
  66. package/dist/src/openclaw-plugin-schemas.js +2 -519
  67. package/dist/src/openclaw-plugin-schemas.js.map +1 -1
  68. package/dist/src/openclaw-plugin-supervisor.d.ts +4 -5
  69. package/dist/src/openclaw-plugin-supervisor.js +5 -94
  70. package/dist/src/openclaw-plugin-supervisor.js.map +1 -1
  71. package/dist/src/openclaw-plugin-value-helpers.d.ts +2 -0
  72. package/dist/src/openclaw-plugin-value-helpers.js +3 -0
  73. package/dist/src/openclaw-plugin-value-helpers.js.map +1 -0
  74. package/dist/src/openclaw-private-authority-offers.d.ts +4 -39
  75. package/dist/src/openclaw-private-authority-offers.js +3 -258
  76. package/dist/src/openclaw-private-authority-offers.js.map +1 -1
  77. package/dist/src/semantic-private-authority-offers.d.ts +41 -0
  78. package/dist/src/semantic-private-authority-offers.js +261 -0
  79. package/dist/src/semantic-private-authority-offers.js.map +1 -0
  80. package/dist/src/semantic-tool-arguments.d.ts +9 -0
  81. package/dist/src/semantic-tool-arguments.js +64 -0
  82. package/dist/src/semantic-tool-arguments.js.map +1 -0
  83. package/dist/src/semantic-tool-catalog.d.ts +48 -0
  84. package/dist/src/semantic-tool-catalog.js +105 -0
  85. package/dist/src/semantic-tool-catalog.js.map +1 -0
  86. package/dist/src/semantic-tool-command-helpers.d.ts +145 -0
  87. package/dist/src/semantic-tool-command-helpers.js +1272 -0
  88. package/dist/src/semantic-tool-command-helpers.js.map +1 -0
  89. package/dist/src/semantic-tool-list-projection.d.ts +10 -0
  90. package/dist/src/semantic-tool-list-projection.js +497 -0
  91. package/dist/src/semantic-tool-list-projection.js.map +1 -0
  92. package/dist/src/semantic-tool-model-facing-field-policy.d.ts +3 -0
  93. package/dist/src/semantic-tool-model-facing-field-policy.js +66 -0
  94. package/dist/src/semantic-tool-model-facing-field-policy.js.map +1 -0
  95. package/dist/src/semantic-tool-presentation.d.ts +37 -0
  96. package/dist/src/semantic-tool-presentation.js +680 -0
  97. package/dist/src/semantic-tool-presentation.js.map +1 -0
  98. package/dist/src/semantic-tool-private-authority.d.ts +39 -0
  99. package/dist/src/semantic-tool-private-authority.js +602 -0
  100. package/dist/src/semantic-tool-private-authority.js.map +1 -0
  101. package/dist/src/semantic-tool-relay.d.ts +16 -0
  102. package/dist/src/semantic-tool-relay.js +175 -0
  103. package/dist/src/semantic-tool-relay.js.map +1 -0
  104. package/dist/src/semantic-tool-runtime.d.ts +10 -0
  105. package/dist/src/semantic-tool-runtime.js +1157 -0
  106. package/dist/src/semantic-tool-runtime.js.map +1 -0
  107. package/dist/src/semantic-tool-schemas.d.ts +563 -0
  108. package/dist/src/semantic-tool-schemas.js +573 -0
  109. package/dist/src/semantic-tool-schemas.js.map +1 -0
  110. package/dist/src/semantic-tool-value-helpers.d.ts +5 -0
  111. package/dist/src/semantic-tool-value-helpers.js +27 -0
  112. package/dist/src/semantic-tool-value-helpers.js.map +1 -0
  113. package/dist/src/store-event-presentation.d.ts +17 -0
  114. package/dist/src/store-event-presentation.js +33 -0
  115. package/dist/src/store-event-presentation.js.map +1 -0
  116. package/dist/src/store.d.ts +3 -16
  117. package/dist/src/store.js +2 -30
  118. package/dist/src/store.js.map +1 -1
  119. package/dist/src/terminal-action-projection.d.ts +3 -17
  120. package/dist/src/terminal-action-projection.js +13 -41
  121. package/dist/src/terminal-action-projection.js.map +1 -1
  122. package/dist/src/terminal-agent-adapter.d.ts +7 -0
  123. package/dist/src/terminal-agent-adapter.js +8 -0
  124. package/dist/src/terminal-agent-adapter.js.map +1 -1
  125. package/dist/src/terminal-agent-bridge.d.ts +35 -332
  126. package/dist/src/terminal-agent-bridge.js +293 -2798
  127. package/dist/src/terminal-agent-bridge.js.map +1 -1
  128. package/dist/src/terminal-approval-cli-adapter.d.ts +17 -0
  129. package/dist/src/terminal-approval-cli-adapter.js +921 -0
  130. package/dist/src/terminal-approval-cli-adapter.js.map +1 -0
  131. package/dist/src/terminal-command-cli-adapter.d.ts +2 -429
  132. package/dist/src/terminal-command-cli-adapter.js +344 -6061
  133. package/dist/src/terminal-command-cli-adapter.js.map +1 -1
  134. package/dist/src/terminal-command-cli-ports.d.ts +443 -0
  135. package/dist/src/terminal-command-cli-ports.js +2 -0
  136. package/dist/src/terminal-command-cli-ports.js.map +1 -0
  137. package/dist/src/terminal-command-dispatch-preparation.d.ts +81 -0
  138. package/dist/src/terminal-command-dispatch-preparation.js +209 -0
  139. package/dist/src/terminal-command-dispatch-preparation.js.map +1 -0
  140. package/dist/src/terminal-command-dispatch-transport.d.ts +35 -0
  141. package/dist/src/terminal-command-dispatch-transport.js +859 -0
  142. package/dist/src/terminal-command-dispatch-transport.js.map +1 -0
  143. package/dist/src/terminal-command-foreground-proof.d.ts +32 -0
  144. package/dist/src/terminal-command-foreground-proof.js +72 -0
  145. package/dist/src/terminal-command-foreground-proof.js.map +1 -0
  146. package/dist/src/terminal-command-send-preflight.d.ts +41 -0
  147. package/dist/src/terminal-command-send-preflight.js +86 -0
  148. package/dist/src/terminal-command-send-preflight.js.map +1 -0
  149. package/dist/src/terminal-composer-classifier.d.ts +55 -0
  150. package/dist/src/terminal-composer-classifier.js +638 -0
  151. package/dist/src/terminal-composer-classifier.js.map +1 -0
  152. package/dist/src/terminal-control-ref.d.ts +23 -0
  153. package/dist/src/terminal-control-ref.js +32 -0
  154. package/dist/src/terminal-control-ref.js.map +1 -1
  155. package/dist/src/terminal-dispatch-recovery-authority.d.ts +56 -0
  156. package/dist/src/terminal-dispatch-recovery-authority.js +251 -0
  157. package/dist/src/terminal-dispatch-recovery-authority.js.map +1 -0
  158. package/dist/src/terminal-dispatch-recovery-cli-adapter.d.ts +1 -9
  159. package/dist/src/terminal-dispatch-recovery-cli-adapter.js +5 -240
  160. package/dist/src/terminal-dispatch-recovery-cli-adapter.js.map +1 -1
  161. package/dist/src/terminal-handoff-cli-adapter.d.ts +1 -11
  162. package/dist/src/terminal-handoff-cli-adapter.js +2 -39
  163. package/dist/src/terminal-handoff-cli-adapter.js.map +1 -1
  164. package/dist/src/terminal-human-explicit-send-cli-adapter.d.ts +25 -0
  165. package/dist/src/terminal-human-explicit-send-cli-adapter.js +903 -0
  166. package/dist/src/terminal-human-explicit-send-cli-adapter.js.map +1 -0
  167. package/dist/src/terminal-identity-authority-cli-adapter.d.ts +1 -6
  168. package/dist/src/terminal-identity-authority-cli-adapter.js +3 -33
  169. package/dist/src/terminal-identity-authority-cli-adapter.js.map +1 -1
  170. package/dist/src/terminal-interaction-cli-adapter.js +209 -210
  171. package/dist/src/terminal-interaction-cli-adapter.js.map +1 -1
  172. package/dist/src/terminal-interaction-core.d.ts +0 -2
  173. package/dist/src/terminal-interaction-core.js +4 -3
  174. package/dist/src/terminal-interaction-core.js.map +1 -1
  175. package/dist/src/terminal-interaction-protocol.d.ts +21 -0
  176. package/dist/src/terminal-interaction-protocol.js +74 -0
  177. package/dist/src/terminal-interaction-protocol.js.map +1 -1
  178. package/dist/src/terminal-interaction-response-bridge.d.ts +126 -0
  179. package/dist/src/terminal-interaction-response-bridge.js +579 -0
  180. package/dist/src/terminal-interaction-response-bridge.js.map +1 -0
  181. package/dist/src/terminal-interaction-response-transaction.d.ts +44 -0
  182. package/dist/src/terminal-interaction-response-transaction.js +56 -0
  183. package/dist/src/terminal-interaction-response-transaction.js.map +1 -0
  184. package/dist/src/terminal-list-action-policy.d.ts +43 -0
  185. package/dist/src/terminal-list-action-policy.js +164 -0
  186. package/dist/src/terminal-list-action-policy.js.map +1 -0
  187. package/dist/src/terminal-list-cli-adapter.d.ts +6 -36
  188. package/dist/src/terminal-list-cli-adapter.js +314 -1711
  189. package/dist/src/terminal-list-cli-adapter.js.map +1 -1
  190. package/dist/src/terminal-list-facts.d.ts +129 -0
  191. package/dist/src/terminal-list-facts.js +179 -0
  192. package/dist/src/terminal-list-facts.js.map +1 -0
  193. package/dist/src/terminal-list-inventory.d.ts +59 -0
  194. package/dist/src/terminal-list-inventory.js +194 -0
  195. package/dist/src/terminal-list-inventory.js.map +1 -0
  196. package/dist/src/terminal-list-ownership-service.d.ts +118 -0
  197. package/dist/src/terminal-list-ownership-service.js +1003 -0
  198. package/dist/src/terminal-list-ownership-service.js.map +1 -0
  199. package/dist/src/terminal-list-projection-service.d.ts +49 -0
  200. package/dist/src/terminal-list-projection-service.js +407 -0
  201. package/dist/src/terminal-list-projection-service.js.map +1 -0
  202. package/dist/src/terminal-list-renderer.d.ts +12 -11
  203. package/dist/src/terminal-list-renderer.js +237 -104
  204. package/dist/src/terminal-list-renderer.js.map +1 -1
  205. package/dist/src/terminal-managed-send-cli-adapter.d.ts +33 -0
  206. package/dist/src/terminal-managed-send-cli-adapter.js +994 -0
  207. package/dist/src/terminal-managed-send-cli-adapter.js.map +1 -0
  208. package/dist/src/terminal-managed-turn-list-action-policy.d.ts +47 -0
  209. package/dist/src/terminal-managed-turn-list-action-policy.js +64 -0
  210. package/dist/src/terminal-managed-turn-list-action-policy.js.map +1 -0
  211. package/dist/src/terminal-model-control-action-policy.d.ts +60 -0
  212. package/dist/src/terminal-model-control-action-policy.js +96 -0
  213. package/dist/src/terminal-model-control-action-policy.js.map +1 -0
  214. package/dist/src/terminal-model-control-availability.d.ts +68 -0
  215. package/dist/src/terminal-model-control-availability.js +157 -0
  216. package/dist/src/terminal-model-control-availability.js.map +1 -0
  217. package/dist/src/terminal-model-control-bridge.d.ts +48 -0
  218. package/dist/src/terminal-model-control-bridge.js +259 -0
  219. package/dist/src/terminal-model-control-bridge.js.map +1 -0
  220. package/dist/src/terminal-model-control-profile.d.ts +55 -0
  221. package/dist/src/terminal-model-control-profile.js +137 -0
  222. package/dist/src/terminal-model-control-profile.js.map +1 -0
  223. package/dist/src/terminal-model-control-subject.d.ts +34 -0
  224. package/dist/src/terminal-model-control-subject.js +58 -0
  225. package/dist/src/terminal-model-control-subject.js.map +1 -0
  226. package/dist/src/terminal-model-control-transaction.d.ts +19 -0
  227. package/dist/src/terminal-model-control-transaction.js +39 -0
  228. package/dist/src/terminal-model-control-transaction.js.map +1 -0
  229. package/dist/src/terminal-model-control.d.ts +331 -0
  230. package/dist/src/terminal-model-control.js +1686 -0
  231. package/dist/src/terminal-model-control.js.map +1 -0
  232. package/dist/src/terminal-monitor-application-service.js +50 -17
  233. package/dist/src/terminal-monitor-application-service.js.map +1 -1
  234. package/dist/src/terminal-native-inspection-bridge.d.ts +211 -0
  235. package/dist/src/terminal-native-inspection-bridge.js +1127 -0
  236. package/dist/src/terminal-native-inspection-bridge.js.map +1 -0
  237. package/dist/src/terminal-runtime-cli-adapter.d.ts +2 -0
  238. package/dist/src/terminal-runtime-cli-adapter.js +59 -17
  239. package/dist/src/terminal-runtime-cli-adapter.js.map +1 -1
  240. package/dist/src/terminal-submission-retry-application.d.ts +24 -0
  241. package/dist/src/terminal-submission-retry-application.js +402 -0
  242. package/dist/src/terminal-submission-retry-application.js.map +1 -0
  243. package/dist/src/terminal-submission-retry-ports.d.ts +30 -0
  244. package/dist/src/terminal-submission-retry-ports.js +2 -0
  245. package/dist/src/terminal-submission-retry-ports.js.map +1 -0
  246. package/dist/src/terminal-submission-retry-reconciliation.d.ts +236 -0
  247. package/dist/src/terminal-submission-retry-reconciliation.js +1130 -0
  248. package/dist/src/terminal-submission-retry-reconciliation.js.map +1 -0
  249. package/dist/src/terminal-submission-retry-transport.d.ts +58 -0
  250. package/dist/src/terminal-submission-retry-transport.js +675 -0
  251. package/dist/src/terminal-submission-retry-transport.js.map +1 -0
  252. package/dist/src/terminal-text-submission-bridge.d.ts +200 -0
  253. package/dist/src/terminal-text-submission-bridge.js +632 -0
  254. package/dist/src/terminal-text-submission-bridge.js.map +1 -0
  255. package/dist/src/terminal-user-explicit-send-policy.d.ts +29 -0
  256. package/dist/src/terminal-user-explicit-send-policy.js +46 -0
  257. package/dist/src/terminal-user-explicit-send-policy.js.map +1 -0
  258. package/dist/src/terminal-watch-cli-adapter.js +91 -143
  259. package/dist/src/terminal-watch-cli-adapter.js.map +1 -1
  260. package/dist/src/terminal-watch-codec.d.ts +9 -0
  261. package/dist/src/terminal-watch-codec.js +1051 -0
  262. package/dist/src/terminal-watch-codec.js.map +1 -0
  263. package/dist/src/terminal-watch-interaction-response-store.d.ts +19 -0
  264. package/dist/src/terminal-watch-interaction-response-store.js +87 -0
  265. package/dist/src/terminal-watch-interaction-response-store.js.map +1 -0
  266. package/dist/src/terminal-watch-record.d.ts +319 -0
  267. package/dist/src/terminal-watch-record.js +742 -0
  268. package/dist/src/terminal-watch-record.js.map +1 -0
  269. package/dist/src/terminal-watch-service.js +48 -27
  270. package/dist/src/terminal-watch-service.js.map +1 -1
  271. package/dist/src/terminal-watch-store.d.ts +3 -279
  272. package/dist/src/terminal-watch-store.js +5 -1779
  273. package/dist/src/terminal-watch-store.js.map +1 -1
  274. package/docs/quickstart-herdr.md +1 -1
  275. package/docs/quickstart-tmux.md +27 -2
  276. package/openclaw.plugin.json +13 -1
  277. package/package.json +7 -2
  278. 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