@scotthuang/agent-knock-knock 0.13.3 → 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 (259) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +13 -5
  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/codex-process-incarnation.d.ts +8 -0
  14. package/dist/src/codex-process-incarnation.js +36 -0
  15. package/dist/src/codex-process-incarnation.js.map +1 -0
  16. package/dist/src/deferred-foreground-conflict-policy.d.ts +22 -0
  17. package/dist/src/deferred-foreground-conflict-policy.js +52 -0
  18. package/dist/src/deferred-foreground-conflict-policy.js.map +1 -0
  19. package/dist/src/durable-notification-kernel.d.ts +94 -0
  20. package/dist/src/durable-notification-kernel.js +68 -0
  21. package/dist/src/durable-notification-kernel.js.map +1 -0
  22. package/dist/src/herdr-socket-transport.d.ts +43 -0
  23. package/dist/src/herdr-socket-transport.js +170 -0
  24. package/dist/src/herdr-socket-transport.js.map +1 -0
  25. package/dist/src/herdr-terminal-control-provider.d.ts +2 -45
  26. package/dist/src/herdr-terminal-control-provider.js +2 -172
  27. package/dist/src/herdr-terminal-control-provider.js.map +1 -1
  28. package/dist/src/host-adapter-capabilities.d.ts +35 -0
  29. package/dist/src/host-adapter-capabilities.js +146 -0
  30. package/dist/src/host-adapter-capabilities.js.map +1 -0
  31. package/dist/src/host-adapter.d.ts +9 -3
  32. package/dist/src/host-adapter.js +33 -9
  33. package/dist/src/host-adapter.js.map +1 -1
  34. package/dist/src/host-bridge-tools.d.ts +3 -4
  35. package/dist/src/host-bridge-tools.js +49 -92
  36. package/dist/src/host-bridge-tools.js.map +1 -1
  37. package/dist/src/host-bridge.js +5 -5
  38. package/dist/src/host-bridge.js.map +1 -1
  39. package/dist/src/host-monitor-reconciliation.d.ts +6 -0
  40. package/dist/src/host-monitor-reconciliation.js +96 -0
  41. package/dist/src/host-monitor-reconciliation.js.map +1 -0
  42. package/dist/src/native-thread-lifecycle-cli-adapter.js +116 -60
  43. package/dist/src/native-thread-lifecycle-cli-adapter.js.map +1 -1
  44. package/dist/src/openclaw-list-projection.d.ts +2 -0
  45. package/dist/src/openclaw-list-projection.js +3 -0
  46. package/dist/src/openclaw-list-projection.js.map +1 -0
  47. package/dist/src/openclaw-model-facing-field-policy.d.ts +2 -0
  48. package/dist/src/openclaw-model-facing-field-policy.js +3 -0
  49. package/dist/src/openclaw-model-facing-field-policy.js.map +1 -0
  50. package/dist/src/openclaw-plugin-command-adapter.d.ts +6 -29
  51. package/dist/src/openclaw-plugin-command-adapter.js +7 -2660
  52. package/dist/src/openclaw-plugin-command-adapter.js.map +1 -1
  53. package/dist/src/openclaw-plugin-helpers.d.ts +2 -148
  54. package/dist/src/openclaw-plugin-helpers.js +2 -1844
  55. package/dist/src/openclaw-plugin-helpers.js.map +1 -1
  56. package/dist/src/openclaw-plugin-schemas.d.ts +2 -563
  57. package/dist/src/openclaw-plugin-schemas.js +2 -572
  58. package/dist/src/openclaw-plugin-schemas.js.map +1 -1
  59. package/dist/src/openclaw-plugin-supervisor.d.ts +4 -5
  60. package/dist/src/openclaw-plugin-supervisor.js +5 -94
  61. package/dist/src/openclaw-plugin-supervisor.js.map +1 -1
  62. package/dist/src/openclaw-plugin-value-helpers.d.ts +2 -0
  63. package/dist/src/openclaw-plugin-value-helpers.js +3 -0
  64. package/dist/src/openclaw-plugin-value-helpers.js.map +1 -0
  65. package/dist/src/openclaw-private-authority-offers.d.ts +4 -39
  66. package/dist/src/openclaw-private-authority-offers.js +3 -258
  67. package/dist/src/openclaw-private-authority-offers.js.map +1 -1
  68. package/dist/src/semantic-private-authority-offers.d.ts +41 -0
  69. package/dist/src/semantic-private-authority-offers.js +261 -0
  70. package/dist/src/semantic-private-authority-offers.js.map +1 -0
  71. package/dist/src/semantic-tool-arguments.d.ts +9 -0
  72. package/dist/src/semantic-tool-arguments.js +64 -0
  73. package/dist/src/semantic-tool-arguments.js.map +1 -0
  74. package/dist/src/semantic-tool-catalog.d.ts +48 -0
  75. package/dist/src/semantic-tool-catalog.js +105 -0
  76. package/dist/src/semantic-tool-catalog.js.map +1 -0
  77. package/dist/src/semantic-tool-command-helpers.d.ts +145 -0
  78. package/dist/src/semantic-tool-command-helpers.js +1272 -0
  79. package/dist/src/semantic-tool-command-helpers.js.map +1 -0
  80. package/dist/src/semantic-tool-list-projection.d.ts +10 -0
  81. package/dist/src/semantic-tool-list-projection.js +497 -0
  82. package/dist/src/semantic-tool-list-projection.js.map +1 -0
  83. package/dist/src/semantic-tool-model-facing-field-policy.d.ts +3 -0
  84. package/dist/src/semantic-tool-model-facing-field-policy.js +66 -0
  85. package/dist/src/semantic-tool-model-facing-field-policy.js.map +1 -0
  86. package/dist/src/semantic-tool-presentation.d.ts +37 -0
  87. package/dist/src/semantic-tool-presentation.js +680 -0
  88. package/dist/src/semantic-tool-presentation.js.map +1 -0
  89. package/dist/src/semantic-tool-private-authority.d.ts +39 -0
  90. package/dist/src/semantic-tool-private-authority.js +602 -0
  91. package/dist/src/semantic-tool-private-authority.js.map +1 -0
  92. package/dist/src/semantic-tool-relay.d.ts +16 -0
  93. package/dist/src/semantic-tool-relay.js +175 -0
  94. package/dist/src/semantic-tool-relay.js.map +1 -0
  95. package/dist/src/semantic-tool-runtime.d.ts +10 -0
  96. package/dist/src/semantic-tool-runtime.js +1157 -0
  97. package/dist/src/semantic-tool-runtime.js.map +1 -0
  98. package/dist/src/semantic-tool-schemas.d.ts +563 -0
  99. package/dist/src/semantic-tool-schemas.js +573 -0
  100. package/dist/src/semantic-tool-schemas.js.map +1 -0
  101. package/dist/src/semantic-tool-value-helpers.d.ts +5 -0
  102. package/dist/src/semantic-tool-value-helpers.js +27 -0
  103. package/dist/src/semantic-tool-value-helpers.js.map +1 -0
  104. package/dist/src/store-event-presentation.d.ts +17 -0
  105. package/dist/src/store-event-presentation.js +33 -0
  106. package/dist/src/store-event-presentation.js.map +1 -0
  107. package/dist/src/store.d.ts +3 -16
  108. package/dist/src/store.js +2 -30
  109. package/dist/src/store.js.map +1 -1
  110. package/dist/src/terminal-action-projection.d.ts +2 -98
  111. package/dist/src/terminal-action-projection.js +13 -215
  112. package/dist/src/terminal-action-projection.js.map +1 -1
  113. package/dist/src/terminal-agent-bridge.d.ts +14 -340
  114. package/dist/src/terminal-agent-bridge.js +155 -3226
  115. package/dist/src/terminal-agent-bridge.js.map +1 -1
  116. package/dist/src/terminal-approval-cli-adapter.d.ts +17 -0
  117. package/dist/src/terminal-approval-cli-adapter.js +921 -0
  118. package/dist/src/terminal-approval-cli-adapter.js.map +1 -0
  119. package/dist/src/terminal-command-cli-adapter.d.ts +2 -429
  120. package/dist/src/terminal-command-cli-adapter.js +344 -6061
  121. package/dist/src/terminal-command-cli-adapter.js.map +1 -1
  122. package/dist/src/terminal-command-cli-ports.d.ts +443 -0
  123. package/dist/src/terminal-command-cli-ports.js +2 -0
  124. package/dist/src/terminal-command-cli-ports.js.map +1 -0
  125. package/dist/src/terminal-command-dispatch-preparation.d.ts +81 -0
  126. package/dist/src/terminal-command-dispatch-preparation.js +209 -0
  127. package/dist/src/terminal-command-dispatch-preparation.js.map +1 -0
  128. package/dist/src/terminal-command-dispatch-transport.d.ts +35 -0
  129. package/dist/src/terminal-command-dispatch-transport.js +859 -0
  130. package/dist/src/terminal-command-dispatch-transport.js.map +1 -0
  131. package/dist/src/terminal-command-foreground-proof.d.ts +32 -0
  132. package/dist/src/terminal-command-foreground-proof.js +72 -0
  133. package/dist/src/terminal-command-foreground-proof.js.map +1 -0
  134. package/dist/src/terminal-command-send-preflight.d.ts +41 -0
  135. package/dist/src/terminal-command-send-preflight.js +86 -0
  136. package/dist/src/terminal-command-send-preflight.js.map +1 -0
  137. package/dist/src/terminal-composer-classifier.d.ts +55 -0
  138. package/dist/src/terminal-composer-classifier.js +638 -0
  139. package/dist/src/terminal-composer-classifier.js.map +1 -0
  140. package/dist/src/terminal-dispatch-recovery-authority.d.ts +56 -0
  141. package/dist/src/terminal-dispatch-recovery-authority.js +251 -0
  142. package/dist/src/terminal-dispatch-recovery-authority.js.map +1 -0
  143. package/dist/src/terminal-dispatch-recovery-cli-adapter.d.ts +1 -9
  144. package/dist/src/terminal-dispatch-recovery-cli-adapter.js +5 -240
  145. package/dist/src/terminal-dispatch-recovery-cli-adapter.js.map +1 -1
  146. package/dist/src/terminal-handoff-cli-adapter.d.ts +1 -11
  147. package/dist/src/terminal-handoff-cli-adapter.js +2 -39
  148. package/dist/src/terminal-handoff-cli-adapter.js.map +1 -1
  149. package/dist/src/terminal-human-explicit-send-cli-adapter.d.ts +25 -0
  150. package/dist/src/terminal-human-explicit-send-cli-adapter.js +903 -0
  151. package/dist/src/terminal-human-explicit-send-cli-adapter.js.map +1 -0
  152. package/dist/src/terminal-identity-authority-cli-adapter.d.ts +1 -6
  153. package/dist/src/terminal-identity-authority-cli-adapter.js +3 -33
  154. package/dist/src/terminal-identity-authority-cli-adapter.js.map +1 -1
  155. package/dist/src/terminal-interaction-cli-adapter.js +209 -210
  156. package/dist/src/terminal-interaction-cli-adapter.js.map +1 -1
  157. package/dist/src/terminal-interaction-core.d.ts +0 -2
  158. package/dist/src/terminal-interaction-core.js +4 -3
  159. package/dist/src/terminal-interaction-core.js.map +1 -1
  160. package/dist/src/terminal-interaction-protocol.d.ts +21 -0
  161. package/dist/src/terminal-interaction-protocol.js +74 -0
  162. package/dist/src/terminal-interaction-protocol.js.map +1 -1
  163. package/dist/src/terminal-interaction-response-bridge.d.ts +126 -0
  164. package/dist/src/terminal-interaction-response-bridge.js +579 -0
  165. package/dist/src/terminal-interaction-response-bridge.js.map +1 -0
  166. package/dist/src/terminal-interaction-response-transaction.d.ts +44 -0
  167. package/dist/src/terminal-interaction-response-transaction.js +56 -0
  168. package/dist/src/terminal-interaction-response-transaction.js.map +1 -0
  169. package/dist/src/terminal-list-action-policy.d.ts +43 -0
  170. package/dist/src/terminal-list-action-policy.js +164 -0
  171. package/dist/src/terminal-list-action-policy.js.map +1 -0
  172. package/dist/src/terminal-list-cli-adapter.d.ts +6 -36
  173. package/dist/src/terminal-list-cli-adapter.js +241 -1946
  174. package/dist/src/terminal-list-cli-adapter.js.map +1 -1
  175. package/dist/src/terminal-list-facts.d.ts +129 -0
  176. package/dist/src/terminal-list-facts.js +179 -0
  177. package/dist/src/terminal-list-facts.js.map +1 -0
  178. package/dist/src/terminal-list-inventory.d.ts +59 -0
  179. package/dist/src/terminal-list-inventory.js +194 -0
  180. package/dist/src/terminal-list-inventory.js.map +1 -0
  181. package/dist/src/terminal-list-ownership-service.d.ts +118 -0
  182. package/dist/src/terminal-list-ownership-service.js +1003 -0
  183. package/dist/src/terminal-list-ownership-service.js.map +1 -0
  184. package/dist/src/terminal-list-projection-service.d.ts +49 -0
  185. package/dist/src/terminal-list-projection-service.js +407 -0
  186. package/dist/src/terminal-list-projection-service.js.map +1 -0
  187. package/dist/src/terminal-list-renderer.d.ts +11 -11
  188. package/dist/src/terminal-list-renderer.js +168 -119
  189. package/dist/src/terminal-list-renderer.js.map +1 -1
  190. package/dist/src/terminal-managed-send-cli-adapter.d.ts +33 -0
  191. package/dist/src/terminal-managed-send-cli-adapter.js +994 -0
  192. package/dist/src/terminal-managed-send-cli-adapter.js.map +1 -0
  193. package/dist/src/terminal-managed-turn-list-action-policy.d.ts +47 -0
  194. package/dist/src/terminal-managed-turn-list-action-policy.js +64 -0
  195. package/dist/src/terminal-managed-turn-list-action-policy.js.map +1 -0
  196. package/dist/src/terminal-model-control-action-policy.d.ts +60 -0
  197. package/dist/src/terminal-model-control-action-policy.js +96 -0
  198. package/dist/src/terminal-model-control-action-policy.js.map +1 -0
  199. package/dist/src/terminal-model-control-availability.d.ts +68 -0
  200. package/dist/src/terminal-model-control-availability.js +157 -0
  201. package/dist/src/terminal-model-control-availability.js.map +1 -0
  202. package/dist/src/terminal-model-control-bridge.d.ts +48 -0
  203. package/dist/src/terminal-model-control-bridge.js +259 -0
  204. package/dist/src/terminal-model-control-bridge.js.map +1 -0
  205. package/dist/src/terminal-model-control-profile.d.ts +55 -0
  206. package/dist/src/terminal-model-control-profile.js +137 -0
  207. package/dist/src/terminal-model-control-profile.js.map +1 -0
  208. package/dist/src/terminal-model-control-subject.d.ts +34 -0
  209. package/dist/src/terminal-model-control-subject.js +58 -0
  210. package/dist/src/terminal-model-control-subject.js.map +1 -0
  211. package/dist/src/terminal-model-control-transaction.d.ts +19 -0
  212. package/dist/src/terminal-model-control-transaction.js +39 -0
  213. package/dist/src/terminal-model-control-transaction.js.map +1 -0
  214. package/dist/src/terminal-model-control.d.ts +37 -18
  215. package/dist/src/terminal-model-control.js +285 -194
  216. package/dist/src/terminal-model-control.js.map +1 -1
  217. package/dist/src/terminal-monitor-application-service.js +50 -17
  218. package/dist/src/terminal-monitor-application-service.js.map +1 -1
  219. package/dist/src/terminal-native-inspection-bridge.d.ts +211 -0
  220. package/dist/src/terminal-native-inspection-bridge.js +1127 -0
  221. package/dist/src/terminal-native-inspection-bridge.js.map +1 -0
  222. package/dist/src/terminal-runtime-cli-adapter.d.ts +2 -2
  223. package/dist/src/terminal-runtime-cli-adapter.js.map +1 -1
  224. package/dist/src/terminal-submission-retry-application.d.ts +24 -0
  225. package/dist/src/terminal-submission-retry-application.js +402 -0
  226. package/dist/src/terminal-submission-retry-application.js.map +1 -0
  227. package/dist/src/terminal-submission-retry-ports.d.ts +30 -0
  228. package/dist/src/terminal-submission-retry-ports.js +2 -0
  229. package/dist/src/terminal-submission-retry-ports.js.map +1 -0
  230. package/dist/src/terminal-submission-retry-reconciliation.d.ts +236 -0
  231. package/dist/src/terminal-submission-retry-reconciliation.js +1130 -0
  232. package/dist/src/terminal-submission-retry-reconciliation.js.map +1 -0
  233. package/dist/src/terminal-submission-retry-transport.d.ts +58 -0
  234. package/dist/src/terminal-submission-retry-transport.js +675 -0
  235. package/dist/src/terminal-submission-retry-transport.js.map +1 -0
  236. package/dist/src/terminal-text-submission-bridge.d.ts +200 -0
  237. package/dist/src/terminal-text-submission-bridge.js +632 -0
  238. package/dist/src/terminal-text-submission-bridge.js.map +1 -0
  239. package/dist/src/terminal-user-explicit-send-policy.d.ts +29 -0
  240. package/dist/src/terminal-user-explicit-send-policy.js +46 -0
  241. package/dist/src/terminal-user-explicit-send-policy.js.map +1 -0
  242. package/dist/src/terminal-watch-cli-adapter.js +91 -143
  243. package/dist/src/terminal-watch-cli-adapter.js.map +1 -1
  244. package/dist/src/terminal-watch-codec.d.ts +9 -0
  245. package/dist/src/terminal-watch-codec.js +1051 -0
  246. package/dist/src/terminal-watch-codec.js.map +1 -0
  247. package/dist/src/terminal-watch-interaction-response-store.d.ts +19 -0
  248. package/dist/src/terminal-watch-interaction-response-store.js +87 -0
  249. package/dist/src/terminal-watch-interaction-response-store.js.map +1 -0
  250. package/dist/src/terminal-watch-record.d.ts +319 -0
  251. package/dist/src/terminal-watch-record.js +742 -0
  252. package/dist/src/terminal-watch-record.js.map +1 -0
  253. package/dist/src/terminal-watch-service.js +48 -27
  254. package/dist/src/terminal-watch-service.js.map +1 -1
  255. package/dist/src/terminal-watch-store.d.ts +3 -279
  256. package/dist/src/terminal-watch-store.js +5 -1779
  257. package/dist/src/terminal-watch-store.js.map +1 -1
  258. package/package.json +7 -2
  259. package/templates/openclaw-skills/agent-knock-knock/SKILL.md +1 -1
@@ -1,2663 +1,10 @@
1
- import { spawn, spawnSync } from "node:child_process";
2
- import { AsyncLocalStorage } from "node:async_hooks";
3
- import { createHash, randomUUID } from "node:crypto";
4
- import { fileURLToPath } from "node:url";
5
- import { executorDefinitionForKind } from "./executors.js";
6
- import { isTerminalApprovalDecision } from "./terminal-agent-adapter.js";
7
- import { isRecord, nonBlankString as stringValue } from "./value-guards.js";
8
- import { AKK_CALLBACK_METHOD, akkUsageText, buildAkkCommandCliArgs, compactAkkListModelProjection, formatAkkListCommandResult, formatAkkModelOptionsCommandResult, formatAkkRepairModelControlCommandResult, formatAkkRespondCommandResult, formatAkkSetModelCommandResult, formatAkkTerminalWatchHint, formatAkkThreadsCommandResult, formatAkkThreadTransitionCommandResult, formatAkkUnwatchCommandResult, formatAkkWatchCommandResult, formatAkkWatchStatusCommandResult, isAkkModelFacingDiagnosticField, isAkkModelFacingPrivateAuthorityField, isAkkNativeSubmissionAccepted, isAkkRepairModelControlSuccess, isAkkSetModelSuccess, isAkkThreadTransitionSuccess, normalizeAkkModelFacingFieldName, parseAkkCommand, resolvePluginStoreDir, sanitizeAkkModelFacingDiagnosticText, sanitizeAkkModelFacingLegacyAuthorityInstructionText } from "./openclaw-plugin-helpers.js";
9
- import { approveParameters, cancelParameters, closeParameters, identifyAndSendParameters, identifyForegroundParameters, listParameters, listResumableThreadsParameters, modelOptionsParameters, nativeInspectParameters, newThreadParameters, reconcileBindingParameters, repairModelControlParameters, renewParameters, respondInteractionParameters, respondParameters, resumeThreadParameters, retryCallbackParameters, sendParameters, setModelParameters, statusParameters, unwatchParameters, watchParameters } from "./openclaw-plugin-schemas.js";
10
- import { consumeOpenClawPrivateAuthorityOffer, invalidateOpenClawInteractionAuthorityOffersForSubject, openClawApprovalAuthorityOfferKey, openClawInteractionAuthorityOfferKey, rememberOpenClawPrivateAuthorityOffer } from "./openclaw-private-authority-offers.js";
11
- import { TERMINAL_INTERACTION_LIMITS, TERMINAL_INTERACTION_SUBJECT_VERSION, terminalInteractionSubjectId, validateAnyTerminalInteractionProjection, validateAnyTerminalInteractionResponse } from "./terminal-interaction-protocol.js";
12
- import { normalizedTerminalSendResultContract, terminalSendEnterDispatched } from "./terminal-dispatch-presenter.js";
13
- const MAX_DISPLAYED_RESUME_SNAPSHOTS = 512;
14
- const OPENCLAW_HANDOFF_AUTHORITY_KIND = "handoff";
15
- const OPENCLAW_RECONCILE_BINDING_AUTHORITY_KIND = "reconcile_binding";
16
- const OPENCLAW_MODEL_OPTIONS_AUTHORITY_KIND = "model_options";
17
- // Model discovery intentionally revalidates every native menu transition.
18
- // Claude's live effort-ring walk can exceed the generic 90-second relay
19
- // budget, while set-model performs discovery both before and after its one
20
- // commit. Keep these operations bounded without killing the child midway
21
- // through a verified native picker. The Host abort signal still cancels them.
22
- const OPENCLAW_MODEL_OPTIONS_CLI_TIMEOUT_MS = 15 * 60_000;
23
- const OPENCLAW_REPAIR_MODEL_CONTROL_CLI_TIMEOUT_MS = 2 * 60_000;
24
- const OPENCLAW_SET_MODEL_CLI_TIMEOUT_MS = 30 * 60_000;
25
- const CALLBACK_METHOD = AKK_CALLBACK_METHOD;
26
- export const defaultOpenClawRelayPath = fileURLToPath(new URL("./cli.js", import.meta.url));
27
- const relayPathByApi = new WeakMap();
28
- const relayEnvironmentByApi = new WeakMap();
29
- const hostBridgePresentationApis = new WeakSet();
30
- const hostBridgeAsyncRelayApis = new WeakSet();
31
- const hostBridgeInvocationStorage = new AsyncLocalStorage();
32
- export function bindOpenClawRelayPath(api, relayPath) {
33
- relayPathByApi.set(api, relayPath);
34
- }
35
- /** Bind a private child-process environment for a Host adapter instance. */
36
- export function bindOpenClawRelayEnvironment(api, environment) {
37
- relayEnvironmentByApi.set(api, Object.freeze({ ...environment }));
38
- }
39
- /** Keep one shared tool implementation while selecting Host-neutral output. */
40
- export function bindHostBridgeToolPresentation(api) {
41
- hostBridgePresentationApis.add(api);
42
- }
43
- /**
44
- * Keep an embedding Host's event loop responsive while AKK CLI work runs.
45
- *
46
- * Every in-process Host, including the OpenClaw Gateway plugin, must opt into
47
- * the asynchronous child-process runner before registering AKK tools.
48
- */
49
- export function bindHostBridgeAsyncRelay(api) {
50
- hostBridgeAsyncRelayApis.add(api);
51
- }
52
- /** Scope one Host invocation's cancellation without sharing mutable state. */
53
- export function withHostBridgeInvocationSignal(signal, operation) {
54
- const effectiveSignal = signal ??
55
- hostBridgeInvocationStorage.getStore()?.signal;
56
- if (effectiveSignal?.aborted) {
57
- return Promise.reject(hostBridgeAbortError());
58
- }
59
- return hostBridgeInvocationStorage.run({ signal: effectiveSignal }, async () => operation());
60
- }
1
+ import { registerSemanticToolCatalog } from "./semantic-tool-catalog.js";
2
+ import { createAkkSemanticToolCatalog } from "./semantic-tool-runtime.js";
3
+ export { bindHostBridgeToolPresentation } from "./semantic-tool-runtime.js";
4
+ export { bindSemanticToolAsyncRelay as bindHostBridgeAsyncRelay, bindSemanticToolRelayEnvironment as bindOpenClawRelayEnvironment, bindSemanticToolRelayPath as bindOpenClawRelayPath, defaultSemanticToolRelayPath as defaultOpenClawRelayPath, runCli, runCliAsync, withHostBridgeInvocationSignal } from "./semantic-tool-relay.js";
5
+ export { pushOptional } from "./semantic-tool-runtime.js";
6
+ /** Adapt the host-neutral AKK catalog to OpenClaw's registration API. */
61
7
  export function registerOpenClawCommands(api, displayedResumeSnapshots) {
62
- api.registerCommand?.({
63
- name: "akk",
64
- description: "Send coding work through existing Codex or Claude Code shared terminals, inspect managed Turns, observe a user-selected terminal with durable read-only Terminal Watch, manage native threads, and safely inspect or change an idle pane's native model selection.",
65
- acceptsArgs: true,
66
- requireAuth: true,
67
- nativeProgressMessages: {
68
- default: "AKK is handling the request..."
69
- },
70
- agentPromptGuidance: [
71
- "Use /akk <task> when exactly one send-ready coding-agent terminal pane should receive new work. Send-ready means an exact live process and terminal plus a scanned, non-blocked approval state. Parsed working activity and ordinary Codex main-Composer visibility, stability, or exactness do not veto this user-priority path. A proven input-owning native approval, questionnaire/editor, or read-only viewer remains a zero-input boundary; Codex 0.154's exact collapsed async-question summary remains sendable, while its expanded, clipped, or ambiguous editor does not. Codex terminal_user_explicit physical fallback sends C-u once to replace the current Composer, injects the request, waits through the paste window, and dispatches Enter exactly once; after text injection, no Composer observation may veto Enter. Claude Code user-explicit Send remains exact-empty-only. Managed Send may still require exact empty before input, while native inspection and native lifecycle input remain exact-empty-only. Broken or stale AKK management activity records do not veto the user's physical Send. Structured tools use only semantic identifiers returned by AKK: session_id for an exact managed context, terminal_id for the currently verified pane, turn_id for one managed Turn, watch_id for one Terminal Watch, and native_thread_id for one resumable native thread. Draft text, composer digests, and opaque freshness authority stay private; AKK revalidates them under its locks. Once the Codex mutation sequence begins, an uncertain result must not be automatically retried. /akk watch is read-only and follows user intent: it prefers an exact task anchor, but version, artifact, managed ownership, and action-advertisement uncertainty degrade to a warning-bearing terminal-activity Watch instead of vetoing the request. New/clear/resume, approval, reconciliation, handoff, and recovery still require the documented user intent or explicit confirmation. AKK never starts a coding-agent process.",
72
- "Use /akk models on one exact currently advertised physical pane before /akk set-model. Codex 0.154 may use either one exact current native Session or a verified-zero-rollout pane; identify_foreground is diagnostic and is never a prerequisite for that zero-rollout path. Claude Code still requires one exact current native Session. Both steps require no active Turn and no approval, questionnaire/editor, or read-only viewer. model_options normally requires an empty Composer; when List binds it to one exact stable Codex /model residual, it may continue only that residual into read-only catalog discovery. repair_model_control remains the separate clear-only alternative and never presses Enter or selects anything. Only ids and reasoning efforts from that current native catalog are valid. Codex changes the current session and persists the selected model for future sessions; ordinary efforts, including max, are also persisted, while ultra remains current-session-only and Codex chooses a non-ultra future fallback. Claude Code changes only the current session. Read effective and new_session_defaults separately. Model control never accepts slash text, raw keys, menu indexes, display labels, scope overrides, or private authority; an uncertain outcome must not be retried automatically."
73
- ],
74
- handler: async (ctx) => handleAkkCommand(api, ctx, displayedResumeSnapshots)
75
- });
76
- registerOpenClawListTool(api);
77
- registerCliTool(api, {
78
- name: "agent_knock_knock_watch",
79
- description: "Start one durable read-only Terminal Watch for the user's exact selected Codex or Claude Code terminal. AKK prefers an exact durable task anchor; if version, artifact, task, managed-ownership, or action-advertisement evidence is unavailable, it remains callable and returns warnings while using best-effort terminal activity. That fallback reports stable-idle activity, not proof of exact task completion. Watch creates no AKK Session or Turn, sends no terminal input, and never adopts or blocks the terminal task.",
80
- parameters: watchParameters,
81
- normalizeTurnIdentity: false,
82
- buildArgs: (params, toolContext) => {
83
- const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
84
- const openclawSession = stringValue(toolContext?.sessionKey) ??
85
- "agent:main:main";
86
- const args = [
87
- "watch-terminal",
88
- "--terminal",
89
- requiredString(params.terminal_id, "terminal_id")
90
- ];
91
- pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
92
- pushOptional(args, "--hard-timeout-minutes", numberString(params.hardTimeoutMinutes) ??
93
- numberString(config.agentHardTimeoutMinutes));
94
- pushOptional(args, "--openclaw-session", openclawSession);
95
- pushOptional(args, "--openclaw-bin", stringValue(config.openclawBin));
96
- return args;
97
- }
98
- });
99
- registerCliTool(api, {
100
- name: "agent_knock_knock_unwatch",
101
- description: "Stop one exact durable Terminal Watch by its authoritative watch_id. This cancels observation only; it sends no terminal input and does not interrupt, adopt, or otherwise mutate the human's coding-agent task.",
102
- parameters: unwatchParameters,
103
- normalizeTurnIdentity: false,
104
- buildArgs: (params) => {
105
- const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
106
- const args = [
107
- "unwatch-terminal",
108
- "--watch",
109
- requiredString(params.watch_id, "watch_id")
110
- ];
111
- pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
112
- return args;
113
- }
114
- });
115
- registerCliTool(api, {
116
- name: "agent_knock_knock_list_resumable_threads",
117
- description: "List structurally verified native Codex or Claude Code threads for one exact terminal. A valid unverified agent version remains callable with a compatibility warning. Resume only a row with resumable=true by passing this terminal_id and that row's complete native_thread_id. AKK retains candidate and binding freshness evidence privately. Number and short-id fields are slash-command display aids, never tool arguments. This is read-only for Session/Turn state and creates no AKK Turn.",
118
- parameters: listResumableThreadsParameters,
119
- normalizeTurnIdentity: false,
120
- buildArgs: (params) => {
121
- const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
122
- const args = [
123
- "list-resumable-threads",
124
- "--terminal",
125
- requiredString(params.terminal_id, "terminal_id")
126
- ];
127
- pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
128
- pushOptional(args, "--codex-home", stringValue(config.codexHome));
129
- return args;
130
- }
131
- });
132
- registerCliTool(api, {
133
- name: "agent_knock_knock_native_inspect",
134
- description: "Execute one closed native status inspection in an exact Codex or Claude Code terminal. Verified versions use their regression-tested profile; unverified complete x.y.z versions remain callable through the generic runtime protocol and return a compatibility warning. Pass only terminal_id and inspection=status; AKK refreshes binding authority privately. Arbitrary slash commands remain unavailable. This creates no AKK Session, Turn, receipt, monitor, or callback.",
135
- parameters: nativeInspectParameters,
136
- normalizeTurnIdentity: false,
137
- buildArgs: async (params) => {
138
- const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
139
- const inspection = requiredString(params.inspection, "inspection");
140
- if (inspection !== "status") {
141
- throw new Error("inspection must be status");
142
- }
143
- const terminalId = requiredString(params.terminal_id, "terminal_id");
144
- const action = await privateTerminalActionArguments(api, terminalId, "agent_knock_knock_native_inspect");
145
- const args = [
146
- "native-inspect",
147
- "--terminal",
148
- terminalId,
149
- "--inspection",
150
- inspection,
151
- "--expected-binding-token",
152
- requiredString(action.expected_binding_token, "current internal native-inspect binding authority")
153
- ];
154
- pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
155
- pushOptional(args, "--codex-home", stringValue(config.codexHome));
156
- return args;
157
- }
158
- });
159
- registerModelControlTools(api);
160
- registerForegroundIdentificationTools(api);
161
- registerCliTool(api, {
162
- name: "agent_knock_knock_new_thread",
163
- description: "Start and verify a clean native coding-agent thread in the exact terminal_id after explicit user intent. A valid unverified agent version remains callable with a compatibility warning. AKK refreshes lifecycle authority privately. Never send /clear as ordinary task text. This creates a new AKK Session but no Turn.",
164
- parameters: newThreadParameters,
165
- normalizeTurnIdentity: false,
166
- isErrorResult: (result) => !isAkkThreadTransitionSuccess(result),
167
- buildArgs: async (params) => {
168
- const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
169
- const terminalId = requiredString(params.terminal_id, "terminal_id");
170
- const action = await privateTerminalActionArguments(api, terminalId, "agent_knock_knock_new_thread");
171
- const args = [
172
- "new-thread",
173
- "--terminal",
174
- terminalId,
175
- "--expected-binding-token",
176
- requiredString(action.expected_binding_token, "current internal new-thread binding authority")
177
- ];
178
- pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
179
- pushOptional(args, "--codex-home", stringValue(config.codexHome));
180
- return args;
181
- }
182
- });
183
- registerCliTool(api, {
184
- name: "agent_knock_knock_reconcile_binding",
185
- description: "Detach one exact conflicting managed Session binding without adopting the live replacement thread. Pass only the advertised terminal_id and conflicting_session_id after explicit user confirmation; AKK refreshes all revision and binding authority privately. This sends no coding-agent input and creates no Turn.",
186
- parameters: reconcileBindingParameters,
187
- normalizeTurnIdentity: false,
188
- buildArgs: async (params, toolContext) => {
189
- const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
190
- const terminalId = requiredString(params.terminal_id, "terminal_id");
191
- const conflictingSessionId = requiredString(params.conflicting_session_id, "conflicting_session_id");
192
- const action = await consumeDisplayedPrivateAction(api, {
193
- sessionKey: requiredOpenClawSessionKey(toolContext?.sessionKey),
194
- sessionId: requiredOpenClawSessionId(toolContext?.sessionId),
195
- kind: OPENCLAW_RECONCILE_BINDING_AUTHORITY_KIND,
196
- target: { type: "terminal_id", id: terminalId },
197
- tool: "agent_knock_knock_reconcile_binding",
198
- terminalId,
199
- matches: (argumentsValue) => stringValue(argumentsValue.terminal_id) === terminalId &&
200
- stringValue(argumentsValue.conflicting_session_id) ===
201
- conflictingSessionId
202
- });
203
- const revision = Number(action.expected_session_revision);
204
- if (!Number.isSafeInteger(revision) || revision < 1) {
205
- throw new Error("expected_session_revision must be a positive safe integer");
206
- }
207
- const args = [
208
- "reconcile-binding",
209
- "--terminal",
210
- terminalId,
211
- "--conflicting-session",
212
- requiredString(conflictingSessionId, "conflicting_session_id"),
213
- "--expected-session-revision",
214
- String(revision),
215
- "--expected-binding-token",
216
- requiredString(action.expected_binding_token, "current internal conflicting binding authority"),
217
- "--expected-terminal-token",
218
- requiredString(action.expected_terminal_token, "current internal terminal authority")
219
- ];
220
- pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
221
- pushOptional(args, "--codex-home", stringValue(config.codexHome));
222
- return args;
223
- }
224
- });
225
- registerCliTool(api, {
226
- name: "agent_knock_knock_resume_thread",
227
- description: "Resume one exact structurally verified historical native thread after explicit user intent. A valid unverified agent version remains callable with a compatibility warning. Pass only terminal_id and one complete native_thread_id from a resumable=true row; AKK refreshes binding and candidate evidence privately. This creates or reactivates an AKK Session but no Turn.",
228
- parameters: resumeThreadParameters,
229
- normalizeTurnIdentity: false,
230
- isErrorResult: (result) => !isAkkThreadTransitionSuccess(result),
231
- buildArgs: async (params) => {
232
- const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
233
- const terminalId = requiredString(params.terminal_id, "terminal_id");
234
- const nativeThreadId = requiredString(params.native_thread_id, "native_thread_id");
235
- const discovery = await privateThreadDiscovery(api, terminalId);
236
- const candidate = Array.isArray(discovery.threads)
237
- ? discovery.threads.find((thread) => isRecord(thread) &&
238
- thread.resumable === true &&
239
- stringValue(thread.native_thread_id) === nativeThreadId)
240
- : undefined;
241
- if (!isRecord(candidate)) {
242
- throw new Error(`native thread ${nativeThreadId} is not resumable in the current lifecycle snapshot`);
243
- }
244
- const args = [
245
- "resume-thread",
246
- "--terminal",
247
- terminalId,
248
- "--native-thread",
249
- nativeThreadId,
250
- "--expected-binding-token",
251
- requiredString(discovery.expected_binding_token, "current internal lifecycle binding authority"),
252
- "--candidate-token",
253
- requiredString(candidate.candidate_token, "current internal resume candidate authority")
254
- ];
255
- pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
256
- pushOptional(args, "--codex-home", stringValue(config.codexHome));
257
- return args;
258
- }
259
- });
260
- api.registerTool((toolContext) => ({
261
- label: "AKK Status",
262
- name: "agent_knock_knock_status",
263
- description: "Inspect one exact AKK-managed Turn by its authoritative turn_id, one durable Terminal Watch by its authoritative watch_id, or use only a raw terminal row's own prefilled compatibility selector. These targets are mutually exclusive. The deprecated conversation_id remains a legacy Turn alias and the list-prefilled raw-terminal input; never construct it. User-selected Watch status reports whether it uses an exact task anchor or best-effort terminal activity without implying Watch sent or adopted the task; that task may independently be managed. Automatic terminal_user_explicit fallback Watch status describes the exact request AKK physically sent without claiming a managed Turn. AKK never starts a coding agent.",
264
- parameters: statusParameters,
265
- async execute(_toolCallId, params, signal) {
266
- return withHostBridgeInvocationSignal(signal, async () => {
267
- try {
268
- const result = await runStatusRequest(api, params, toolContext);
269
- const rendered = toolResult(result);
270
- rememberDisplayedApprovalOffer(api, toolContext?.sessionKey, toolContext?.sessionId, result);
271
- rememberDisplayedInteractionOffer(api, toolContext?.sessionKey, toolContext?.sessionId, result);
272
- return rendered;
273
- }
274
- catch (error) {
275
- throw modelFacingToolError(error);
276
- }
277
- });
278
- }
279
- }), { name: "agent_knock_knock_status", optional: true });
280
- api.registerTool((toolContext) => ({
281
- label: "AKK Send",
282
- name: "agent_knock_knock_send",
283
- description: "Start a new AKK Turn, use one advertised terminal_user_explicit user-priority send, or explicitly recover one current uncertain submission only through its advertised retry_submission action. Ordinary send requires request and may use session_id or terminal_id exactly as advertised. terminal_user_explicit requires one exact live physical terminal/process, a scanned non-blocked approval state, and no input-owning native questionnaire/editor or read-only viewer; parsed working activity, Codex rollout ambiguity, AKK management state, and ordinary Codex main-Composer visibility, stability, or exactness do not veto physical delivery. Codex 0.154's exact collapsed async-question summary remains sendable, while its expanded, clipped, or ambiguous editor receives zero input. Codex physical fallback sends C-u once to replace the current Composer, injects the request, waits through the paste window, and dispatches Enter exactly once without a post-text Composer veto; Claude Code remains exact-empty-only. A source-less Codex terminal freezes all current rollout roots before input, then promotes a provisional Session/Turn only when exactly one anchored or newly opened rollout durably accepts the exact request hash; zero matches remain pending and ambiguity becomes uncertain without replay. If managed preparation fails before input, AKK still delivers once as unmanaged work, then best-effort attaches an exact Terminal Watch callback. After exact request acceptance and terminal attribution, a supported questionnaire on that Watch may expose owner-bound response authority through Status and its watch_id; terminal-activity observations and manual_required interactions remain notification-only. Read terminal_input_dispatched, agent_acceptance, management_mode, observation_mode, and capabilities independently. Watch attachment failure never changes a successful Send. Once the mutation sequence begins, an uncertain result must not be automatically retried. Retry submission is the mutually exclusive exact {turn_id} form and cannot change request text or routing. Draft text, composer digests, and opaque freshness authority stay private. A Turn id is never an ordinary-send destination. Managed acceptance is asynchronous: yield and wait for its callback or an explicit status request.",
284
- parameters: sendParameters,
285
- async execute(toolCallId, params, signal) {
286
- return withHostBridgeInvocationSignal(signal, async () => {
287
- try {
288
- const result = await runSendRequest(api, isRecord(params) ? params : {}, toolContext, terminalMessageIdForToolCall({
289
- toolCallId,
290
- sessionKey: toolContext?.sessionKey,
291
- sessionId: toolContext?.sessionId,
292
- toolName: "agent_knock_knock_send"
293
- }));
294
- return toolResult(result, { submissionErrors: true });
295
- }
296
- catch (error) {
297
- throw modelFacingToolError(error);
298
- }
299
- });
300
- }
301
- }), { name: "agent_knock_knock_send", optional: true });
302
- registerCliTool(api, {
303
- name: "agent_knock_knock_respond",
304
- description: "Respond to a question or blocked callback in one exact in-flight AKK turn. This continues that turn and does not create a new turn; for later ordinary work refresh agent_knock_knock_list and use that terminal row's currently advertised send action.",
305
- parameters: respondParameters,
306
- buildArgs: (params, toolContext, toolCallId) => {
307
- const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
308
- const openclawSession = stringValue(toolContext?.sessionKey) ?? "agent:main:main";
309
- const args = [
310
- "respond",
311
- "--turn",
312
- authoritativeManagedId(params.turn_id, "turn_id"),
313
- "--message",
314
- requiredString(params.request, "request")
315
- ];
316
- pushOptional(args, "--message-id", terminalMessageIdForToolCall({
317
- toolCallId,
318
- sessionKey: openclawSession,
319
- sessionId: toolContext?.sessionId,
320
- toolName: "agent_knock_knock_respond"
321
- }));
322
- pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
323
- pushOptional(args, "--openclaw-session", openclawSession);
324
- return args;
325
- }
326
- });
327
- registerCliTool(api, {
328
- name: "agent_knock_knock_respond_interaction",
329
- description: "Answer exactly one current native questionnaire step shown by agent_knock_knock_status in this controller conversation. Supply exactly one authoritative subject target: turn_id for a managed Turn or watch_id for an interactive Terminal Watch, plus interaction_id and one typed semantic answer. single_select uses selected_option_ids with one advertised option_id; free_text uses text; confirm uses confirm. AKK consumes only that subject's displayed private offer and revalidates the exact discriminator-specific shape, prompt, owner, task attribution, and terminal authority before any input. A displayed expiry is a freshness boundary: after it passes, AKK must prove the same exact live questionnaire again instead of rejecting an otherwise live prompt. Raw keys, menu indexes, rendered labels, fingerprints, versions, and terminal commands are never accepted. An uncertain response must never be retried blindly.",
330
- parameters: respondInteractionParameters,
331
- buildArgs: (params, toolContext) => buildPrivateInteractionResponseArgs(api, params, {
332
- sessionKey: requiredOpenClawSessionKey(toolContext?.sessionKey),
333
- sessionId: requiredOpenClawSessionId(toolContext?.sessionId)
334
- })
335
- });
336
- registerCliTool(api, {
337
- name: "agent_knock_knock_approve",
338
- description: "Dispatch one closed semantic decision for the current exact permission request only after the user reviews and explicitly chooses it. decision defaults to approve_once for compatibility; reject is available only on a managed Turn when the adapter proves a safe native reject choice. Use turn_id for a managed Turn or terminal_id for a separately advertised approve_once-only terminal action. AKK privately refreshes the prompt and authority, then recaptures it under lock. Raw keys, indexes, and labels are not accepted. Never retry an interrupted decision blindly.",
339
- parameters: approveParameters,
340
- buildArgs: (params, toolContext) => buildPrivateApprovalArgs(api, params, {
341
- sessionKey: requiredOpenClawSessionKey(toolContext?.sessionKey),
342
- sessionId: requiredOpenClawSessionId(toolContext?.sessionId)
343
- })
344
- });
345
- registerCliTool(api, {
346
- name: "agent_knock_knock_renew",
347
- description: "Renew monitoring for one exact stalled turn_id without sending text or keys to the coding agent. Use this when the user wants a still-live long-running terminal task to keep monitoring after an inactivity stall.",
348
- parameters: renewParameters,
349
- buildArgs: (params) => {
350
- const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
351
- const args = ["renew"];
352
- pushTurnTarget(args, params);
353
- pushOptional(args, "--minutes", numberString(params.minutes) ?? numberString(config.agentTimeoutMinutes));
354
- pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
355
- return args;
356
- }
357
- });
358
- registerCliTool(api, {
359
- name: "agent_knock_knock_retry_callback",
360
- description: "Retry a persisted AKK callback for an exact turn that failed before reaching the controller Host. The original callback message id and turn identity are reused for idempotent delivery.",
361
- parameters: retryCallbackParameters,
362
- buildArgs: (params) => {
363
- const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
364
- const args = ["retry-callback"];
365
- pushTurnTarget(args, params);
366
- pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
367
- return args;
368
- }
369
- });
370
- registerCliTool(api, {
371
- name: "agent_knock_knock_cancel",
372
- description: "Interrupt one exact AKK turn_id, or use only an unmanaged raw terminal row's own prefilled cancel action. Claude sends Escape; Codex uses its declared interrupt key. The shared terminal pane remains open for human takeover.",
373
- parameters: cancelParameters,
374
- buildArgs: (params) => {
375
- const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
376
- const args = ["cancel"];
377
- pushTurnTarget(args, params);
378
- pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
379
- pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ?? numberString(config.idleTimeoutMinutes));
380
- return args;
381
- }
382
- });
383
- registerCliTool(api, {
384
- name: "agent_knock_knock_close",
385
- description: "Honor an explicit user request to close one managed turn_id and release AKK management. Raw orphan recovery may instead use conversation_id with expected_message_id or expected_transition_id. Close never sends terminal input, stops the coding agent, or closes the shared pane. Deferred transfer, Session, ledger, and callback cleanup is best-effort and cannot veto closing the Turn; warnings identify metadata AKK preserved. Refresh list afterward and use Watch if the coding agent is still working.",
386
- parameters: closeParameters,
387
- isErrorResult: isBlockedTerminalDispatchResult,
388
- buildArgs: (params) => {
389
- const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
390
- const args = ["close"];
391
- assertExclusiveRecoveryFence(params);
392
- pushTurnTarget(args, params);
393
- pushOptional(args, "--reason", stringValue(params.reason));
394
- pushOptional(args, "--expected-message-id", stringValue(params.expected_message_id));
395
- pushOptional(args, "--expected-transition-id", stringValue(params.expected_transition_id));
396
- pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
397
- return args;
398
- }
399
- });
400
- }
401
- function registerOpenClawListTool(api) {
402
- registerCliTool(api, {
403
- name: "agent_knock_knock_list",
404
- description: "List live AKK terminal resources and Terminal Watches. The model-facing result is a compact projection: available_actions contains current semantic action names and action_inputs contains only their dynamic semantic inputs. Follow the installed agent-knock-knock skill for action meaning, target rules, safety boundaries, and recovery behavior; AKK privately revalidates every mutation.",
405
- parameters: listParameters,
406
- modelProjection: compactAkkListModelProjection,
407
- compactText: true,
408
- buildArgs: (params) => {
409
- const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
410
- const args = ["list", "--reconcile"];
411
- pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
412
- pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ??
413
- numberString(api.pluginConfig?.idleTimeoutMinutes));
414
- pushOptional(args, "--agent", stringValue(params.agent));
415
- pushOptional(args, "--status", stringValue(params.status));
416
- if (params.all === true)
417
- args.push("--all");
418
- if (params.noApprovalScan === true)
419
- args.push("--no-approval-scan");
420
- if (params.terminalDebug === true)
421
- args.push("--terminal-debug");
422
- return args;
423
- }
424
- });
425
- }
426
- function registerModelControlTools(api) {
427
- registerCliTool(api, {
428
- name: "agent_knock_knock_model_options",
429
- description: "Inspect the exact current native model catalog for one explicitly selected physical Codex or Claude Code pane. This is the required read-only first step before set_model. Codex 0.154 accepts one exact current native Session or a verified-zero-rollout pane without identify_foreground; Claude Code still requires one exact current native Session. AKK requires an exact live pane/process, no active Turn, and no approval, questionnaire/editor, or read-only viewer. The Composer must be empty unless current List privately binds this action to one exact stable Codex 0.154 /model residual, which AKK may continue into the native picker without retyping it. That residual-bound authority is consumed by discovery; after restoring an exact empty Composer, AKK retains only fresh ordinary terminal/catalog authority for one set_model attempt in this exact controller conversation. It obtains model ids and reasoning-effort values from the native UI/runtime and exposes only semantic choices. Codex advertises scope=current_and_new_sessions; Claude Code advertises scope=current_session. Arbitrary commands, keys, menu indexes, labels, and hidden authority are never accepted.",
430
- parameters: modelOptionsParameters,
431
- timeoutMs: OPENCLAW_MODEL_OPTIONS_CLI_TIMEOUT_MS,
432
- normalizeTurnIdentity: false,
433
- buildArgs: async (params) => {
434
- assertOnlyModelControlParameters(params, ["terminal_id"], "model_options");
435
- const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
436
- const terminalId = requiredString(params.terminal_id, "terminal_id");
437
- const action = await privateTerminalActionArguments(api, terminalId, "agent_knock_knock_model_options");
438
- const args = [
439
- "model-options",
440
- "--terminal",
441
- terminalId,
442
- "--expected-binding-token",
443
- requiredString(action.expected_binding_token, "current internal model-options binding authority")
444
- ];
445
- pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
446
- pushOptional(args, "--codex-home", stringValue(config.codexHome));
447
- return args;
448
- },
449
- rememberResult: (result, params, toolContext) => rememberDisplayedModelOptionsOffer(api, toolContext?.sessionKey, toolContext?.sessionId, params.terminal_id, result)
450
- });
451
- registerCliTool(api, {
452
- name: "agent_knock_knock_repair_model_control",
453
- description: "Clear one exact stale Codex 0.154 /model completion surface, exact bare /model Composer, or exact open native model picker left by a failed native model-control attempt. This explicit one-shot repair is available only when the current AKK list proves the same exact pane/process, the closed profiled model-control residue, no active Turn, and no approval, questionnaire/editor, or read-only viewer. Pass only terminal_id; AKK privately derives and revalidates every physical, screen, and Composer fence before each reversible cleanup input. It never submits a task, selects a model, approves a prompt, accepts raw commands or keys, or automatically continues into model_options/set_model. An open picker receives dismissal authority only, never Enter authority. outcome=uncertain must never be retried automatically.",
454
- parameters: repairModelControlParameters,
455
- timeoutMs: OPENCLAW_REPAIR_MODEL_CONTROL_CLI_TIMEOUT_MS,
456
- normalizeTurnIdentity: false,
457
- isErrorResult: (result) => !isAkkRepairModelControlSuccess(result),
458
- buildArgs: async (params) => {
459
- assertOnlyModelControlParameters(params, ["terminal_id"], "repair_model_control");
460
- const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
461
- const terminalId = requiredString(params.terminal_id, "terminal_id");
462
- const action = await privateTerminalActionArguments(api, terminalId, "agent_knock_knock_repair_model_control");
463
- const args = [
464
- "repair-model-control",
465
- "--terminal",
466
- terminalId,
467
- "--expected-binding-token",
468
- requiredString(action.expected_binding_token, "current internal model-control repair authority")
469
- ];
470
- pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
471
- pushOptional(args, "--codex-home", stringValue(config.codexHome));
472
- return args;
473
- }
474
- });
475
- registerCliTool(api, {
476
- name: "agent_knock_knock_set_model",
477
- description: "Change exactly one already-open physical coding-agent pane to one semantic model and reasoning-effort tuple from the immediately preceding model_options result in this same controller conversation. AKK consumes the private current-snapshot offer, revalidates the exact pane/process plus idle and empty native UI under lock, rejects active Turns and every input-owning prompt/viewer, and verifies the effective postcondition. Codex 0.154 accepts one exact current native Session or a verified-zero-rollout pane without identify_foreground; Claude Code still requires one exact current native Session. Codex scope is current_and_new_sessions: the model and ordinary efforts (including max) are persisted, but ultra remains current-session-only; the future model is reported while the native TUI's unobservable fallback effort is omitted. Claude Code scope is current_session and leaves future defaults unchanged. Parameters never accept scope, raw commands, slash text, keys, menu indexes, display labels, fingerprints, or tokens. outcome=uncertain must never be retried automatically.",
478
- parameters: setModelParameters,
479
- timeoutMs: OPENCLAW_SET_MODEL_CLI_TIMEOUT_MS,
480
- normalizeTurnIdentity: false,
481
- isErrorResult: (result) => !isAkkSetModelSuccess(result),
482
- buildArgs: (params, toolContext) => buildPrivateSetModelArgs(api, params, {
483
- sessionKey: requiredOpenClawSessionKey(toolContext?.sessionKey),
484
- sessionId: requiredOpenClawSessionId(toolContext?.sessionId)
485
- })
486
- });
487
- }
488
- function registerForegroundIdentificationTools(api) {
489
- registerCliTool(api, {
490
- name: "agent_knock_knock_identify_foreground",
491
- description: "Explicitly identify the foreground Codex native thread in one exact idle terminal by issuing the closed /status probe once. The result is a short-lived diagnostic bound to the current pane, process, cwd, and screen generation; it creates no Session or Turn and grants no later response, approval, lifecycle, or send authority. Ordinary list/status never runs this probe.",
492
- parameters: identifyForegroundParameters,
493
- normalizeTurnIdentity: false,
494
- buildArgs: async (params) => {
495
- const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
496
- const terminalId = requiredString(params.terminal_id, "terminal_id");
497
- const action = await privateTerminalActionArguments(api, terminalId, "agent_knock_knock_identify_foreground", { reconcile: false });
498
- const args = [
499
- "identify-foreground",
500
- "--terminal",
501
- terminalId,
502
- "--expected-terminal-token",
503
- requiredString(action.expected_terminal_token, "current internal foreground-identification terminal authority")
504
- ];
505
- pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
506
- pushOptional(args, "--codex-home", stringValue(config.codexHome));
507
- return args;
508
- }
509
- });
510
- registerCliTool(api, {
511
- name: "agent_knock_knock_identify_and_send",
512
- description: "Explicitly identify the foreground Codex native thread with one closed /status probe, then dispatch one task while retaining the same terminal lock. This is an optional managed-attachment enhancement, not a prerequisite for ordinary human Send. The short-lived status observation never becomes durable identity: only the rollout that uniquely accepts the exact task may own the resulting Session/Turn. If the probe or boundary becomes uncertain, AKK does not send or retry the task.",
513
- parameters: identifyAndSendParameters,
514
- isErrorResult: isSubmissionError,
515
- buildArgs: async (params, toolContext, toolCallId) => {
516
- const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
517
- const terminalId = requiredString(params.terminal_id, "terminal_id");
518
- const action = await privateTerminalActionArguments(api, terminalId, "agent_knock_knock_identify_and_send", { reconcile: false });
519
- const openclawSession = stringValue(toolContext?.sessionKey) ?? "agent:main:main";
520
- const args = [
521
- "send",
522
- "--conversation",
523
- terminalId,
524
- "--expected-terminal-token",
525
- requiredString(action.expected_terminal_token, "current internal identify-and-send terminal authority"),
526
- "--identify-foreground",
527
- "--message",
528
- requiredString(params.request, "request"),
529
- "--background"
530
- ];
531
- pushOptional(args, "--message-id", terminalMessageIdForToolCall({
532
- toolCallId,
533
- sessionKey: openclawSession,
534
- sessionId: toolContext?.sessionId,
535
- toolName: "agent_knock_knock_identify_and_send"
536
- }));
537
- pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
538
- pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ??
539
- numberString(config.idleTimeoutMinutes));
540
- pushOptional(args, "--agent-timeout-minutes", numberString(params.agentTimeoutMinutes) ??
541
- numberString(config.agentTimeoutMinutes));
542
- pushOptional(args, "--agent-hard-timeout-minutes", numberString(params.agentHardTimeoutMinutes) ??
543
- numberString(config.agentHardTimeoutMinutes));
544
- pushOptional(args, "--openclaw-session", openclawSession);
545
- pushOptional(args, "--gateway-method", CALLBACK_METHOD);
546
- pushOptional(args, "--gateway-session", openclawSession);
547
- pushOptional(args, "--openclaw-bin", stringValue(config.openclawBin));
548
- return args;
549
- }
550
- });
551
- }
552
- function resumeSelectionScope(sessionKey, sessionId) {
553
- const key = requiredString(sessionKey, "Controller session identity is required for snapshot-bound Resume");
554
- const incarnation = stringValue(sessionId) ?? null;
555
- return `openclaw:${createHash("sha256")
556
- .update(JSON.stringify([key, incarnation]))
557
- .digest("hex")}`;
558
- }
559
- function resumeSnapshotCacheKey(sessionKey, sessionId, terminalId) {
560
- const key = requiredString(sessionKey, "Controller session identity is required for snapshot-bound Resume");
561
- return JSON.stringify([key, stringValue(sessionId) ?? null, terminalId]);
562
- }
563
- function rememberDisplayedResumeSnapshot(snapshots, key, snapshotId, expiresAtMs) {
564
- const now = Date.now();
565
- for (const [candidateKey, value] of snapshots) {
566
- if (value.expiresAtMs <= now) {
567
- snapshots.delete(candidateKey);
568
- }
569
- }
570
- snapshots.delete(key);
571
- while (snapshots.size >= MAX_DISPLAYED_RESUME_SNAPSHOTS) {
572
- const oldestKey = snapshots.keys().next().value;
573
- if (typeof oldestKey !== "string") {
574
- break;
575
- }
576
- snapshots.delete(oldestKey);
577
- }
578
- snapshots.set(key, { snapshotId, expiresAtMs });
579
- }
580
- function currentDisplayedResumeSnapshotId(snapshots, key) {
581
- const value = snapshots.get(key);
582
- if (!value) {
583
- return undefined;
584
- }
585
- if (value.expiresAtMs <= Date.now()) {
586
- snapshots.delete(key);
587
- return undefined;
588
- }
589
- return value.snapshotId;
590
- }
591
- async function handleAkkCommand(api, ctx, displayedResumeSnapshots) {
592
- try {
593
- const parsed = parseAkkCommand(ctx.args);
594
- if (parsed.action === "help") {
595
- return { text: akkUsageText() };
596
- }
597
- if (parsed.action === "delegate") {
598
- const result = await runDelegate(api, {
599
- request: parsed.request,
600
- messageId: `openclaw-command-${randomUUID()}`
601
- }, {
602
- sessionKey: ctx.sessionKey
603
- });
604
- return {
605
- text: result.scope === "terminal_user_explicit"
606
- ? formatSendCommandResult(result)
607
- : formatDelegateCommandResult(result),
608
- isError: sendCommandResultIsError(result)
609
- };
610
- }
611
- const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
612
- if (parsed.action === "model-options" ||
613
- parsed.action === "repair-model-control" ||
614
- parsed.action === "set-model") {
615
- return await handleAkkModelCommand(api, ctx, parsed, config);
616
- }
617
- if (parsed.action === "list-resumable-threads" ||
618
- parsed.action === "new-thread" ||
619
- parsed.action === "resume-thread") {
620
- return await handleAkkLifecycleCommand(api, ctx, parsed, config, displayedResumeSnapshots);
621
- }
622
- const args = parsed.action === "approve"
623
- ? await buildPrivateApprovalArgs(api, {
624
- turn_id: parsed.turnId,
625
- decision: parsed.decision
626
- }, {
627
- sessionKey: requiredOpenClawSessionKey(ctx.sessionKey),
628
- sessionId: requiredOpenClawSessionId(ctx.sessionId)
629
- })
630
- : buildAkkCommandCliArgs(parsed, config, {
631
- sessionKey: ctx.sessionKey,
632
- messageId: parsed.action === "send"
633
- ? `openclaw-command-${randomUUID()}`
634
- : undefined
635
- });
636
- if (!args) {
637
- return { text: akkUsageText(), isError: true };
638
- }
639
- // Doctor calls back into the running Gateway for its independent health
640
- // check. Keep the Gateway event loop free while that child CLI runs.
641
- const result = parsed.action === "doctor"
642
- ? await runCliAsync(api, args, { allowNonzeroJson: true })
643
- : await runHostAwareCli(api, args);
644
- switch (parsed.action) {
645
- case "doctor":
646
- return {
647
- text: formatDoctorCommandResult(result),
648
- isError: result.ok !== true
649
- };
650
- case "list":
651
- return { text: formatAkkListCommandResult(result) };
652
- case "watch":
653
- return { text: formatAkkWatchCommandResult(result) };
654
- case "unwatch":
655
- return { text: formatAkkUnwatchCommandResult(result) };
656
- case "status":
657
- return { text: formatStatusCommandResult(result) };
658
- case "send":
659
- return {
660
- text: formatSendCommandResult(result),
661
- isError: sendCommandResultIsError(result)
662
- };
663
- case "respond":
664
- return formatAkkRespondCommandResult(result);
665
- case "approve":
666
- return { text: formatApproveCommandResult(result) };
667
- case "renew":
668
- return { text: formatRenewCommandResult(result) };
669
- case "retry-callback":
670
- return { text: formatRetryCallbackCommandResult(result) };
671
- case "cancel":
672
- return { text: formatCancelCommandResult(result) };
673
- case "close":
674
- return {
675
- text: formatCloseCommandResult(result),
676
- isError: isBlockedTerminalDispatchResult(result)
677
- };
678
- }
679
- }
680
- catch (error) {
681
- if (error instanceof Error && error.name === "AbortError") {
682
- throw error;
683
- }
684
- const message = modelFacingErrorMessage(error);
685
- return {
686
- text: `AKK command failed: ${message}`,
687
- isError: true
688
- };
689
- }
690
- }
691
- async function handleAkkModelCommand(api, ctx, parsed, config) {
692
- const sessionKey = requiredOpenClawSessionKey(ctx.sessionKey);
693
- const sessionId = requiredOpenClawSessionId(ctx.sessionId);
694
- if (parsed.action === "model-options") {
695
- const action = await privateTerminalActionArguments(api, parsed.terminalId, "agent_knock_knock_model_options");
696
- const args = buildAkkCommandCliArgs(parsed, config, {
697
- expectedBindingToken: action.expected_binding_token
698
- });
699
- if (!args)
700
- throw new Error("could not build model-options command");
701
- const result = await runHostAwareCli(api, args, {
702
- timeoutMs: OPENCLAW_MODEL_OPTIONS_CLI_TIMEOUT_MS
703
- });
704
- rememberDisplayedModelOptionsOffer(api, sessionKey, sessionId, parsed.terminalId, result);
705
- return { text: formatAkkModelOptionsCommandResult(result) };
706
- }
707
- if (parsed.action === "repair-model-control") {
708
- const action = await privateTerminalActionArguments(api, parsed.terminalId, "agent_knock_knock_repair_model_control");
709
- const args = buildAkkCommandCliArgs(parsed, config, {
710
- expectedBindingToken: action.expected_binding_token
711
- });
712
- if (!args)
713
- throw new Error("could not build repair-model-control command");
714
- const result = await runHostAwareCli(api, args, {
715
- timeoutMs: OPENCLAW_REPAIR_MODEL_CONTROL_CLI_TIMEOUT_MS
716
- });
717
- return {
718
- text: formatAkkRepairModelControlCommandResult(result),
719
- isError: !isAkkRepairModelControlSuccess(result)
720
- };
721
- }
722
- const args = buildPrivateSetModelArgs(api, {
723
- terminal_id: parsed.terminalId,
724
- model: parsed.model,
725
- reasoning_effort: parsed.reasoningEffort
726
- }, { sessionKey, sessionId });
727
- const result = await runHostAwareCli(api, args, {
728
- timeoutMs: OPENCLAW_SET_MODEL_CLI_TIMEOUT_MS
729
- });
730
- return {
731
- text: formatAkkSetModelCommandResult(result),
732
- isError: !isAkkSetModelSuccess(result)
733
- };
734
- }
735
- function sendCommandResultIsError(result) {
736
- return isSuccessfulTerminalDispatch(result)
737
- ? false
738
- : terminalSubmissionReported(result)
739
- ? !isAkkNativeSubmissionAccepted(result)
740
- : result.status === "delivered_unfenced";
741
- }
742
- function isSuccessfulTerminalDispatch(result) {
743
- return isRecord(result) && terminalSendEnterDispatched(result);
744
- }
745
- async function handleAkkLifecycleCommand(api, ctx, parsed, config, displayedResumeSnapshots) {
746
- const selectionScope = resumeSelectionScope(ctx.sessionKey, ctx.sessionId);
747
- const snapshotCacheKey = resumeSnapshotCacheKey(ctx.sessionKey, ctx.sessionId, parsed.terminalId);
748
- if (parsed.action === "resume-thread" &&
749
- parsed.selection &&
750
- ["number", "short-id"].includes(parsed.selection.kind)) {
751
- requiredString(ctx.sessionId, "Controller conversation incarnation is required for number or short-id Resume; run /akk threads again in the current conversation or use the complete UUID");
752
- const mutationArgs = buildAkkCommandCliArgs(parsed, config, {
753
- sessionKey: ctx.sessionKey,
754
- selectionScope,
755
- selectionSnapshotId: currentDisplayedResumeSnapshotId(displayedResumeSnapshots, snapshotCacheKey)
756
- });
757
- if (!mutationArgs) {
758
- throw new Error("could not build snapshot-bound resume command");
759
- }
760
- const transitionResult = await runHostAwareCli(api, mutationArgs);
761
- if (isAkkThreadTransitionSuccess(transitionResult)) {
762
- displayedResumeSnapshots.delete(snapshotCacheKey);
763
- }
764
- return {
765
- text: formatAkkThreadTransitionCommandResult(transitionResult),
766
- isError: !isAkkThreadTransitionSuccess(transitionResult)
767
- };
768
- }
769
- const discoveryArgs = buildAkkCommandCliArgs({
770
- action: "list-resumable-threads",
771
- terminalId: parsed.terminalId
772
- }, config, { selectionScope });
773
- if (!discoveryArgs) {
774
- throw new Error("could not build native-thread discovery command");
775
- }
776
- const discovery = await runHostAwareCli(api, discoveryArgs);
777
- if (parsed.action === "list-resumable-threads" ||
778
- (parsed.action === "resume-thread" &&
779
- !parsed.selection)) {
780
- const snapshotId = isRecord(discovery.selection_snapshot)
781
- ? stringValue(discovery.selection_snapshot.snapshot_id)
782
- : undefined;
783
- const expiresAt = isRecord(discovery.selection_snapshot)
784
- ? Date.parse(String(discovery.selection_snapshot.expires_at ?? ""))
785
- : Number.NaN;
786
- if (snapshotId && Number.isFinite(expiresAt) && expiresAt > Date.now()) {
787
- rememberDisplayedResumeSnapshot(displayedResumeSnapshots, snapshotCacheKey, snapshotId, expiresAt);
788
- }
789
- else {
790
- displayedResumeSnapshots.delete(snapshotCacheKey);
791
- }
792
- return { text: formatAkkThreadsCommandResult(discovery) };
793
- }
794
- let expectedBindingToken = requiredString(discovery.expected_binding_token, "expected_binding_token from lifecycle discovery");
795
- let candidateToken;
796
- let mutationCommand = parsed;
797
- if (parsed.action === "resume-thread") {
798
- let nativeThreadId;
799
- if (parsed.selection?.kind === "previous") {
800
- const previous = isRecord(discovery.previous)
801
- ? discovery.previous
802
- : undefined;
803
- const action = isRecord(previous?.available_actions) &&
804
- isRecord(previous.available_actions.resume_thread)
805
- ? previous.available_actions.resume_thread
806
- : undefined;
807
- const actionArguments = isRecord(action?.arguments)
808
- ? action.arguments
809
- : undefined;
810
- nativeThreadId = requiredString(actionArguments?.native_thread_id, "no verified previous native thread is available; run /akk threads");
811
- if (stringValue(actionArguments?.terminal_id) !== parsed.terminalId) {
812
- throw new Error("previous resume action belongs to another terminal");
813
- }
814
- expectedBindingToken = requiredString(actionArguments?.expected_binding_token, "expected_binding_token from the previous resume action");
815
- candidateToken = requiredString(actionArguments?.candidate_token, "candidate_token from the previous resume action");
816
- }
817
- else {
818
- if (parsed.selection?.kind !== "exact") {
819
- throw new Error("resume selection could not be resolved");
820
- }
821
- nativeThreadId = parsed.selection.nativeThreadId;
822
- const candidate = Array.isArray(discovery.threads)
823
- ? discovery.threads.find((thread) => isRecord(thread) &&
824
- stringValue(thread.native_thread_id) === nativeThreadId)
825
- : undefined;
826
- if (!isRecord(candidate) || candidate.resumable !== true) {
827
- throw new Error(`native thread ${nativeThreadId} is not resumable in the current lifecycle snapshot`);
828
- }
829
- candidateToken = requiredString(candidate.candidate_token, "candidate_token from the selected resumable thread");
830
- }
831
- mutationCommand = {
832
- action: "resume-thread",
833
- terminalId: parsed.terminalId,
834
- selection: { kind: "exact", nativeThreadId }
835
- };
836
- }
837
- const mutationArgs = buildAkkCommandCliArgs(mutationCommand, config, {
838
- sessionKey: ctx.sessionKey,
839
- expectedBindingToken,
840
- candidateToken,
841
- selectionScope
842
- });
843
- if (!mutationArgs) {
844
- throw new Error("could not build native-thread lifecycle command");
845
- }
846
- const transitionResult = await runHostAwareCli(api, mutationArgs);
847
- if (isAkkThreadTransitionSuccess(transitionResult)) {
848
- displayedResumeSnapshots.delete(snapshotCacheKey);
849
- }
850
- return {
851
- text: formatAkkThreadTransitionCommandResult(transitionResult),
852
- isError: !isAkkThreadTransitionSuccess(transitionResult)
853
- };
854
- }
855
- function formatDelegateCommandResult(result) {
856
- const agent = executorDisplayName(result.agent);
857
- const { sessionId, turnId } = publicTurnIdentity(result);
858
- if (result.status === "submission_unfenced") {
859
- return [
860
- `AKK sent the terminal input to ${agent}, but could not bind later side effects to an exact native session.`,
861
- `session: ${sessionId}`,
862
- `turn: ${turnId}`,
863
- "next: do not retry or continue automatically; inspect the named pane and close this Turn before sending more work."
864
- ].join("\n");
865
- }
866
- if (result.status === "submission_uncertain") {
867
- return [
868
- `AKK could not prove whether ${agent} received the terminal task.`,
869
- `session: ${sessionId}`,
870
- `turn: ${turnId}`,
871
- "next: do not retry automatically; inspect AKK status and the named terminal pane."
872
- ].join("\n");
873
- }
874
- if (result.status === "submission_aborted") {
875
- const safeToRetry = result.safe_to_retry === true && result.do_not_retry !== true;
876
- return [
877
- `AKK stopped before sending the terminal task to ${agent}.`,
878
- `session: ${sessionId}`,
879
- `turn: ${turnId}`,
880
- safeToRetry
881
- ? "next: no terminal input was sent and the aborted receipt is durable, so this request may be retried."
882
- : "next: do not retry automatically; AKK could not prove a durable safe abort, so inspect this Turn and its terminal dispatch ledger."
883
- ].join("\n");
884
- }
885
- if (result.status === "submission_pending_acceptance") {
886
- return [
887
- `AKK dispatched terminal input to ${agent}, but native acceptance is still pending.`,
888
- `session: ${sessionId}`,
889
- `turn: ${turnId}`,
890
- "next: do not retry or report success; wait for acceptance or inspect the shared terminal pane."
891
- ].join("\n");
892
- }
893
- if (result.status === "submission_not_accepted") {
894
- return [
895
- `AKK proved that ${agent} did not accept the terminal draft.`,
896
- `session: ${sessionId}`,
897
- `turn: ${turnId}`,
898
- "next: do not retry automatically; inspect the exact draft in the shared terminal pane."
899
- ].join("\n");
900
- }
901
- return [
902
- `AKK sent the task to ${agent} in the shared terminal.`,
903
- `session: ${sessionId}`,
904
- `turn: ${turnId}`,
905
- `status: ${result.conversation_status ?? result.status ?? "unknown"}`,
906
- "The result will return to this controller session through the callback."
907
- ].join("\n");
908
- }
909
- function executorDisplayName(kind) {
910
- try {
911
- return executorDefinitionForKind(String(kind ?? "codex")).displayName;
912
- }
913
- catch {
914
- return String(kind ?? "agent");
915
- }
916
- }
917
- function formatStatusCommandResult(result) {
918
- if (stringValue(result.watch_id) ||
919
- (isRecord(result.watch) && stringValue(result.watch.watch_id)) ||
920
- (isRecord(result.terminal_watch) &&
921
- stringValue(result.terminal_watch.watch_id))) {
922
- return formatAkkWatchStatusCommandResult(result);
923
- }
924
- const summary = result.summary ?? result.conversation ?? result ?? {};
925
- const terminalStatus = isRecord(result.terminal_status) ? result.terminal_status : {};
926
- const rawCallbackDelivery = isRecord(result.conversation?.callback_delivery)
927
- ? result.conversation.callback_delivery
928
- : undefined;
929
- const summarizedCallbackDelivery = isRecord(summary.callback_delivery)
930
- ? summary.callback_delivery
931
- : undefined;
932
- const callbackDelivery = rawCallbackDelivery || summarizedCallbackDelivery
933
- ? {
934
- ...(rawCallbackDelivery ?? {}),
935
- ...(summarizedCallbackDelivery ?? {})
936
- }
937
- : undefined;
938
- const { sessionId, turnId } = publicTurnIdentity(result);
939
- const lines = [
940
- "AKK status:",
941
- `session: ${sessionId}`,
942
- `turn: ${turnId}`,
943
- `agent: ${summary.agent ?? summary.executor?.kind ?? terminalStatus.agent ?? "unknown"}`,
944
- `turn status: ${summary.status ?? result.conversation_status ?? result.status ?? "not managed"}`,
945
- `terminal activity: ${terminalStatus.activity_state ?? "unavailable"}`,
946
- ...formatAkkTerminalWatchHint(result)
947
- ];
948
- if (callbackDelivery) {
949
- const callbackParts = [
950
- String(callbackDelivery.status ?? "unknown"),
951
- Number.isSafeInteger(Number(callbackDelivery.attempts))
952
- ? `attempt ${Number(callbackDelivery.attempts)}`
953
- : undefined,
954
- callbackDelivery.attempt_state === "in_flight"
955
- ? "in flight"
956
- : undefined,
957
- stringValue(callbackDelivery.next_attempt_at)
958
- ? `next retry ${stringValue(callbackDelivery.next_attempt_at)}`
959
- : undefined
960
- ].filter(Boolean);
961
- lines.push(`callback: ${callbackParts.join(", ")}`);
962
- }
963
- if (summary.request) {
964
- lines.push(`request: ${truncateText(summary.request, 180)}`);
965
- }
966
- if (result.about) {
967
- lines.push(`about: ${truncateText(result.about, 500)}`);
968
- }
969
- if (result.confidence) {
970
- lines.push(`confidence: ${result.confidence}`);
971
- }
972
- const limitations = Array.isArray(result.limitations)
973
- ? result.limitations.filter(Boolean)
974
- : [];
975
- if (limitations.length > 0) {
976
- lines.push(`limitations: ${limitations.slice(0, 3).join("; ")}`);
977
- }
978
- const screen = terminalScreenExcerpt(result);
979
- if (screen) {
980
- lines.push(`terminal screen:\n${screen}`);
981
- }
982
- return lines.join("\n");
983
- }
984
- function formatDoctorCommandResult(result) {
985
- const capabilities = isRecord(result.capabilities) ? result.capabilities : {};
986
- const tmux = isRecord(capabilities.tmux) ? capabilities.tmux : {};
987
- const herdr = isRecord(capabilities.herdr) ? capabilities.herdr : {};
988
- const dependencyChecks = Array.isArray(result.checks)
989
- ? result.checks.filter(isRecord)
990
- : [];
991
- const codingAgentLines = [
992
- ["Codex", "codex"],
993
- ["Claude Code", "claude"]
994
- ].map(([label, command]) => {
995
- const check = dependencyChecks.find((entry) => entry.command === command);
996
- const version = typeof check?.version === "string"
997
- ? check.version
998
- : "unavailable";
999
- const nativeProfile = check?.native_profile_supported === true &&
1000
- typeof check.native_profile === "string"
1001
- ? `native profile ${check.native_profile}`
1002
- : check?.native_actions_available === true
1003
- ? "native lifecycle/status available with compatibility warning"
1004
- : check?.available === true
1005
- ? "native lifecycle/status unavailable: unrecognized version format"
1006
- : "unavailable";
1007
- return `${label}: ${version} (${nativeProfile})`;
1008
- });
1009
- const openclaw = isRecord(result.openclaw) ? result.openclaw : {};
1010
- const checks = Array.isArray(openclaw.checks) ? openclaw.checks : [];
1011
- const failures = checks
1012
- .filter((check) => isRecord(check) && check.ok !== true)
1013
- .map((check) => String(check.name ?? "unknown"));
1014
- const remediation = [...new Set(checks.flatMap((check) => isRecord(check) && Array.isArray(check.remediation)
1015
- ? check.remediation.filter((command) => typeof command === "string" && command.trim().length > 0)
1016
- : []))].slice(0, 3);
1017
- return [
1018
- `AKK doctor: ${result.ok === true ? "ready" : "needs attention"}`,
1019
- `tmux: ${tmux.status ?? "unknown"}`,
1020
- `Herdr: ${herdr.status ?? "unknown"}`,
1021
- ...codingAgentLines,
1022
- `OpenClaw package: ${openclaw.package_ready === true ? "ready" : "not ready"}`,
1023
- `Gateway: ${openclaw.gateway_ready === true ? "healthy" : "unavailable"}`,
1024
- ...(failures.length > 0 ? [`check: ${failures.join(", ")}`] : []),
1025
- ...remediation.map((command) => `next: ${command}`)
1026
- ].join("\n");
1027
- }
1028
- function formatRenewCommandResult(result) {
1029
- const { sessionId, turnId } = publicTurnIdentity(result);
1030
- return [
1031
- "AKK monitoring renewed.",
1032
- `session: ${sessionId}`,
1033
- `turn: ${turnId}`,
1034
- `inactivity timeout: ${result.agent_timeout_minutes ?? "unknown"} minutes`,
1035
- `hard lifetime: ${result.agent_hard_timeout_minutes ?? "unknown"} minutes`,
1036
- "No message or key was sent to the coding agent."
1037
- ].join("\n");
1038
- }
1039
- function formatRetryCallbackCommandResult(result) {
1040
- const { sessionId, turnId } = publicTurnIdentity(result);
1041
- return [
1042
- "AKK callback delivered.",
1043
- `session: ${sessionId}`,
1044
- `turn: ${turnId}`,
1045
- `status: ${result.conversation?.status ?? "unknown"}`,
1046
- `attempts: ${result.conversation?.callback_delivery?.attempts ?? "unknown"}`
1047
- ].join("\n");
1048
- }
1049
- function formatUnconfirmedTerminalUserExplicitSendResult(result) {
1050
- const terminalInputDispatched = result.terminal_input_dispatched === true;
1051
- const enterDispatched = terminalSendEnterDispatched(result);
1052
- if (!enterDispatched) {
1053
- return [
1054
- terminalInputDispatched
1055
- ? "AKK started this terminal Send, but could not prove that Enter was dispatched."
1056
- : "AKK stopped this terminal Send before any terminal input was proven.",
1057
- `terminal: ${result.terminal_id ?? "unknown"}`,
1058
- `message: ${result.message_id ?? "unknown"}`,
1059
- terminalInputDispatched
1060
- ? "delivery: text input may be present; do not resend this message id."
1061
- : "delivery: no terminal input proven.",
1062
- "next: inspect AKK status and the terminal before deciding whether to continue."
1063
- ].join("\n");
1064
- }
1065
- return [
1066
- "AKK already dispatched this managed terminal Send; native acceptance is still pending.",
1067
- `terminal: ${result.terminal_id ?? "unknown"}`,
1068
- `message: ${result.message_id ?? "unknown"}`,
1069
- "delivery: Enter dispatched; do not resend this message id.",
1070
- "next: inspect AKK status or the terminal; wait for the existing managed Turn."
1071
- ].join("\n");
1072
- }
1073
- function formatTerminalUserExplicitSendResult(result) {
1074
- if (!terminalSendEnterDispatched(result)) {
1075
- return formatUnconfirmedTerminalUserExplicitSendResult(result);
1076
- }
1077
- const unmanaged = result.delivered_unmanaged === true;
1078
- const replayed = result.replayed === true;
1079
- const watchCallback = unmanaged &&
1080
- result.callback_expected === true &&
1081
- result.callback_mode === "terminal_watch" &&
1082
- typeof result.watch_id === "string"
1083
- ? result.watch_id
1084
- : undefined;
1085
- return [
1086
- unmanaged
1087
- ? replayed
1088
- ? "AKK confirmed this direct terminal Send was already delivered."
1089
- : "AKK delivered the user's request directly to the coding agent."
1090
- : replayed
1091
- ? "AKK confirmed this managed terminal Send was already delivered."
1092
- : "AKK delivered the user's terminal Send through managed routing.",
1093
- `terminal: ${result.terminal_id ?? "unknown"}`,
1094
- `message: ${result.message_id ?? "unknown"}`,
1095
- `delivery: ${unmanaged ? "unmanaged fallback" : "managed"}`,
1096
- ...(unmanaged
1097
- ? [watchCallback
1098
- ? `callback: Terminal Watch ${watchCallback}; no managed AKK Turn was created.`
1099
- : "callback: unavailable; no managed AKK Turn was created."]
1100
- : []),
1101
- unmanaged
1102
- ? watchCallback
1103
- ? "next: wait for the Terminal Watch callback; watch-status is the recovery path."
1104
- : "next: refresh AKK list and use Watch to observe the still-running coding-agent task."
1105
- : "next: refresh AKK list; do not resend this message id."
1106
- ].join("\n");
1107
- }
1108
- function formatManagedSendCommandResult(result) {
1109
- const conversation = result.conversation ?? {};
1110
- const conversationId = conversation.conversation_id ?? result.conversation_id ?? "unknown";
1111
- const sessionId = conversation.session_id ?? result.session_id ?? conversationId;
1112
- const turnId = conversation.turn_id ?? result.turn_id ?? conversationId;
1113
- const status = conversation.status ?? result.status ?? "unknown";
1114
- const nextAction = isRecord(result.openclaw_next_action) ? result.openclaw_next_action : undefined;
1115
- if (result.status === "delivered_unfenced") {
1116
- return [
1117
- "AKK sent the terminal input but could not bind an exact native session.",
1118
- `session: ${sessionId}`,
1119
- `turn: ${turnId}`,
1120
- `status: ${status}`,
1121
- "next: do not retry or continue automatically; inspect the shared terminal pane and close this Turn before sending more work."
1122
- ].join("\n");
1123
- }
1124
- if (result.submission_outcome === "uncertain") {
1125
- return [
1126
- "AKK terminal submission outcome is uncertain.",
1127
- `session: ${sessionId}`,
1128
- `turn: ${turnId}`,
1129
- `status: ${result.status ?? status}`,
1130
- "next: do not retry automatically; inspect AKK status and the named terminal pane."
1131
- ].join("\n");
1132
- }
1133
- if (result.submission_outcome === "aborted") {
1134
- const safeToRetry = result.safe_to_retry === true && result.do_not_retry !== true;
1135
- return [
1136
- "AKK terminal submission was aborted before terminal input.",
1137
- `session: ${sessionId}`,
1138
- `turn: ${turnId}`,
1139
- `status: ${result.status ?? status}`,
1140
- safeToRetry
1141
- ? "next: this request was not sent, the aborted receipt is durable, and it may be retried."
1142
- : "next: do not retry automatically; inspect the Turn because a durable safe abort was not proven."
1143
- ].join("\n");
1144
- }
1145
- if (result.submission_outcome === "pending_acceptance") {
1146
- return [
1147
- "AKK dispatched the terminal input but native acceptance is still pending.",
1148
- `session: ${sessionId}`,
1149
- `turn: ${turnId}`,
1150
- `status: ${result.status ?? status}`,
1151
- "next: do not retry or report success; wait for acceptance or inspect the shared terminal pane."
1152
- ].join("\n");
1153
- }
1154
- if (result.submission_outcome === "not_accepted") {
1155
- return [
1156
- "AKK proved that the agent did not accept the terminal draft.",
1157
- `session: ${sessionId}`,
1158
- `turn: ${turnId}`,
1159
- `status: ${result.status ?? status}`,
1160
- "next: do not retry automatically; inspect the exact draft in the shared terminal pane."
1161
- ].join("\n");
1162
- }
1163
- if (terminalSubmissionReported(result) &&
1164
- !isAkkNativeSubmissionAccepted(result)) {
1165
- return [
1166
- "AKK could not verify native agent acceptance for this terminal input.",
1167
- `session: ${sessionId}`,
1168
- `turn: ${turnId}`,
1169
- `status: ${result.status ?? status}`,
1170
- "next: do not retry or report success; inspect the exact Turn receipt and shared terminal pane."
1171
- ].join("\n");
1172
- }
1173
- const lines = [
1174
- "AKK turn sent.",
1175
- `session: ${sessionId}`,
1176
- `turn: ${turnId}`,
1177
- `status: ${status}`
1178
- ];
1179
- if (result.source) {
1180
- lines.push(`source: ${result.source}`);
1181
- }
1182
- return [
1183
- ...lines,
1184
- nextAction?.action === "yield"
1185
- ? "next: yield now and wait for the AKK callback or an explicit status request."
1186
- : `launched: ${result.launched === true ? "yes" : "no"}`
1187
- ].join("\n");
1188
- }
1189
- function formatSendCommandResult(result) {
1190
- return result.scope === "terminal_user_explicit"
1191
- ? formatTerminalUserExplicitSendResult(result)
1192
- : formatManagedSendCommandResult(result);
1193
- }
1194
- function formatCancelCommandResult(result) {
1195
- const conversation = result.conversation ?? {};
1196
- const { sessionId, turnId } = publicTurnIdentity(result);
1197
- return [
1198
- "AKK cancel requested.",
1199
- `session: ${sessionId}`,
1200
- `turn: ${turnId}`,
1201
- `agent: ${result.executor?.kind ?? conversation.executor?.kind ?? "unknown"}`,
1202
- `status: ${conversation.status ?? (result.cancel_requested === true ? "cancel requested" : "not cancelled")}`
1203
- ].join("\n");
1204
- }
1205
- function formatApproveCommandResult(result) {
1206
- const conversation = result.conversation ?? {};
1207
- const { sessionId, turnId } = publicTurnIdentity(result);
1208
- return [
1209
- result.approved === true
1210
- ? "AKK approved the current terminal request."
1211
- : "AKK did not approve the terminal request.",
1212
- `session: ${sessionId}`,
1213
- `turn: ${turnId}`,
1214
- `status: ${conversation.status ?? "unknown"}`,
1215
- ...(result.reason
1216
- ? [`reason: ${sanitizeAkkModelFacingDiagnosticText(String(result.reason))}`]
1217
- : [])
1218
- ].join("\n");
1219
- }
1220
- function formatCloseCommandResult(result) {
1221
- if (result.source === "terminal_control") {
1222
- const terminalControl = isRecord(result.terminal_control)
1223
- ? result.terminal_control
1224
- : {};
1225
- if (result.terminal_dispatch_resolved !== true) {
1226
- return [
1227
- "AKK did not clear the unresolved terminal dispatch fence.",
1228
- `terminal: ${terminalControl.target ?? "unknown"}`,
1229
- ...(stringValue(result.transition_id)
1230
- ? [`transition: ${stringValue(result.transition_id)}`]
1231
- : []),
1232
- `reason: ${sanitizeAkkModelFacingDiagnosticText(stringValue(result.reason) ??
1233
- "the recorded lifecycle outcome could not be verified")}`,
1234
- "This terminal remains blocked. Do not retry or continue automatically; inspect the pane and use the fresh recovery action from /akk list."
1235
- ].join("\n");
1236
- }
1237
- return [
1238
- "AKK cleared the unresolved terminal dispatch fence.",
1239
- `terminal: ${terminalControl.target ?? "unknown"}`,
1240
- ...(stringValue(result.transition_id)
1241
- ? [`transition: ${stringValue(result.transition_id)}`]
1242
- : [
1243
- `previous turn: ${result.owner_turn_id ?? result.owner_conversation_id ?? "unknown"}`
1244
- ]),
1245
- "The coding agent and terminal pane remain open."
1246
- ].join("\n");
1247
- }
1248
- const conversation = result.conversation ?? {};
1249
- const { sessionId, turnId } = publicTurnIdentity(result);
1250
- return [
1251
- "AKK Turn record closed.",
1252
- `session: ${sessionId}`,
1253
- `turn: ${turnId}`,
1254
- `status: ${conversation.status ?? "unknown"}`,
1255
- "AKK management was released; the coding agent and terminal pane were not stopped.",
1256
- ...(Array.isArray(result.warnings) && result.warnings.length > 0
1257
- ? [`cleanup warnings: ${result.warnings.map((warning) => sanitizeAkkModelFacingDiagnosticText(String(warning))).join("; ")}`]
1258
- : []),
1259
- "Refresh the AKK list; if the coding agent is still working, Watch can attach to it again."
1260
- ].join("\n");
1261
- }
1262
- function publicTurnIdentity(result) {
1263
- const conversation = isRecord(result.conversation) ? result.conversation : {};
1264
- const summary = isRecord(result.summary) ? result.summary : {};
1265
- const compatibilityId = stringValue(conversation.conversation_id) ??
1266
- stringValue(summary.conversation_id) ??
1267
- stringValue(result.conversation_id);
1268
- return {
1269
- sessionId: stringValue(conversation.session_id) ??
1270
- stringValue(summary.session_id) ??
1271
- stringValue(result.session_id) ??
1272
- compatibilityId ??
1273
- "unknown",
1274
- turnId: stringValue(conversation.turn_id) ??
1275
- stringValue(summary.turn_id) ??
1276
- stringValue(result.turn_id) ??
1277
- compatibilityId ??
1278
- "unknown"
1279
- };
1280
- }
1281
- function buildStatusCliArgs(api, params, toolContext) {
1282
- const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
1283
- if (Object.hasOwn(params, "watch_id")) {
1284
- if (Object.hasOwn(params, "turn_id") ||
1285
- Object.hasOwn(params, "conversation_id")) {
1286
- throw new Error("status accepts exactly one of turn_id, conversation_id, or watch_id");
1287
- }
1288
- const watchId = requiredString(params.watch_id, "watch_id");
1289
- const watchArgs = [
1290
- "watch-status",
1291
- "--watch",
1292
- watchId,
1293
- "--openclaw-session",
1294
- requiredOpenClawSessionKey(toolContext?.sessionKey)
1295
- ];
1296
- pushOptional(watchArgs, "--store-dir", resolvePluginStoreDir(config));
1297
- return watchArgs;
1298
- }
1299
- const args = [
1300
- "status",
1301
- "--reconcile"
1302
- ];
1303
- pushTurnTarget(args, params);
1304
- pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
1305
- pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ??
1306
- numberString(api.pluginConfig?.idleTimeoutMinutes));
1307
- if (params.trace === true) {
1308
- args.push("--trace");
1309
- }
1310
- return args;
1311
- }
1312
- async function runStatusRequest(api, params, toolContext) {
1313
- const statusParams = isRecord(params) ? params : {};
1314
- const statusArgs = buildStatusCliArgs(api, statusParams, toolContext);
1315
- invalidateRequestedInteractionOffers(api, toolContext?.sessionKey, toolContext?.sessionId, statusParams);
1316
- return runHostAwareCli(api, statusArgs);
1317
- }
1318
- function terminalScreenExcerpt(result) {
1319
- const screen = result.terminal_screen;
1320
- const text = typeof screen === "string"
1321
- ? screen
1322
- : isRecord(screen)
1323
- ? stringValue(screen.excerpt) ??
1324
- stringValue(screen.text) ??
1325
- stringValue(screen.content)
1326
- : undefined;
1327
- if (!text) {
1328
- return undefined;
1329
- }
1330
- return text.length <= 1600
1331
- ? text
1332
- : `…${text.slice(-1599)}`;
1333
- }
1334
- async function runSendRequest(api, params, toolContext, messageId) {
1335
- if (Object.hasOwn(params, "turn_id")) {
1336
- const unexpected = Object.keys(params).filter((key) => key !== "turn_id");
1337
- if (unexpected.length > 0) {
1338
- throw new Error("send retry_submission accepts exactly turn_id; do not pass request, terminal_id, session_id, timeout overrides, or callback route data");
1339
- }
1340
- const turnId = authoritativeManagedId(params.turn_id, "turn_id");
1341
- await privateActionArguments(api, {
1342
- tool: "agent_knock_knock_send",
1343
- matches: (argumentsValue) => Object.keys(argumentsValue).length === 1 &&
1344
- stringValue(argumentsValue.turn_id) === turnId
1345
- });
1346
- const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
1347
- const args = ["send", "--turn", turnId];
1348
- pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
1349
- const result = await runHostAwareCli(api, args);
1350
- return {
1351
- ...result,
1352
- ...normalizedTerminalSendResultContract(result)
1353
- };
1354
- }
1355
- const requestedType = Object.hasOwn(params, "type")
1356
- ? stringValue(params.type)
1357
- : "task";
1358
- if (requestedType !== "task") {
1359
- throw new Error("ordinary send type must be task; use agent_knock_knock_respond for an in-flight response");
1360
- }
1361
- if (Object.hasOwn(params, "session_id") && Object.hasOwn(params, "terminal_id")) {
1362
- throw new Error("ordinary send accepts only one of session_id or terminal_id");
1363
- }
1364
- const sessionId = Object.hasOwn(params, "session_id")
1365
- ? authoritativeManagedId(params.session_id, "session_id")
1366
- : undefined;
1367
- const terminalId = Object.hasOwn(params, "terminal_id")
1368
- ? requiredString(params.terminal_id, "terminal_id")
1369
- : undefined;
1370
- if (terminalId && !/^terminal:v[0-9]+:\S+$/u.test(terminalId)) {
1371
- throw new Error("terminal_id must be the exact full terminal identifier returned by AKK list");
1372
- }
1373
- if (!sessionId && !terminalId) {
1374
- return runDelegate(api, { ...params, messageId }, toolContext);
1375
- }
1376
- const terminalAction = terminalId
1377
- ? await privateActionArguments(api, {
1378
- tool: "agent_knock_knock_send",
1379
- terminalId,
1380
- matches: (argumentsValue) => stringValue(argumentsValue.selector) === terminalId ||
1381
- stringValue(argumentsValue.terminal_id) === terminalId
1382
- })
1383
- : undefined;
1384
- const expectedTerminalToken = terminalAction
1385
- ? requiredString(terminalAction.expected_terminal_token, "current internal terminal send authority")
1386
- : undefined;
1387
- const expectedManagedTerminalToken = terminalAction
1388
- ? stringValue(terminalAction.expected_managed_terminal_token)
1389
- : undefined;
1390
- const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
1391
- const openclawSession = stringValue(toolContext?.sessionKey) ??
1392
- "agent:main:main";
1393
- const args = [
1394
- "send"
1395
- ];
1396
- if (sessionId) {
1397
- args.push("--session", sessionId);
1398
- }
1399
- else {
1400
- args.push("--conversation", requiredString(terminalId, "terminal_id"));
1401
- }
1402
- pushOptional(args, "--expected-terminal-token", expectedTerminalToken);
1403
- pushOptional(args, "--expected-managed-terminal-token", expectedManagedTerminalToken);
1404
- args.push("--message", requiredString(params.request, "request"), "--background");
1405
- pushOptional(args, "--type", stringValue(params.type));
1406
- pushOptional(args, "--message-id", messageId);
1407
- pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
1408
- pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ??
1409
- numberString(config.idleTimeoutMinutes));
1410
- pushOptional(args, "--agent-timeout-minutes", numberString(params.agentTimeoutMinutes) ??
1411
- numberString(config.agentTimeoutMinutes));
1412
- pushOptional(args, "--agent-hard-timeout-minutes", numberString(params.agentHardTimeoutMinutes) ??
1413
- numberString(config.agentHardTimeoutMinutes));
1414
- pushOptional(args, "--openclaw-session", openclawSession);
1415
- pushOptional(args, "--gateway-method", CALLBACK_METHOD);
1416
- pushOptional(args, "--gateway-session", openclawSession);
1417
- pushOptional(args, "--openclaw-bin", stringValue(config.openclawBin));
1418
- const result = await runHostAwareCli(api, args);
1419
- return {
1420
- ...result,
1421
- ...normalizedTerminalSendResultContract(result)
1422
- };
1423
- }
1424
- function toolResult(result, { submissionErrors = false, normalizeTurnIdentity = true, forceError = false, modelProjection = undefined, compactText = false } = {}) {
1425
- const normalized = normalizeTurnIdentity ? withTurnIdentity(result) : result;
1426
- const sanitized = sanitizeModelFacingValue(normalized);
1427
- const modelFacing = modelProjection
1428
- ? modelProjection(sanitized)
1429
- : sanitized;
1430
- const submissionError = submissionErrors && isSubmissionError(normalized);
1431
- return {
1432
- content: [
1433
- {
1434
- type: "text",
1435
- text: compactText
1436
- ? JSON.stringify(modelFacing)
1437
- : JSON.stringify(modelFacing, null, 2)
1438
- }
1439
- ],
1440
- details: modelFacing,
1441
- ...(submissionError || forceError ? { isError: true } : {})
1442
- };
1443
- }
1444
- async function privateList(api, options = {}) {
1445
- const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
1446
- const args = ["list"];
1447
- if (options.reconcile !== false)
1448
- args.push("--reconcile");
1449
- pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
1450
- pushOptional(args, "--idle-timeout-minutes", numberString(config.idleTimeoutMinutes));
1451
- return runHostAwareCli(api, args);
1452
- }
1453
- async function privateThreadDiscovery(api, terminalId) {
1454
- const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
1455
- const args = [
1456
- "list-resumable-threads",
1457
- "--terminal",
1458
- terminalId
1459
- ];
1460
- pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
1461
- pushOptional(args, "--codex-home", stringValue(config.codexHome));
1462
- return runHostAwareCli(api, args);
1463
- }
1464
- function rememberDisplayedModelOptionsOffer(api, sessionKeyValue, sessionIdValue, requestedTerminalIdValue, result) {
1465
- const sessionKey = requiredOpenClawSessionKey(sessionKeyValue);
1466
- const sessionId = requiredOpenClawSessionId(sessionIdValue);
1467
- const requestedTerminalId = requiredString(requestedTerminalIdValue, "terminal_id");
1468
- if (!isRecord(result)) {
1469
- throw new Error("model-options returned an invalid result");
1470
- }
1471
- const terminalId = requiredString(result.terminal_id, "model-options terminal_id");
1472
- if (terminalId !== requestedTerminalId) {
1473
- throw new Error("model-options returned a different terminal");
1474
- }
1475
- const scope = requiredModelSwitchScope(result.agent, result.scope);
1476
- const models = modelOptionsCatalog(result.models);
1477
- const args = setModelActionArguments(result);
1478
- if (stringValue(args.terminal ?? args.terminal_id) !== terminalId) {
1479
- throw new Error("set-model action belongs to a different terminal");
1480
- }
1481
- const catalogFingerprint = requiredString(args.expected_catalog_fingerprint, "current internal model catalog authority");
1482
- if (catalogFingerprint !== stringValue(result.catalog_fingerprint)) {
1483
- throw new Error("set-model action does not match the displayed catalog");
1484
- }
1485
- const key = modelOptionsOfferKey(sessionKey, sessionId, terminalId);
1486
- consumeOpenClawPrivateAuthorityOffer(api, key);
1487
- rememberOpenClawPrivateAuthorityOffer(api, key, {
1488
- fingerprint: catalogFingerprint,
1489
- args,
1490
- scope,
1491
- models
1492
- });
1493
- }
1494
- function modelOptionsCatalog(value) {
1495
- if (!Array.isArray(value) || value.length === 0) {
1496
- throw new Error("model-options returned no semantic model choices");
1497
- }
1498
- const models = value.map((entry) => {
1499
- if (!isRecord(entry))
1500
- throw new Error("model-options returned an invalid model");
1501
- const id = requiredModelSemanticValue(entry.id, "model id", 160);
1502
- if (!Array.isArray(entry.reasoning_efforts)) {
1503
- throw new Error(`model ${id} has no reasoning-effort catalog`);
1504
- }
1505
- const reasoningEfforts = entry.reasoning_efforts.map((effort) => requiredReasoningEffort(effort, `reasoning effort for ${id}`));
1506
- if (new Set(reasoningEfforts).size !== reasoningEfforts.length) {
1507
- throw new Error(`model ${id} has duplicate reasoning efforts`);
1508
- }
1509
- return { id, reasoning_efforts: reasoningEfforts };
1510
- });
1511
- const ids = models.map((model) => String(model.id));
1512
- if (new Set(ids).size !== ids.length) {
1513
- throw new Error("model-options returned duplicate model ids");
1514
- }
1515
- return models;
1516
- }
1517
- function setModelActionArguments(result) {
1518
- const availableActions = isRecord(result.available_actions)
1519
- ? result.available_actions
1520
- : undefined;
1521
- const action = isRecord(availableActions?.set_model)
1522
- ? availableActions.set_model
1523
- : undefined;
1524
- if (action?.tool !== "agent_knock_knock_set_model" ||
1525
- !isRecord(action.arguments)) {
1526
- throw new Error("model-options did not advertise a typed set-model action");
1527
- }
1528
- requiredString(action.arguments.expected_binding_token, "current internal set-model binding authority");
1529
- return action.arguments;
1530
- }
1531
- function requiredModelSwitchScope(agentValue, scopeValue) {
1532
- const agent = requiredString(agentValue, "model-options agent");
1533
- const scope = requiredString(scopeValue, "model-options scope");
1534
- const expected = agent === "codex"
1535
- ? "current_and_new_sessions"
1536
- : agent === "claude"
1537
- ? "current_session"
1538
- : undefined;
1539
- if (!expected || scope !== expected) {
1540
- throw new Error("model-options returned an unsupported agent or mutation scope");
1541
- }
1542
- return scope;
1543
- }
1544
- function modelOptionsOfferKey(sessionKey, sessionId, terminalId) {
1545
- return privateAuthorityOfferKey(sessionKey, sessionId, OPENCLAW_MODEL_OPTIONS_AUTHORITY_KIND, { type: "terminal_id", id: terminalId });
1546
- }
1547
- function buildPrivateSetModelArgs(api, params, context) {
1548
- assertOnlyModelControlParameters(params, ["terminal_id", "model", "reasoning_effort"], "set_model");
1549
- const terminalId = requiredString(params.terminal_id, "terminal_id");
1550
- const model = requiredModelSemanticValue(params.model, "model", 160);
1551
- const reasoningEffort = requiredReasoningEffort(params.reasoning_effort, "reasoning_effort");
1552
- const offered = consumeOpenClawPrivateAuthorityOffer(api, modelOptionsOfferKey(context.sessionKey, context.sessionId, terminalId));
1553
- if (!offered || !isRecord(offered.args) || !Array.isArray(offered.models)) {
1554
- throw new Error("set_model requires current choices shown by agent_knock_knock_model_options in this controller conversation; refresh model options and choose one exact semantic tuple");
1555
- }
1556
- assertOfferedModelTuple(offered.models, model, reasoningEffort);
1557
- const offeredTerminalId = stringValue(offered.args.terminal ?? offered.args.terminal_id);
1558
- if (offeredTerminalId !== terminalId) {
1559
- throw new Error("the displayed set-model action belongs to another terminal");
1560
- }
1561
- const fingerprint = requiredString(offered.args.expected_catalog_fingerprint, "current internal model catalog authority");
1562
- if (fingerprint !== offered.fingerprint) {
1563
- throw new Error("the displayed model catalog authority changed");
1564
- }
1565
- const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
1566
- const args = buildAkkCommandCliArgs({
1567
- action: "set-model",
1568
- terminalId,
1569
- model,
1570
- reasoningEffort
1571
- }, config, {
1572
- expectedBindingToken: offered.args.expected_binding_token,
1573
- expectedCatalogFingerprint: fingerprint
1574
- });
1575
- if (!args)
1576
- throw new Error("could not build set-model command");
1577
- return args;
1578
- }
1579
- function assertOnlyModelControlParameters(params, allowed, label) {
1580
- const unexpected = Object.keys(params).filter((key) => !allowed.includes(key));
1581
- if (unexpected.length > 0) {
1582
- throw new Error(`${label} accepts only typed semantic fields; raw commands, keys, menu indexes, scope, labels, tokens, and fingerprints are forbidden`);
1583
- }
1584
- }
1585
- function assertOfferedModelTuple(modelEntries, model, reasoningEffort) {
1586
- const entry = modelEntries.find((candidate) => isRecord(candidate) && candidate.id === model);
1587
- if (!isRecord(entry) || !Array.isArray(entry.reasoning_efforts)) {
1588
- throw new Error("model was not advertised by the current native catalog");
1589
- }
1590
- if (!entry.reasoning_efforts.includes(reasoningEffort)) {
1591
- throw new Error("reasoning_effort was not advertised for this model by the current native catalog");
1592
- }
1593
- }
1594
- function requiredModelSemanticValue(value, label, maxLength) {
1595
- const text = requiredString(value, label);
1596
- if (text.length > maxLength ||
1597
- !/^[A-Za-z0-9][A-Za-z0-9._:/+\-]*$/u.test(text)) {
1598
- throw new Error(`${label} must be one exact advertised semantic id`);
1599
- }
1600
- return text;
1601
- }
1602
- function requiredReasoningEffort(value, label) {
1603
- const text = requiredString(value, label);
1604
- if (text.length > 64 || !/^[a-z][a-z0-9_-]*$/u.test(text)) {
1605
- throw new Error(`${label} must be one exact advertised semantic value`);
1606
- }
1607
- return text;
1608
- }
1609
- async function privateTerminalActionArguments(api, terminalId, tool, options = {}) {
1610
- return privateActionArguments(api, {
1611
- tool,
1612
- terminalId,
1613
- reconcile: options.reconcile,
1614
- matches: (argumentsValue) => stringValue(argumentsValue.terminal_id) === terminalId
1615
- });
1616
- }
1617
- async function privateActionArguments(api, input) {
1618
- const result = await privateList(api, { reconcile: input.reconcile });
1619
- const terminals = input.terminalId
1620
- ? terminalRows(result).filter((terminal) => stringValue(terminal.id) === input.terminalId)
1621
- : terminalRows(result);
1622
- if (input.terminalId && terminals.length !== 1) {
1623
- throw new Error(`terminal ${input.terminalId} is not uniquely present in the current AKK list`);
1624
- }
1625
- const candidates = (input.tool === "agent_knock_knock_close"
1626
- ? authoritativeHandoffActionArguments(terminals)
1627
- : terminals.flatMap((terminal) => authoritativeTerminalActionArguments(terminal, input.tool))).filter(input.matches);
1628
- const uniqueCandidates = [...new Map(candidates.map((candidate) => [
1629
- JSON.stringify(candidate),
1630
- candidate
1631
- ])).values()];
1632
- if (uniqueCandidates.length !== 1) {
1633
- throw new Error(`the current AKK list does not advertise one exact ${input.tool} action for the requested semantic target`);
1634
- }
1635
- return uniqueCandidates[0];
1636
- }
1637
- function rememberDisplayedPrivateAuthorityOffers(api, sessionKeyValue, sessionIdValue, result) {
1638
- const sessionKey = stringValue(sessionKeyValue);
1639
- const sessionId = stringValue(sessionIdValue);
1640
- if (!sessionKey || !sessionId || !isRecord(result))
1641
- return;
1642
- rememberDisplayedHandoffActions(api, sessionKey, sessionId, result);
1643
- rememberDisplayedReconcileActions(api, sessionKey, sessionId, result);
1644
- }
1645
- function rememberDisplayedHandoffActions(api, sessionKey, sessionId, result) {
1646
- for (const args of uniqueArgumentObjects(authoritativeHandoffActionArguments(terminalRows(result)))) {
1647
- const turnId = stringValue(args.turn_id ?? args.conversation_id);
1648
- if (!turnId ||
1649
- stringValue(args.reason) !== "superseded_by_human_context_switch" ||
1650
- !stringValue(args.expected_handoff_token)) {
1651
- continue;
1652
- }
1653
- rememberOpenClawPrivateAuthorityOffer(api, privateAuthorityOfferKey(sessionKey, sessionId, OPENCLAW_HANDOFF_AUTHORITY_KIND, { type: "turn_id", id: turnId }), { args });
1654
- }
1655
- }
1656
- function rememberDisplayedReconcileActions(api, sessionKey, sessionId, result) {
1657
- for (const args of uniqueArgumentObjects(terminalRows(result).flatMap((terminal) => authoritativeTerminalActionArguments(terminal, "agent_knock_knock_reconcile_binding")))) {
1658
- const terminalId = stringValue(args.terminal_id);
1659
- const conflictingSessionId = stringValue(args.conflicting_session_id);
1660
- if (!terminalId || !conflictingSessionId)
1661
- continue;
1662
- rememberOpenClawPrivateAuthorityOffer(api, privateAuthorityOfferKey(sessionKey, sessionId, OPENCLAW_RECONCILE_BINDING_AUTHORITY_KIND, { type: "terminal_id", id: terminalId }), { args });
1663
- }
1664
- }
1665
- function rememberDisplayedApprovalOffer(api, sessionKeyValue, sessionIdValue, result) {
1666
- const sessionKey = stringValue(sessionKeyValue);
1667
- const sessionId = stringValue(sessionIdValue);
1668
- if (!sessionKey || !sessionId || !isRecord(result))
1669
- return;
1670
- const decisionFingerprints = currentApprovalDecisionFingerprints(result);
1671
- const fingerprint = decisionFingerprints.approve_once;
1672
- if (!fingerprint)
1673
- return;
1674
- const target = approvalTargetFromStatus(result);
1675
- if (!target)
1676
- return;
1677
- rememberOpenClawPrivateAuthorityOffer(api, openClawApprovalAuthorityOfferKey(sessionKey, sessionId, target), {
1678
- fingerprint,
1679
- decision_fingerprints: decisionFingerprints
1680
- });
1681
- }
1682
- function rememberDisplayedInteractionOffer(api, sessionKeyValue, sessionIdValue, result) {
1683
- const sessionKey = stringValue(sessionKeyValue);
1684
- const sessionId = stringValue(sessionIdValue);
1685
- if (!sessionKey || !sessionId || !isRecord(result))
1686
- return;
1687
- const displayed = displayedInteractionStatus(result);
1688
- if (!displayed)
1689
- return;
1690
- const expectedSubjectId = stringValue(displayed.expectedSubjectId);
1691
- if (!expectedSubjectId || expectedSubjectId === "unknown")
1692
- return;
1693
- invalidateOpenClawInteractionAuthorityOffersForSubject(api, sessionKey, sessionId, displayed.expectedSubjectKind, expectedSubjectId);
1694
- const fingerprint = stringValue(displayed.fields.interaction_prompt_fingerprint);
1695
- if (!isExactInteractionFingerprint(fingerprint))
1696
- return;
1697
- let interactionState;
1698
- try {
1699
- interactionState = validateAnyTerminalInteractionProjection(displayed.fields.interaction_state);
1700
- }
1701
- catch {
1702
- return;
1703
- }
1704
- const subject = interactionProjectionSubject(interactionState);
1705
- if (interactionState.state !== "pending" ||
1706
- interactionState.capabilities.respond !== true ||
1707
- subject.kind !== displayed.expectedSubjectKind ||
1708
- subject.id !== expectedSubjectId ||
1709
- (interactionState.version === TERMINAL_INTERACTION_SUBJECT_VERSION &&
1710
- (interactionState.response_authority !== "executable" ||
1711
- interactionState.prompt_fingerprint !== fingerprint))) {
1712
- return;
1713
- }
1714
- rememberOpenClawPrivateAuthorityOffer(api, openClawInteractionAuthorityOfferKey(sessionKey, sessionId, subject.kind, subject.id, interactionState.interaction_id), {
1715
- fingerprint,
1716
- interaction_state: interactionState
1717
- });
1718
- }
1719
- function invalidateRequestedInteractionOffers(api, sessionKeyValue, sessionIdValue, params) {
1720
- const sessionKey = stringValue(sessionKeyValue);
1721
- const sessionId = stringValue(sessionIdValue);
1722
- if (!sessionKey || !sessionId)
1723
- return;
1724
- const watchId = stringValue(params.watch_id);
1725
- if (watchId) {
1726
- invalidateOpenClawInteractionAuthorityOffersForSubject(api, sessionKey, sessionId, "terminal_watch", watchId);
1727
- return;
1728
- }
1729
- const turnId = stringValue(params.turn_id);
1730
- if (!turnId)
1731
- return;
1732
- invalidateOpenClawInteractionAuthorityOffersForSubject(api, sessionKey, sessionId, "managed_turn", turnId);
1733
- }
1734
- function displayedInteractionStatus(result) {
1735
- const watch = isRecord(result.watch) ? result.watch : undefined;
1736
- if (watch) {
1737
- return {
1738
- fields: watch,
1739
- expectedSubjectKind: "terminal_watch",
1740
- expectedSubjectId: stringValue(watch.watch_id)
1741
- };
1742
- }
1743
- const terminalStatus = isRecord(result.terminal_status)
1744
- ? result.terminal_status
1745
- : undefined;
1746
- if (terminalStatus) {
1747
- return {
1748
- fields: terminalStatus,
1749
- expectedSubjectKind: "managed_turn",
1750
- expectedSubjectId: publicTurnIdentity(result).turnId
1751
- };
1752
- }
1753
- if (result.interaction_state === undefined)
1754
- return undefined;
1755
- const watchId = stringValue(result.watch_id);
1756
- return {
1757
- fields: result,
1758
- expectedSubjectKind: watchId ? "terminal_watch" : "managed_turn",
1759
- expectedSubjectId: watchId ?? publicTurnIdentity(result).turnId
1760
- };
1761
- }
1762
- function interactionProjectionSubject(projection) {
1763
- if (projection.version === TERMINAL_INTERACTION_SUBJECT_VERSION) {
1764
- return {
1765
- kind: projection.subject.kind,
1766
- id: terminalInteractionSubjectId(projection.subject)
1767
- };
1768
- }
1769
- return { kind: "managed_turn", id: projection.turn_id };
1770
- }
1771
- function approvalTargetFromStatus(result) {
1772
- const conversationId = stringValue(result.conversation_id);
1773
- if (stringValue(result.source) === "terminal_control" ||
1774
- conversationId?.startsWith("terminal:")) {
1775
- return conversationId
1776
- ? { type: "terminal_id", id: conversationId }
1777
- : undefined;
1778
- }
1779
- const { turnId } = publicTurnIdentity(result);
1780
- return turnId && turnId !== "unknown"
1781
- ? { type: "turn_id", id: turnId }
1782
- : undefined;
1783
- }
1784
- function uniqueArgumentObjects(candidates) {
1785
- return [...new Map(candidates.map((args) => [
1786
- JSON.stringify(args),
1787
- args
1788
- ])).values()];
1789
- }
1790
- function privateAuthorityOfferKey(sessionKey, sessionId, kind, target) {
1791
- return { sessionKey, sessionId, kind, target };
1792
- }
1793
- async function consumeDisplayedPrivateAction(api, input) {
1794
- const offered = consumeOpenClawPrivateAuthorityOffer(api, {
1795
- sessionKey: input.sessionKey,
1796
- sessionId: input.sessionId,
1797
- kind: input.kind,
1798
- target: input.target
1799
- });
1800
- if (!offered || !isRecord(offered.args)) {
1801
- throw new Error(`${input.tool} requires a current action shown by AKK list in this controller session; refresh list, review it, and explicitly confirm again`);
1802
- }
1803
- const current = await privateActionArguments(api, input);
1804
- if (JSON.stringify(current) !== JSON.stringify(offered.args)) {
1805
- throw new Error(`${input.tool} authority changed after it was shown; refresh AKK list, review the current action, and explicitly confirm again`);
1806
- }
1807
- return current;
1808
- }
1809
- function terminalRows(result) {
1810
- return Array.isArray(result.terminals)
1811
- ? result.terminals.filter(isRecord)
1812
- : [];
1813
- }
1814
- function authoritativeTerminalActionArguments(terminal, tool) {
1815
- const availableActions = isRecord(terminal.available_actions)
1816
- ? terminal.available_actions
1817
- : undefined;
1818
- if (!availableActions)
1819
- return [];
1820
- return Object.values(availableActions).flatMap((action) => isRecord(action) && action.tool === tool && isRecord(action.arguments)
1821
- ? [action.arguments]
1822
- : []);
1823
- }
1824
- function authoritativeHandoffActionArguments(terminals) {
1825
- return terminals.flatMap((terminal) => {
1826
- const decision = isRecord(terminal.handoff_decision)
1827
- ? terminal.handoff_decision
1828
- : undefined;
1829
- const choices = isRecord(decision?.choices) ? decision.choices : undefined;
1830
- const takeOver = isRecord(choices?.take_over_current)
1831
- ? choices.take_over_current
1832
- : undefined;
1833
- const action = isRecord(takeOver?.action) ? takeOver.action : undefined;
1834
- return decision?.kind === "active_turn_requires_decision" &&
1835
- action?.tool === "agent_knock_knock_close" &&
1836
- action.requires_explicit_user_confirmation === true &&
1837
- isRecord(action.arguments)
1838
- ? [action.arguments]
1839
- : [];
1840
- });
1841
- }
1842
- function currentApprovalFingerprint(result, decision) {
1843
- const fingerprint = isRecord(result)
1844
- ? currentApprovalDecisionFingerprints(result)[decision]
1845
- : undefined;
1846
- if (!fingerprint) {
1847
- throw new Error(`the current status does not contain one exact ${decision} choice; refresh status and ask the user to review it again`);
1848
- }
1849
- return fingerprint;
1850
- }
1851
- async function buildPrivateApprovalArgs(api, params, { sessionKey, sessionId }) {
1852
- const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
1853
- const turnId = stringValue(params.turn_id);
1854
- const terminalId = stringValue(params.terminal_id);
1855
- const decisionValue = params.decision ?? "approve_once";
1856
- if (!isTerminalApprovalDecision(decisionValue)) {
1857
- throw new Error("decision must be one of: approve_once, reject");
1858
- }
1859
- const decision = decisionValue;
1860
- if (Boolean(turnId) === Boolean(terminalId)) {
1861
- throw new Error("approve requires exactly one of turn_id or terminal_id");
1862
- }
1863
- if (terminalId && decision !== "approve_once") {
1864
- throw new Error("terminal-scoped approval supports approve_once only; reject requires an exact managed Turn");
1865
- }
1866
- const target = terminalId
1867
- ? { type: "terminal_id", id: terminalId }
1868
- : { type: "turn_id", id: requiredString(turnId, "turn_id") };
1869
- const offered = consumeOpenClawPrivateAuthorityOffer(api, openClawApprovalAuthorityOfferKey(sessionKey, sessionId, target));
1870
- const offeredDecisions = isRecord(offered?.decision_fingerprints)
1871
- ? offered.decision_fingerprints
1872
- : undefined;
1873
- const offeredFingerprint = stringValue(offeredDecisions?.[decision]) ??
1874
- (decision === "approve_once" ? stringValue(offered?.fingerprint) : undefined) ??
1875
- (isRecord(offered?.args)
1876
- ? decision === "approve_once"
1877
- ? stringValue(offered.args.expected_approval_fingerprint)
1878
- : undefined
1879
- : undefined);
1880
- if (!isExactApprovalFingerprint(offeredFingerprint)) {
1881
- throw new Error("approve requires a current approval request shown by agent_knock_knock_status in this controller conversation; refresh status, ask the user to review it, and explicitly confirm again");
1882
- }
1883
- const action = terminalId
1884
- ? await privateActionArguments(api, {
1885
- tool: "agent_knock_knock_approve",
1886
- terminalId,
1887
- matches: (argumentsValue) => stringValue(argumentsValue.terminal_id ?? argumentsValue.conversation_id) === terminalId
1888
- })
1889
- : undefined;
1890
- const statusArgs = ["status", "--reconcile"];
1891
- if (terminalId) {
1892
- statusArgs.push("--conversation", terminalId);
1893
- }
1894
- else {
1895
- statusArgs.push("--turn", requiredString(turnId, "turn_id"));
1896
- }
1897
- pushOptional(statusArgs, "--store-dir", resolvePluginStoreDir(config));
1898
- const args = ["approve"];
1899
- if (terminalId) {
1900
- args.push("--conversation", terminalId);
1901
- }
1902
- else {
1903
- args.push("--turn", requiredString(turnId, "turn_id"));
1904
- }
1905
- const currentFingerprint = currentApprovalFingerprint(await runHostAwareCli(api, statusArgs), decision);
1906
- if (currentFingerprint !== offeredFingerprint) {
1907
- throw new Error("the approval request changed after it was shown; refresh AKK status, ask the user to review the current request, and explicitly confirm again");
1908
- }
1909
- args.push("--decision", decision);
1910
- args.push("--expected-approval-fingerprint", currentFingerprint);
1911
- if (terminalId) {
1912
- args.push("--expected-terminal-token", requiredString(action?.expected_terminal_token, "current internal terminal approval authority"));
1913
- }
1914
- pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
1915
- return args;
1916
- }
1917
- function buildPrivateInteractionResponseArgs(api, params, { sessionKey, sessionId }) {
1918
- const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
1919
- const requestedSubject = requestedInteractionSubject(params);
1920
- const interactionId = requiredTerminalInteractionIdentifier(params.interaction_id, "interaction_id");
1921
- const offered = consumeOpenClawPrivateAuthorityOffer(api, openClawInteractionAuthorityOfferKey(sessionKey, sessionId, requestedSubject.kind, requestedSubject.id, interactionId));
1922
- const fingerprint = stringValue(offered?.fingerprint);
1923
- if (!isExactInteractionFingerprint(fingerprint) ||
1924
- offered?.interaction_state === undefined) {
1925
- throw new Error("respond_interaction requires a current pending interaction shown by agent_knock_knock_status in this controller conversation; refresh status, review the current questions, and respond again");
1926
- }
1927
- const projection = validateAnyTerminalInteractionProjection(offered.interaction_state);
1928
- assertInteractionSubjectMatchesProjection(requestedSubject, projection);
1929
- const responseInput = interactionResponseForProjection(params, projection);
1930
- const response = validateAnyTerminalInteractionResponse(responseInput, projection, {
1931
- // This consumes a still-live, session/incarnation-bound private offer.
1932
- // The CLI/bridge path always performs exact live terminal recaptures
1933
- // before it can reserve or dispatch input, so projection expiry is a
1934
- // recheck trigger rather than proof that the questionnaire disappeared.
1935
- allowExpiredForLiveRecapture: true
1936
- });
1937
- const args = [
1938
- "respond-interaction",
1939
- requestedSubject.cliOption,
1940
- requestedSubject.id,
1941
- "--interaction",
1942
- response.interaction_id,
1943
- "--response-json",
1944
- JSON.stringify(response),
1945
- "--expected-interaction-fingerprint",
1946
- fingerprint,
1947
- "--expected-interaction-expires-at",
1948
- projection.expires_at,
1949
- "--openclaw-session",
1950
- sessionKey
1951
- ];
1952
- pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
1953
- return args;
1954
- }
1955
- function requestedInteractionSubject(params) {
1956
- const hasTurn = Object.hasOwn(params, "turn_id");
1957
- const hasWatch = Object.hasOwn(params, "watch_id");
1958
- if (hasTurn === hasWatch) {
1959
- throw new Error("respond_interaction requires exactly one of turn_id or watch_id");
1960
- }
1961
- return hasTurn
1962
- ? {
1963
- kind: "managed_turn",
1964
- id: requiredTerminalInteractionIdentifier(params.turn_id, "turn_id"),
1965
- cliOption: "--turn"
1966
- }
1967
- : {
1968
- kind: "terminal_watch",
1969
- id: requiredTerminalInteractionIdentifier(params.watch_id, "watch_id"),
1970
- cliOption: "--watch"
1971
- };
1972
- }
1973
- function assertInteractionSubjectMatchesProjection(requested, projection) {
1974
- const projected = interactionProjectionSubject(projection);
1975
- if (requested.kind !== projected.kind || requested.id !== projected.id) {
1976
- throw new Error("respond_interaction target does not match the displayed interaction subject; refresh status and respond to its exact turn_id or watch_id");
1977
- }
1978
- }
1979
- function interactionResponseForProjection(params, projection) {
1980
- if (projection.version !== TERMINAL_INTERACTION_SUBJECT_VERSION) {
1981
- return params;
1982
- }
1983
- return {
1984
- interaction_id: params.interaction_id,
1985
- subject: projection.subject,
1986
- ...(projection.subject.kind === "managed_turn"
1987
- ? { turn_id: params.turn_id }
1988
- : {}),
1989
- answers: params.answers
1990
- };
1991
- }
1992
- function isExactApprovalFingerprint(value) {
1993
- return typeof value === "string" && /^[a-f0-9]{64}$/u.test(value);
1994
- }
1995
- function isExactInteractionFingerprint(value) {
1996
- return typeof value === "string" && /^[a-f0-9]{64}$/u.test(value);
1997
- }
1998
- function currentApprovalDecisionFingerprints(result) {
1999
- const terminalStatus = isRecord(result.terminal_status)
2000
- ? result.terminal_status
2001
- : undefined;
2002
- const states = [
2003
- isRecord(result.approval_state) ? result.approval_state : undefined,
2004
- isRecord(terminalStatus?.approval_state)
2005
- ? terminalStatus.approval_state
2006
- : undefined
2007
- ];
2008
- const found = new Map();
2009
- const remember = (decision, value) => {
2010
- if (!isExactApprovalFingerprint(value))
2011
- return;
2012
- const fingerprints = found.get(decision) ?? new Set();
2013
- fingerprints.add(value);
2014
- found.set(decision, fingerprints);
2015
- };
2016
- for (const state of states) {
2017
- const fingerprint = state?.approvable === true
2018
- ? stringValue(state.fingerprint)
2019
- : undefined;
2020
- remember("approve_once", fingerprint);
2021
- if (state?.approvable !== true || !Array.isArray(state.choices))
2022
- continue;
2023
- for (const choice of state.choices) {
2024
- if (isRecord(choice) &&
2025
- isTerminalApprovalDecision(choice.decision)) {
2026
- remember(choice.decision, choice.fingerprint);
2027
- }
2028
- }
2029
- }
2030
- return Object.fromEntries([...found.entries()].flatMap(([decision, fingerprints]) => fingerprints.size === 1
2031
- ? [[decision, [...fingerprints][0]]]
2032
- : []));
2033
- }
2034
- function sanitizeModelFacingValue(value, parentKey, path = []) {
2035
- if (Array.isArray(value)) {
2036
- if (parentKey === "missing_required") {
2037
- return value.filter((item) => typeof item !== "string" ||
2038
- !isAkkModelFacingPrivateAuthorityField(item));
2039
- }
2040
- return value.map((item, index) => sanitizeModelFacingValue(item, parentKey, [...path, String(index)]));
2041
- }
2042
- if (!isRecord(value)) {
2043
- if (typeof value !== "string") {
2044
- return value;
2045
- }
2046
- if (isAkkModelFacingDiagnosticField(parentKey)) {
2047
- return sanitizeAkkModelFacingDiagnosticText(value);
2048
- }
2049
- return isLegacyAuthorityInstructionPath(path)
2050
- ? sanitizeAkkModelFacingLegacyAuthorityInstructionText(value)
2051
- : value;
2052
- }
2053
- const output = {};
2054
- for (const [key, item] of Object.entries(value)) {
2055
- if (isAkkModelFacingPrivateAuthorityField(key) ||
2056
- key === "_user_explicit_composer_ready" ||
2057
- key === "composer_draft" ||
2058
- key === "selection_snapshot" ||
2059
- key === "live_native_thread_id" ||
2060
- (key === "fingerprint" && parentKey === "approval_state")) {
2061
- continue;
2062
- }
2063
- if (key === "interaction_state") {
2064
- try {
2065
- output[key] = sanitizeModelFacingValue(validateAnyTerminalInteractionProjection(item), undefined, [...path, key]);
2066
- }
2067
- catch {
2068
- continue;
2069
- }
2070
- continue;
2071
- }
2072
- output[key] = sanitizeModelFacingValue(item, key, [...path, key]);
2073
- }
2074
- if (output.tool === "agent_knock_knock_send" && isRecord(output.arguments)) {
2075
- const selector = stringValue(output.arguments.selector);
2076
- if (selector && /^terminal:v[0-9]+:\S+$/u.test(selector)) {
2077
- const { selector: _selector, ...argumentsValue } = output.arguments;
2078
- output.arguments = { ...argumentsValue, terminal_id: selector };
2079
- }
2080
- }
2081
- if (output.tool === "agent_knock_knock_approve" && isRecord(output.arguments)) {
2082
- const conversationId = stringValue(output.arguments.conversation_id);
2083
- if (conversationId && /^terminal:v[0-9]+:\S+$/u.test(conversationId)) {
2084
- const { conversation_id: _conversationId, ...argumentsValue } = output.arguments;
2085
- output.arguments = { ...argumentsValue, terminal_id: conversationId };
2086
- }
2087
- const approvalArguments = isRecord(output.arguments)
2088
- ? output.arguments
2089
- : {};
2090
- const terminalId = stringValue(approvalArguments.terminal_id);
2091
- output.before_call = {
2092
- tool: "agent_knock_knock_status",
2093
- arguments: terminalId
2094
- ? { conversation_id: terminalId }
2095
- : { ...approvalArguments },
2096
- use: "Show the current approval request to the user. After explicit confirmation, call approve with only this semantic target; AKK revalidates the prompt privately."
2097
- };
2098
- delete output.missing_required;
2099
- }
2100
- if (output.tool === "agent_knock_knock_set_model" &&
2101
- isRecord(output.arguments)) {
2102
- const terminalId = stringValue(output.arguments.terminal_id ?? output.arguments.terminal);
2103
- output.arguments = terminalId && /^terminal:v[0-9]+:\S+$/u.test(terminalId)
2104
- ? { terminal_id: terminalId }
2105
- : {};
2106
- if (Array.isArray(output.missing_required)) {
2107
- output.missing_required = output.missing_required.filter((field) => field === "model" || field === "reasoning_effort");
2108
- }
2109
- }
2110
- return output;
2111
- }
2112
- function isLegacyAuthorityInstructionPath(path) {
2113
- const normalized = path.map(normalizeAkkModelFacingFieldName);
2114
- if (normalized.at(-1) !== "body")
2115
- return false;
2116
- const callbackMessageBody = normalized.slice(-3).join(".") ===
2117
- "callbackdelivery.message.body";
2118
- return callbackMessageBody || normalized.includes("recentevents");
2119
- }
2120
- function modelFacingToolError(error) {
2121
- if (error instanceof Error && error.name === "AbortError") {
2122
- return error;
2123
- }
2124
- return new Error(modelFacingErrorMessage(error));
2125
- }
2126
- function modelFacingErrorMessage(error) {
2127
- const message = error instanceof Error ? error.message : String(error);
2128
- return sanitizeAkkModelFacingDiagnosticText(message);
2129
- }
2130
- function isBlockedTerminalDispatchResult(result) {
2131
- return Boolean(isRecord(result) &&
2132
- result.source === "terminal_control" &&
2133
- result.terminal_dispatch_resolved !== true);
2134
- }
2135
- function isSubmissionError(result) {
2136
- if (!isRecord(result)) {
2137
- return false;
2138
- }
2139
- if (isSuccessfulTerminalDispatch(result)) {
2140
- return false;
2141
- }
2142
- if (terminalSubmissionReported(result)) {
2143
- return !isAkkNativeSubmissionAccepted(result);
2144
- }
2145
- return [
2146
- "submission_unfenced",
2147
- "submission_uncertain",
2148
- "submission_aborted",
2149
- "submission_pending_acceptance",
2150
- "submission_not_accepted"
2151
- ].includes(String(result.status ?? "")) ||
2152
- ["uncertain", "aborted", "pending_acceptance", "not_accepted"].includes(String(result.submission_outcome ?? "")) ||
2153
- result.status === "delivered_unfenced";
2154
- }
2155
- function terminalSubmissionReported(result) {
2156
- return result.submission_outcome !== undefined ||
2157
- result.delivery_receipt !== undefined ||
2158
- result.delivered !== undefined;
2159
- }
2160
- function withTurnIdentity(result) {
2161
- if (!isRecord(result)) {
2162
- return result;
2163
- }
2164
- const sources = [
2165
- { label: "result", value: result },
2166
- { label: "result.conversation", value: result.conversation },
2167
- { label: "result.summary", value: result.summary },
2168
- { label: "result.message", value: result.message }
2169
- ];
2170
- const compatibilityId = consistentResultIdentity("conversation_id", sources);
2171
- const explicitSessionId = consistentResultIdentity("session_id", sources);
2172
- const explicitTurnId = consistentResultIdentity("turn_id", sources);
2173
- const hasModernIdentity = Boolean(explicitSessionId || explicitTurnId);
2174
- if (hasModernIdentity && (!explicitSessionId || !explicitTurnId)) {
2175
- throw new Error("agent-knock-knock CLI returned a partial session_id/turn_id identity");
2176
- }
2177
- if (hasModernIdentity && !compatibilityId) {
2178
- throw new Error("agent-knock-knock CLI returned modern identity without conversation_id");
2179
- }
2180
- if (hasModernIdentity && compatibilityId !== explicitTurnId) {
2181
- throw new Error("agent-knock-knock CLI returned conversation_id that differs from turn_id");
2182
- }
2183
- if (!hasModernIdentity && !compatibilityId) {
2184
- return result;
2185
- }
2186
- if (!hasModernIdentity &&
2187
- (compatibilityId?.startsWith("terminal:") ||
2188
- stringValue(result.source) === "terminal" ||
2189
- (isRecord(result.summary) &&
2190
- stringValue(result.summary.source) === "terminal"))) {
2191
- return result;
2192
- }
2193
- const sessionId = explicitSessionId ?? compatibilityId;
2194
- const turnId = explicitTurnId ?? compatibilityId;
2195
- return {
2196
- ...result,
2197
- session_id: sessionId,
2198
- turn_id: turnId
2199
- };
2200
- }
2201
- function consistentResultIdentity(field, sources) {
2202
- const values = sources.flatMap(({ label, value }) => {
2203
- if (!isRecord(value) || !Object.hasOwn(value, field)) {
2204
- return [];
2205
- }
2206
- const identity = stringValue(value[field]);
2207
- if (!identity) {
2208
- throw new Error(`agent-knock-knock CLI returned invalid ${label}.${field}`);
2209
- }
2210
- return [{ label, identity }];
2211
- });
2212
- const expected = values[0];
2213
- for (const candidate of values.slice(1)) {
2214
- if (candidate.identity !== expected.identity) {
2215
- throw new Error(`agent-knock-knock CLI returned conflicting ${field} between ${expected.label} and ${candidate.label}`);
2216
- }
2217
- }
2218
- return expected?.identity;
2219
- }
2220
- async function runDelegate(api, params, toolContext) {
2221
- const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
2222
- const request = requiredString(params.request, "request");
2223
- const openclawSession = stringValue(toolContext?.sessionKey) ??
2224
- "agent:main:main";
2225
- const args = [
2226
- "delegate",
2227
- "--request",
2228
- request,
2229
- "--background"
2230
- ];
2231
- pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
2232
- pushOptional(args, "--message-id", stringValue(params.messageId));
2233
- pushOptional(args, "--openclaw-session", openclawSession);
2234
- pushOptional(args, "--gateway-method", CALLBACK_METHOD);
2235
- pushOptional(args, "--gateway-session", openclawSession);
2236
- pushOptional(args, "--openclaw-bin", stringValue(config.openclawBin));
2237
- pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ?? numberString(config.idleTimeoutMinutes));
2238
- pushOptional(args, "--agent-timeout-minutes", numberString(params.agentTimeoutMinutes) ?? numberString(config.agentTimeoutMinutes));
2239
- pushOptional(args, "--agent-hard-timeout-minutes", numberString(params.agentHardTimeoutMinutes) ?? numberString(config.agentHardTimeoutMinutes));
2240
- const parsed = withTurnIdentity(await runCliAsync(api, args));
2241
- if (!isRecord(parsed)) {
2242
- throw new Error("agent-knock-knock delegate returned a non-object result");
2243
- }
2244
- if (parsed.scope === "terminal_user_explicit") {
2245
- // A uniquely delegated user-priority Send may deliberately complete
2246
- // without creating a managed Turn. Preserve that terminal receipt for the
2247
- // shared Send formatter instead of inventing Session/Turn identity.
2248
- return {
2249
- ...parsed,
2250
- ...normalizedTerminalSendResultContract(parsed)
2251
- };
2252
- }
2253
- const conversationId = stringValue(parsed.conversation_id) ??
2254
- (isRecord(parsed.conversation)
2255
- ? stringValue(parsed.conversation.conversation_id)
2256
- : undefined);
2257
- const sessionId = stringValue(parsed.session_id);
2258
- const turnId = stringValue(parsed.turn_id);
2259
- if (!conversationId || !sessionId || !turnId) {
2260
- throw new Error("agent-knock-knock delegate returned incomplete Turn identity");
2261
- }
2262
- const parsedConversation = isRecord(parsed.conversation)
2263
- ? parsed.conversation
2264
- : undefined;
2265
- const parsedPaths = isRecord(parsed.paths) ? parsed.paths : undefined;
2266
- const parsedTerminalControl = isRecord(parsed.terminal_control)
2267
- ? parsed.terminal_control
2268
- : undefined;
2269
- const statePath = parsedConversation?.state_path ??
2270
- parsedPaths?.statePath;
2271
- const logPath = parsedConversation?.event_log_path ??
2272
- parsedPaths?.logPath;
2273
- const submissionUncertain = parsed.submission_outcome === "uncertain";
2274
- const submissionAborted = parsed.submission_outcome === "aborted";
2275
- const submissionAccepted = parsed.submission_outcome === "agent_accepted" &&
2276
- parsed.delivery_receipt === "agent_accepted" &&
2277
- parsed.delivered === true;
2278
- const submissionNotAccepted = parsed.submission_outcome === "not_accepted";
2279
- const submissionPending = !submissionAccepted &&
2280
- !submissionUncertain &&
2281
- !submissionAborted &&
2282
- !submissionNotAccepted;
2283
- const submissionUnfenced = parsed.status === "delivered_unfenced";
2284
- const agent = (isRecord(parsedConversation?.executor)
2285
- ? stringValue(parsedConversation.executor.kind)
2286
- : undefined) ??
2287
- stringValue(parsed.agent);
2288
- return {
2289
- status: submissionUnfenced
2290
- ? "submission_unfenced"
2291
- : submissionUncertain
2292
- ? "submission_uncertain"
2293
- : submissionAborted
2294
- ? "submission_aborted"
2295
- : submissionNotAccepted
2296
- ? "submission_not_accepted"
2297
- : submissionPending
2298
- ? "submission_pending_acceptance"
2299
- : "async_pending",
2300
- submission_status: submissionUnfenced
2301
- ? "submitted_unfenced"
2302
- : submissionUncertain
2303
- ? "uncertain"
2304
- : submissionAborted
2305
- ? "aborted"
2306
- : submissionNotAccepted
2307
- ? "not_accepted"
2308
- : submissionPending
2309
- ? "pending_acceptance"
2310
- : "accepted",
2311
- conversation_id: conversationId,
2312
- session_id: sessionId,
2313
- turn_id: turnId,
2314
- conversation_status: parsedConversation?.status,
2315
- state_path: statePath,
2316
- event_log_path: logPath,
2317
- agent,
2318
- executor: parsedConversation?.executor,
2319
- session: (isRecord(parsedConversation?.executor)
2320
- ? parsedConversation.executor.session
2321
- : undefined) ??
2322
- parsedTerminalControl?.target,
2323
- openclaw_session: openclawSession,
2324
- launched: parsed.launched === true,
2325
- replayed: parsed.replayed === true,
2326
- background: parsed.background === true,
2327
- pid: parsed.pid ?? parsedTerminalControl?.panePid ?? null,
2328
- callback_method: hostBridgePresentationApis.has(api)
2329
- ? "command_json_v1"
2330
- : CALLBACK_METHOD,
2331
- ...normalizedTerminalSendResultContract(parsed),
2332
- ...(submissionUnfenced
2333
- ? {
2334
- submission_outcome: "submitted",
2335
- do_not_retry: true,
2336
- reason: parsed.reason,
2337
- openclaw_next_action: parsed.openclaw_next_action,
2338
- note: "AKK sent the terminal input but could not fence later side effects to an exact native session. Do not retry or continue automatically; inspect the pane and close this Turn."
2339
- }
2340
- : submissionUncertain
2341
- ? {
2342
- submission_outcome: "uncertain",
2343
- do_not_retry: true,
2344
- reason: parsed.reason,
2345
- openclaw_next_action: parsed.openclaw_next_action,
2346
- note: "AKK could not prove whether the terminal accepted Enter. Do not retry automatically; inspect the exact AKK Turn record and shared terminal."
2347
- }
2348
- : submissionAborted
2349
- ? parsed.safe_to_retry === true && parsed.do_not_retry !== true
2350
- ? {
2351
- submission_outcome: "aborted",
2352
- safe_to_retry: true,
2353
- do_not_retry: false,
2354
- reason: parsed.reason,
2355
- openclaw_next_action: parsed.openclaw_next_action,
2356
- note: "AKK stopped before sending terminal input and durably proved a safe abort. The request may be retried."
2357
- }
2358
- : {
2359
- submission_outcome: "aborted",
2360
- safe_to_retry: false,
2361
- do_not_retry: true,
2362
- reason: parsed.reason,
2363
- openclaw_next_action: parsed.openclaw_next_action,
2364
- note: "AKK could not prove a durable safe abort. Do not retry automatically; inspect the exact Turn and terminal dispatch ledger."
2365
- }
2366
- : submissionNotAccepted
2367
- ? {
2368
- submission_outcome: "not_accepted",
2369
- do_not_retry: true,
2370
- reason: parsed.reason,
2371
- openclaw_next_action: parsed.openclaw_next_action,
2372
- note: "AKK proved terminal transport but the exact draft is still present in the agent composer. Do not retry automatically; inspect the shared pane."
2373
- }
2374
- : submissionPending
2375
- ? {
2376
- submission_outcome: "pending_acceptance",
2377
- do_not_retry: true,
2378
- reason: parsed.reason,
2379
- openclaw_next_action: parsed.openclaw_next_action,
2380
- note: "AKK proved only terminal transport and is still waiting for native agent acceptance. Do not retry or report the task as accepted."
2381
- }
2382
- : {
2383
- openclaw_next_action: {
2384
- action: "yield",
2385
- reason: "The coding agent is working in the shared terminal. End this controller turn now and wait for an Agent Knock Knock callback.",
2386
- do_not: "Do not poll terminal internals while waiting. Further communication must use Agent Knock Knock tools so the same shared terminal remains authoritative.",
2387
- expected_callback: "The callback will be injected into this controller session by its configured callback transport."
2388
- },
2389
- note: "The task was sent to the shared terminal. The controller Host should yield now and wait for the callback turn."
2390
- })
2391
- };
2392
- }
2393
- function terminalMessageIdForToolCall({ toolCallId: toolCallIdValue, sessionKey: sessionKeyValue, sessionId: sessionIdValue, toolName }) {
2394
- const toolCallId = stringValue(toolCallIdValue);
2395
- if (!toolCallId) {
2396
- return undefined;
2397
- }
2398
- const sessionKey = stringValue(sessionKeyValue) ?? "agent:main:main";
2399
- // sessionId is the OpenClaw conversation incarnation. It changes across
2400
- // /new and /reset even when sessionKey remains stable. Keep a literal null
2401
- // fallback so retries with the same legacy context remain deterministic.
2402
- const sessionId = stringValue(sessionIdValue) ?? null;
2403
- const digest = createHash("sha256")
2404
- .update(JSON.stringify([sessionKey, sessionId, toolName, toolCallId]))
2405
- .digest("hex");
2406
- return `msg-openclaw-${digest}`;
2407
- }
2408
- function registerCliTool(api, { name, description, parameters, buildArgs, rememberResult = undefined, timeoutMs = undefined, normalizeTurnIdentity = true, modelProjection = undefined, compactText = false, isErrorResult = (_result) => false }) {
2409
- api.registerTool((toolContext) => ({
2410
- label: toolLabel(name),
2411
- name,
2412
- description,
2413
- parameters,
2414
- async execute(toolCallId, params, signal) {
2415
- return withHostBridgeInvocationSignal(signal, async () => {
2416
- try {
2417
- const result = await runHostAwareCli(api, await buildArgs(isRecord(params) ? params : {}, toolContext, toolCallId), timeoutMs === undefined ? {} : { timeoutMs });
2418
- if (typeof rememberResult === "function") {
2419
- rememberResult(result, isRecord(params) ? params : {}, toolContext);
2420
- }
2421
- if (name === "agent_knock_knock_list") {
2422
- rememberDisplayedPrivateAuthorityOffers(api, toolContext?.sessionKey, toolContext?.sessionId, result);
2423
- }
2424
- const rendered = toolResult(result, {
2425
- submissionErrors: name === "agent_knock_knock_respond" ||
2426
- name === "agent_knock_knock_identify_and_send",
2427
- normalizeTurnIdentity,
2428
- modelProjection,
2429
- compactText,
2430
- forceError: typeof isErrorResult === "function" && isErrorResult(result) === true
2431
- });
2432
- return rendered;
2433
- }
2434
- catch (error) {
2435
- throw modelFacingToolError(error);
2436
- }
2437
- });
2438
- }
2439
- }), { name, optional: true });
2440
- }
2441
- function toolLabel(name) {
2442
- const action = String(name)
2443
- .replace(/^agent_knock_knock_/, "")
2444
- .split("_")
2445
- .filter(Boolean)
2446
- .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
2447
- .join(" ");
2448
- return `AKK ${action || "Tool"}`;
2449
- }
2450
- export function runCli(api, cliArgs, { cwd = process.cwd(), allowNonzeroJson = false } = {}) {
2451
- const binPath = relayPathForApi(api);
2452
- const spawned = spawnSync(process.execPath, [binPath, ...cliArgs], {
2453
- encoding: "utf8",
2454
- maxBuffer: 1024 * 1024 * 10,
2455
- cwd,
2456
- env: relayEnvironmentForApi(api)
2457
- });
2458
- if (spawned.error) {
2459
- throw new Error(`agent-knock-knock ${cliArgs[0]} failed to start: ${spawned.error.message}`);
2460
- }
2461
- if (spawned.status !== 0) {
2462
- if (allowNonzeroJson && spawned.stdout.trim()) {
2463
- return parseJson(spawned.stdout);
2464
- }
2465
- throw new Error(cleanError(spawned.stderr || spawned.stdout || `agent-knock-knock ${cliArgs[0]} exited with status ${spawned.status}`));
2466
- }
2467
- return parseJson(spawned.stdout);
2468
- }
2469
- export function runCliAsync(api, cliArgs, { cwd = process.cwd(), allowNonzeroJson = false, timeoutMs = 90_000 } = {}) {
2470
- const binPath = relayPathForApi(api);
2471
- const maxBuffer = 10 * 1024 * 1024;
2472
- const signal = hostBridgeInvocationStorage.getStore()?.signal;
2473
- if (signal?.aborted) {
2474
- return Promise.reject(hostBridgeAbortError());
2475
- }
2476
- return new Promise((resolve, reject) => {
2477
- const child = spawn(process.execPath, [binPath, ...cliArgs], {
2478
- cwd,
2479
- env: relayEnvironmentForApi(api),
2480
- stdio: ["ignore", "pipe", "pipe"],
2481
- windowsHide: true
2482
- });
2483
- child.stdout.setEncoding("utf8");
2484
- child.stderr.setEncoding("utf8");
2485
- let stdout = "";
2486
- let stderr = "";
2487
- let overflow = false;
2488
- let timedOut = false;
2489
- let aborted = false;
2490
- const onAbort = () => {
2491
- aborted = true;
2492
- child.kill("SIGKILL");
2493
- };
2494
- const cleanup = () => {
2495
- clearTimeout(timeout);
2496
- signal?.removeEventListener("abort", onAbort);
2497
- };
2498
- const append = (current, chunk) => {
2499
- const next = current + chunk;
2500
- if (Buffer.byteLength(next, "utf8") > maxBuffer) {
2501
- overflow = true;
2502
- child.kill("SIGKILL");
2503
- }
2504
- return next;
2505
- };
2506
- child.stdout.on("data", (chunk) => {
2507
- stdout = append(stdout, chunk);
2508
- });
2509
- child.stderr.on("data", (chunk) => {
2510
- stderr = append(stderr, chunk);
2511
- });
2512
- const timeout = setTimeout(() => {
2513
- timedOut = true;
2514
- child.kill("SIGKILL");
2515
- }, timeoutMs);
2516
- timeout.unref();
2517
- signal?.addEventListener("abort", onAbort, { once: true });
2518
- if (signal?.aborted) {
2519
- onAbort();
2520
- }
2521
- child.once("error", (error) => {
2522
- cleanup();
2523
- if (aborted) {
2524
- reject(hostBridgeAbortError());
2525
- return;
2526
- }
2527
- reject(new Error(`agent-knock-knock ${cliArgs[0]} failed to start: ${error.message}`));
2528
- });
2529
- child.once("close", (status) => {
2530
- cleanup();
2531
- if (aborted) {
2532
- reject(hostBridgeAbortError());
2533
- return;
2534
- }
2535
- if (timedOut) {
2536
- reject(new Error(`agent-knock-knock ${cliArgs[0]} timed out`));
2537
- return;
2538
- }
2539
- if (overflow) {
2540
- reject(new Error(`agent-knock-knock ${cliArgs[0]} output exceeded 10 MiB`));
2541
- return;
2542
- }
2543
- if (status !== 0) {
2544
- if (allowNonzeroJson && stdout.trim()) {
2545
- try {
2546
- resolve(parseJson(stdout));
2547
- }
2548
- catch (error) {
2549
- reject(error);
2550
- }
2551
- return;
2552
- }
2553
- reject(new Error(cleanError(stderr ||
2554
- stdout ||
2555
- `agent-knock-knock ${cliArgs[0]} exited with status ${status}`)));
2556
- return;
2557
- }
2558
- try {
2559
- resolve(parseJson(stdout));
2560
- }
2561
- catch (error) {
2562
- reject(error);
2563
- }
2564
- });
2565
- });
2566
- }
2567
- function hostBridgeAbortError() {
2568
- const error = new Error("agent-knock-knock Host invocation was aborted");
2569
- error.name = "AbortError";
2570
- return error;
2571
- }
2572
- async function runHostAwareCli(api, cliArgs, options = {}) {
2573
- if (hostBridgeAsyncRelayApis.has(api)) {
2574
- return runCliAsync(api, cliArgs, options);
2575
- }
2576
- return runCli(api, cliArgs, options);
2577
- }
2578
- function relayPathForApi(api) {
2579
- return relayPathByApi.get(api) ?? defaultOpenClawRelayPath;
2580
- }
2581
- function relayEnvironmentForApi(api) {
2582
- return relayEnvironmentByApi.get(api) ?? process.env;
2583
- }
2584
- function pushTurnTarget(args, params) {
2585
- if (Object.hasOwn(params, "turn_id") && Object.hasOwn(params, "conversation_id")) {
2586
- throw new Error("turn-target tools accept only one of turn_id or conversation_id");
2587
- }
2588
- const turnId = stringValue(params.turn_id);
2589
- if (turnId) {
2590
- args.push("--turn", authoritativeManagedId(turnId, "turn_id"));
2591
- return;
2592
- }
2593
- args.push("--conversation", requiredString(params.conversation_id, "turn_id"));
2594
- }
2595
- function authoritativeManagedId(value, name) {
2596
- const id = requiredString(value, name).trim();
2597
- if (/^(?:only|latest|codex|claude|(?:codex|claude):latest)$/iu.test(id) ||
2598
- /^@[0-9a-f]+$/iu.test(id) ||
2599
- /^terminal:/iu.test(id)) {
2600
- throw new Error(`${name} must be an authoritative managed id, not a discovery selector or terminal id`);
2601
- }
2602
- return id;
2603
- }
2604
- function assertExclusiveRecoveryFence(params) {
2605
- const expectedMessageId = stringValue(params.expected_message_id);
2606
- const expectedTransitionId = stringValue(params.expected_transition_id);
2607
- const closeFenceCount = [expectedMessageId, expectedTransitionId]
2608
- .filter(Boolean).length;
2609
- if (closeFenceCount > 1) {
2610
- throw new Error("close accepts only one of expected_message_id or expected_transition_id");
2611
- }
2612
- if (stringValue(params.reason) === "superseded_by_human_context_switch" &&
2613
- (!stringValue(params.turn_id) || stringValue(params.conversation_id))) {
2614
- throw new Error("human-context handoff Close requires the exact managed turn_id");
2615
- }
2616
- }
2617
- export function pushOptional(args, flag, value) {
2618
- if (value !== undefined && value !== "") {
2619
- args.push(flag, value);
2620
- }
2621
- }
2622
- function numberString(value) {
2623
- return typeof value === "number" && Number.isFinite(value) ? String(value) : undefined;
2624
- }
2625
- function truncateText(value, maxLength) {
2626
- const text = String(value ?? "").replace(/\s+/g, " ").trim();
2627
- if (text.length <= maxLength) {
2628
- return text;
2629
- }
2630
- return `${text.slice(0, Math.max(0, maxLength - 1))}...`;
2631
- }
2632
- function requiredString(value, name) {
2633
- if (typeof value !== "string" || !value.trim()) {
2634
- throw new Error(`${name} is required`);
2635
- }
2636
- return value;
2637
- }
2638
- function requiredOpenClawSessionKey(value) {
2639
- return requiredString(value, "Controller session identity for this confirmed action");
2640
- }
2641
- function requiredOpenClawSessionId(value) {
2642
- return requiredString(value, "Controller conversation incarnation for this confirmed action");
2643
- }
2644
- function requiredTerminalInteractionIdentifier(value, name) {
2645
- const identifier = requiredString(value, name);
2646
- if (identifier.length > TERMINAL_INTERACTION_LIMITS.maxIdentifierLength ||
2647
- !/^[A-Za-z0-9][A-Za-z0-9._:-]*$/u.test(identifier)) {
2648
- throw new Error(`${name} must be an exact safe interaction identifier`);
2649
- }
2650
- return identifier;
2651
- }
2652
- function parseJson(text) {
2653
- try {
2654
- return JSON.parse(text);
2655
- }
2656
- catch (error) {
2657
- throw new Error(`agent-knock-knock CLI returned invalid JSON: ${error.message}`);
2658
- }
2659
- }
2660
- function cleanError(text) {
2661
- return String(text).trim().slice(0, 2000);
8
+ registerSemanticToolCatalog(api, createAkkSemanticToolCatalog(api, displayedResumeSnapshots));
2662
9
  }
2663
10
  //# sourceMappingURL=openclaw-plugin-command-adapter.js.map