@scotthuang/agent-knock-knock 0.12.11 → 0.12.12

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 (322) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +5 -5
  3. package/dist/src/callback-cli-adapter.d.ts +47 -0
  4. package/dist/src/callback-cli-adapter.js +156 -0
  5. package/dist/src/callback-cli-adapter.js.map +1 -0
  6. package/dist/src/callback-outbox-policy.d.ts +95 -0
  7. package/dist/src/callback-outbox-policy.js +149 -0
  8. package/dist/src/callback-outbox-policy.js.map +1 -0
  9. package/dist/src/callback-outbox-service.d.ts +247 -0
  10. package/dist/src/callback-outbox-service.js +1053 -0
  11. package/dist/src/callback-outbox-service.js.map +1 -0
  12. package/dist/src/callback-outbox-settlement.d.ts +54 -0
  13. package/dist/src/callback-outbox-settlement.js +241 -0
  14. package/dist/src/callback-outbox-settlement.js.map +1 -0
  15. package/dist/src/canonical-json.d.ts +1 -0
  16. package/dist/src/canonical-json.js +14 -0
  17. package/dist/src/canonical-json.js.map +1 -0
  18. package/dist/src/claude-lifecycle-compatibility.js +15 -0
  19. package/dist/src/claude-lifecycle-compatibility.js.map +1 -1
  20. package/dist/src/claude-local-transcript-provider.d.ts +1 -1
  21. package/dist/src/claude-local-transcript-provider.js +19 -5
  22. package/dist/src/claude-local-transcript-provider.js.map +1 -1
  23. package/dist/src/cli-command-runtime.d.ts +14 -0
  24. package/dist/src/cli-command-runtime.js +164 -0
  25. package/dist/src/cli-command-runtime.js.map +1 -0
  26. package/dist/src/cli-core.d.ts +4 -42
  27. package/dist/src/cli-core.js +906 -28717
  28. package/dist/src/cli-core.js.map +1 -1
  29. package/dist/src/cli-runtime-context.d.ts +57 -0
  30. package/dist/src/cli-runtime-context.js +118 -0
  31. package/dist/src/cli-runtime-context.js.map +1 -0
  32. package/dist/src/codex-lifecycle-compatibility.js +2 -1
  33. package/dist/src/codex-lifecycle-compatibility.js.map +1 -1
  34. package/dist/src/codex-local-session-provider.d.ts +18 -0
  35. package/dist/src/codex-local-session-provider.js +67 -0
  36. package/dist/src/codex-local-session-provider.js.map +1 -1
  37. package/dist/src/codex-session-provider.js +1 -3
  38. package/dist/src/codex-session-provider.js.map +1 -1
  39. package/dist/src/conversation-trace.d.ts +30 -0
  40. package/dist/src/conversation-trace.js +185 -0
  41. package/dist/src/conversation-trace.js.map +1 -0
  42. package/dist/src/deferred-foreground-application-service.d.ts +84 -0
  43. package/dist/src/deferred-foreground-application-service.js +742 -0
  44. package/dist/src/deferred-foreground-application-service.js.map +1 -0
  45. package/dist/src/deferred-foreground-authority-cli-adapter.d.ts +78 -0
  46. package/dist/src/deferred-foreground-authority-cli-adapter.js +724 -0
  47. package/dist/src/deferred-foreground-authority-cli-adapter.js.map +1 -0
  48. package/dist/src/deferred-foreground-boundary.d.ts +61 -0
  49. package/dist/src/deferred-foreground-boundary.js +2 -0
  50. package/dist/src/deferred-foreground-boundary.js.map +1 -0
  51. package/dist/src/deferred-foreground-capability.d.ts +4 -0
  52. package/dist/src/deferred-foreground-capability.js +184 -0
  53. package/dist/src/deferred-foreground-capability.js.map +1 -0
  54. package/dist/src/deferred-foreground-preparation-cli-adapter.d.ts +53 -0
  55. package/dist/src/deferred-foreground-preparation-cli-adapter.js +210 -0
  56. package/dist/src/deferred-foreground-preparation-cli-adapter.js.map +1 -0
  57. package/dist/src/deferred-foreground-preparation-service.d.ts +72 -0
  58. package/dist/src/deferred-foreground-preparation-service.js +179 -0
  59. package/dist/src/deferred-foreground-preparation-service.js.map +1 -0
  60. package/dist/src/deferred-foreground-recovery-cli-adapter.d.ts +139 -0
  61. package/dist/src/deferred-foreground-recovery-cli-adapter.js +1125 -0
  62. package/dist/src/deferred-foreground-recovery-cli-adapter.js.map +1 -0
  63. package/dist/src/deferred-foreground-recovery-service.d.ts +48 -0
  64. package/dist/src/deferred-foreground-recovery-service.js +91 -0
  65. package/dist/src/deferred-foreground-recovery-service.js.map +1 -0
  66. package/dist/src/deferred-foreground-transfer-policy.d.ts +2 -0
  67. package/dist/src/deferred-foreground-transfer-policy.js +10 -0
  68. package/dist/src/deferred-foreground-transfer-policy.js.map +1 -0
  69. package/dist/src/deferred-foreground-transfer.js +43 -101
  70. package/dist/src/deferred-foreground-transfer.js.map +1 -1
  71. package/dist/src/doctor-capabilities.d.ts +9 -0
  72. package/dist/src/doctor-capabilities.js +22 -0
  73. package/dist/src/doctor-capabilities.js.map +1 -1
  74. package/dist/src/durable-json-file.d.ts +22 -0
  75. package/dist/src/durable-json-file.js +127 -0
  76. package/dist/src/durable-json-file.js.map +1 -0
  77. package/dist/src/file-lock-cli-adapter.d.ts +43 -0
  78. package/dist/src/file-lock-cli-adapter.js +150 -0
  79. package/dist/src/file-lock-cli-adapter.js.map +1 -0
  80. package/dist/src/herdr-terminal-control-provider.js +1 -3
  81. package/dist/src/herdr-terminal-control-provider.js.map +1 -1
  82. package/dist/src/install-doctor-command-adapter.d.ts +17 -0
  83. package/dist/src/install-doctor-command-adapter.js +225 -0
  84. package/dist/src/install-doctor-command-adapter.js.map +1 -0
  85. package/dist/src/live-lifecycle-evidence.js +76 -46
  86. package/dist/src/live-lifecycle-evidence.js.map +1 -1
  87. package/dist/src/live-lifecycle-smoke.js +74 -50
  88. package/dist/src/live-lifecycle-smoke.js.map +1 -1
  89. package/dist/src/managed-session.d.ts +2 -0
  90. package/dist/src/managed-session.js +24 -0
  91. package/dist/src/managed-session.js.map +1 -1
  92. package/dist/src/managed-turn-recovery-service.d.ts +86 -0
  93. package/dist/src/managed-turn-recovery-service.js +145 -0
  94. package/dist/src/managed-turn-recovery-service.js.map +1 -0
  95. package/dist/src/mutation-transaction.d.ts +48 -0
  96. package/dist/src/mutation-transaction.js +73 -0
  97. package/dist/src/mutation-transaction.js.map +1 -0
  98. package/dist/src/native-thread-lifecycle-cli-adapter.d.ts +154 -0
  99. package/dist/src/native-thread-lifecycle-cli-adapter.js +786 -0
  100. package/dist/src/native-thread-lifecycle-cli-adapter.js.map +1 -0
  101. package/dist/src/native-thread-lifecycle-ledger-cli-adapter.d.ts +25 -0
  102. package/dist/src/native-thread-lifecycle-ledger-cli-adapter.js +50 -0
  103. package/dist/src/native-thread-lifecycle-ledger-cli-adapter.js.map +1 -0
  104. package/dist/src/native-thread-lifecycle-query-service.d.ts +75 -0
  105. package/dist/src/native-thread-lifecycle-query-service.js +297 -0
  106. package/dist/src/native-thread-lifecycle-query-service.js.map +1 -0
  107. package/dist/src/native-thread-lifecycle-recovery-adapter.d.ts +45 -0
  108. package/dist/src/native-thread-lifecycle-recovery-adapter.js +154 -0
  109. package/dist/src/native-thread-lifecycle-recovery-adapter.js.map +1 -0
  110. package/dist/src/native-thread-lifecycle-recovery-service.d.ts +211 -0
  111. package/dist/src/native-thread-lifecycle-recovery-service.js +1093 -0
  112. package/dist/src/native-thread-lifecycle-recovery-service.js.map +1 -0
  113. package/dist/src/native-thread-resume-snapshot-policy.d.ts +73 -0
  114. package/dist/src/native-thread-resume-snapshot-policy.js +218 -0
  115. package/dist/src/native-thread-resume-snapshot-policy.js.map +1 -0
  116. package/dist/src/native-thread-resume-snapshot.d.ts +3 -61
  117. package/dist/src/native-thread-resume-snapshot.js +2 -143
  118. package/dist/src/native-thread-resume-snapshot.js.map +1 -1
  119. package/dist/src/native-thread-transition-application.d.ts +147 -0
  120. package/dist/src/native-thread-transition-application.js +974 -0
  121. package/dist/src/native-thread-transition-application.js.map +1 -0
  122. package/dist/src/native-thread-transition-policy.d.ts +127 -0
  123. package/dist/src/native-thread-transition-policy.js +154 -0
  124. package/dist/src/native-thread-transition-policy.js.map +1 -0
  125. package/dist/src/native-thread-transition-resource-adapter.d.ts +12 -0
  126. package/dist/src/native-thread-transition-resource-adapter.js +44 -0
  127. package/dist/src/native-thread-transition-resource-adapter.js.map +1 -0
  128. package/dist/src/native-thread-transition-settlement-service.d.ts +94 -0
  129. package/dist/src/native-thread-transition-settlement-service.js +134 -0
  130. package/dist/src/native-thread-transition-settlement-service.js.map +1 -0
  131. package/dist/src/native-thread-transition-verification-adapter.d.ts +62 -0
  132. package/dist/src/native-thread-transition-verification-adapter.js +451 -0
  133. package/dist/src/native-thread-transition-verification-adapter.js.map +1 -0
  134. package/dist/src/openclaw-callback-transport.d.ts +64 -0
  135. package/dist/src/openclaw-callback-transport.js +583 -0
  136. package/dist/src/openclaw-callback-transport.js.map +1 -0
  137. package/dist/src/openclaw-plugin-callback-adapter.d.ts +1 -0
  138. package/dist/src/openclaw-plugin-callback-adapter.js +341 -0
  139. package/dist/src/openclaw-plugin-callback-adapter.js.map +1 -0
  140. package/dist/src/openclaw-plugin-command-adapter.d.ts +16 -0
  141. package/dist/src/openclaw-plugin-command-adapter.js +1427 -0
  142. package/dist/src/openclaw-plugin-command-adapter.js.map +1 -0
  143. package/dist/src/openclaw-plugin-helpers.js +48 -38
  144. package/dist/src/openclaw-plugin-helpers.js.map +1 -1
  145. package/dist/src/openclaw-plugin-schemas.d.ts +382 -0
  146. package/dist/src/openclaw-plugin-schemas.js +394 -0
  147. package/dist/src/openclaw-plugin-schemas.js.map +1 -0
  148. package/dist/src/openclaw-plugin-supervisor.d.ts +6 -0
  149. package/dist/src/openclaw-plugin-supervisor.js +85 -0
  150. package/dist/src/openclaw-plugin-supervisor.js.map +1 -0
  151. package/dist/src/openclaw-plugin.js +7 -2212
  152. package/dist/src/openclaw-plugin.js.map +1 -1
  153. package/dist/src/protocol.d.ts +5 -0
  154. package/dist/src/protocol.js +59 -3
  155. package/dist/src/protocol.js.map +1 -1
  156. package/dist/src/session-store.d.ts +1 -0
  157. package/dist/src/session-store.js +84 -106
  158. package/dist/src/session-store.js.map +1 -1
  159. package/dist/src/store.js +2 -1
  160. package/dist/src/store.js.map +1 -1
  161. package/dist/src/terminal-acceptance-application-service.d.ts +65 -0
  162. package/dist/src/terminal-acceptance-application-service.js +63 -0
  163. package/dist/src/terminal-acceptance-application-service.js.map +1 -0
  164. package/dist/src/terminal-acceptance-cli-adapter.d.ts +210 -0
  165. package/dist/src/terminal-acceptance-cli-adapter.js +957 -0
  166. package/dist/src/terminal-acceptance-cli-adapter.js.map +1 -0
  167. package/dist/src/terminal-action-projection.d.ts +291 -0
  168. package/dist/src/terminal-action-projection.js +353 -0
  169. package/dist/src/terminal-action-projection.js.map +1 -0
  170. package/dist/src/terminal-agent-bridge.d.ts +3 -0
  171. package/dist/src/terminal-agent-bridge.js +29 -8
  172. package/dist/src/terminal-agent-bridge.js.map +1 -1
  173. package/dist/src/terminal-authority-policy.d.ts +141 -0
  174. package/dist/src/terminal-authority-policy.js +367 -0
  175. package/dist/src/terminal-authority-policy.js.map +1 -0
  176. package/dist/src/terminal-binding-authority.d.ts +118 -0
  177. package/dist/src/terminal-binding-authority.js +245 -0
  178. package/dist/src/terminal-binding-authority.js.map +1 -0
  179. package/dist/src/terminal-binding-reconciliation-service.d.ts +75 -0
  180. package/dist/src/terminal-binding-reconciliation-service.js +73 -0
  181. package/dist/src/terminal-binding-reconciliation-service.js.map +1 -0
  182. package/dist/src/terminal-command-cli-adapter.d.ts +375 -0
  183. package/dist/src/terminal-command-cli-adapter.js +3128 -0
  184. package/dist/src/terminal-command-cli-adapter.js.map +1 -0
  185. package/dist/src/terminal-control-ref.d.ts +4 -0
  186. package/dist/src/terminal-control-ref.js +11 -8
  187. package/dist/src/terminal-control-ref.js.map +1 -1
  188. package/dist/src/terminal-delegate-cli-adapter.d.ts +41 -0
  189. package/dist/src/terminal-delegate-cli-adapter.js +242 -0
  190. package/dist/src/terminal-delegate-cli-adapter.js.map +1 -0
  191. package/dist/src/terminal-dispatch-abort.d.ts +22 -0
  192. package/dist/src/terminal-dispatch-abort.js +30 -0
  193. package/dist/src/terminal-dispatch-abort.js.map +1 -0
  194. package/dist/src/terminal-dispatch-application.d.ts +115 -0
  195. package/dist/src/terminal-dispatch-application.js +478 -0
  196. package/dist/src/terminal-dispatch-application.js.map +1 -0
  197. package/dist/src/terminal-dispatch-capability.d.ts +63 -0
  198. package/dist/src/terminal-dispatch-capability.js +133 -0
  199. package/dist/src/terminal-dispatch-capability.js.map +1 -0
  200. package/dist/src/terminal-dispatch-completion-cli-adapter.d.ts +22 -0
  201. package/dist/src/terminal-dispatch-completion-cli-adapter.js +96 -0
  202. package/dist/src/terminal-dispatch-completion-cli-adapter.js.map +1 -0
  203. package/dist/src/terminal-dispatch-composition.d.ts +79 -0
  204. package/dist/src/terminal-dispatch-composition.js +2 -0
  205. package/dist/src/terminal-dispatch-composition.js.map +1 -0
  206. package/dist/src/terminal-dispatch-execution.d.ts +290 -0
  207. package/dist/src/terminal-dispatch-execution.js +916 -0
  208. package/dist/src/terminal-dispatch-execution.js.map +1 -0
  209. package/dist/src/terminal-dispatch-ledger-codec.d.ts +110 -0
  210. package/dist/src/terminal-dispatch-ledger-codec.js +399 -0
  211. package/dist/src/terminal-dispatch-ledger-codec.js.map +1 -0
  212. package/dist/src/terminal-dispatch-policy.d.ts +93 -0
  213. package/dist/src/terminal-dispatch-policy.js +122 -0
  214. package/dist/src/terminal-dispatch-policy.js.map +1 -0
  215. package/dist/src/terminal-dispatch-presenter.d.ts +65 -0
  216. package/dist/src/terminal-dispatch-presenter.js +258 -0
  217. package/dist/src/terminal-dispatch-presenter.js.map +1 -0
  218. package/dist/src/terminal-dispatch-receipt.d.ts +79 -0
  219. package/dist/src/terminal-dispatch-receipt.js +314 -0
  220. package/dist/src/terminal-dispatch-receipt.js.map +1 -0
  221. package/dist/src/terminal-dispatch-recovery-cli-adapter.d.ts +132 -0
  222. package/dist/src/terminal-dispatch-recovery-cli-adapter.js +1330 -0
  223. package/dist/src/terminal-dispatch-recovery-cli-adapter.js.map +1 -0
  224. package/dist/src/terminal-dispatch-recovery-service.d.ts +271 -0
  225. package/dist/src/terminal-dispatch-recovery-service.js +374 -0
  226. package/dist/src/terminal-dispatch-recovery-service.js.map +1 -0
  227. package/dist/src/terminal-dispatch-repository-cli-adapter.d.ts +35 -0
  228. package/dist/src/terminal-dispatch-repository-cli-adapter.js +257 -0
  229. package/dist/src/terminal-dispatch-repository-cli-adapter.js.map +1 -0
  230. package/dist/src/terminal-handoff-application-service.d.ts +7 -0
  231. package/dist/src/terminal-handoff-application-service.js +18 -0
  232. package/dist/src/terminal-handoff-application-service.js.map +1 -0
  233. package/dist/src/terminal-handoff-cli-adapter.d.ts +229 -0
  234. package/dist/src/terminal-handoff-cli-adapter.js +1389 -0
  235. package/dist/src/terminal-handoff-cli-adapter.js.map +1 -0
  236. package/dist/src/terminal-handoff-facts.d.ts +12 -0
  237. package/dist/src/terminal-handoff-facts.js +16 -0
  238. package/dist/src/terminal-handoff-facts.js.map +1 -0
  239. package/dist/src/terminal-identity-authority-cli-adapter.d.ts +318 -0
  240. package/dist/src/terminal-identity-authority-cli-adapter.js +1072 -0
  241. package/dist/src/terminal-identity-authority-cli-adapter.js.map +1 -0
  242. package/dist/src/terminal-identity-authority-service.d.ts +48 -0
  243. package/dist/src/terminal-identity-authority-service.js +45 -0
  244. package/dist/src/terminal-identity-authority-service.js.map +1 -0
  245. package/dist/src/terminal-list-cli-adapter.d.ts +250 -0
  246. package/dist/src/terminal-list-cli-adapter.js +2226 -0
  247. package/dist/src/terminal-list-cli-adapter.js.map +1 -0
  248. package/dist/src/terminal-list-renderer.d.ts +32 -0
  249. package/dist/src/terminal-list-renderer.js +568 -0
  250. package/dist/src/terminal-list-renderer.js.map +1 -0
  251. package/dist/src/terminal-maintenance-cli-adapter.d.ts +102 -0
  252. package/dist/src/terminal-maintenance-cli-adapter.js +1059 -0
  253. package/dist/src/terminal-maintenance-cli-adapter.js.map +1 -0
  254. package/dist/src/terminal-monitor-application-service.d.ts +275 -0
  255. package/dist/src/terminal-monitor-application-service.js +982 -0
  256. package/dist/src/terminal-monitor-application-service.js.map +1 -0
  257. package/dist/src/terminal-monitor-cli-adapter.d.ts +130 -0
  258. package/dist/src/terminal-monitor-cli-adapter.js +442 -0
  259. package/dist/src/terminal-monitor-cli-adapter.js.map +1 -0
  260. package/dist/src/terminal-monitor-decision-policy.d.ts +98 -0
  261. package/dist/src/terminal-monitor-decision-policy.js +246 -0
  262. package/dist/src/terminal-monitor-decision-policy.js.map +1 -0
  263. package/dist/src/terminal-monitor-launch-plan.d.ts +45 -0
  264. package/dist/src/terminal-monitor-launch-plan.js +94 -0
  265. package/dist/src/terminal-monitor-launch-plan.js.map +1 -0
  266. package/dist/src/terminal-monitor-ownership-policy.d.ts +31 -0
  267. package/dist/src/terminal-monitor-ownership-policy.js +64 -0
  268. package/dist/src/terminal-monitor-ownership-policy.js.map +1 -0
  269. package/dist/src/terminal-monitor-poll-policy.d.ts +48 -0
  270. package/dist/src/terminal-monitor-poll-policy.js +67 -0
  271. package/dist/src/terminal-monitor-poll-policy.js.map +1 -0
  272. package/dist/src/terminal-monitor-reconciliation-eligibility.d.ts +53 -0
  273. package/dist/src/terminal-monitor-reconciliation-eligibility.js +114 -0
  274. package/dist/src/terminal-monitor-reconciliation-eligibility.js.map +1 -0
  275. package/dist/src/terminal-monitor-state-cli-adapter.d.ts +106 -0
  276. package/dist/src/terminal-monitor-state-cli-adapter.js +1501 -0
  277. package/dist/src/terminal-monitor-state-cli-adapter.js.map +1 -0
  278. package/dist/src/terminal-monitor-state-reconciliation-service.d.ts +87 -0
  279. package/dist/src/terminal-monitor-state-reconciliation-service.js +106 -0
  280. package/dist/src/terminal-monitor-state-reconciliation-service.js.map +1 -0
  281. package/dist/src/terminal-monitor-supervision-cli-adapter.d.ts +122 -0
  282. package/dist/src/terminal-monitor-supervision-cli-adapter.js +685 -0
  283. package/dist/src/terminal-monitor-supervision-cli-adapter.js.map +1 -0
  284. package/dist/src/terminal-process-facts.d.ts +2 -0
  285. package/dist/src/terminal-process-facts.js +19 -0
  286. package/dist/src/terminal-process-facts.js.map +1 -0
  287. package/dist/src/terminal-process-source.d.ts +6 -4
  288. package/dist/src/terminal-process-source.js +25 -20
  289. package/dist/src/terminal-process-source.js.map +1 -1
  290. package/dist/src/terminal-runtime-cli-adapter.d.ts +80 -0
  291. package/dist/src/terminal-runtime-cli-adapter.js +488 -0
  292. package/dist/src/terminal-runtime-cli-adapter.js.map +1 -0
  293. package/dist/src/terminal-scoped-approval-authority.d.ts +62 -0
  294. package/dist/src/terminal-scoped-approval-authority.js +281 -0
  295. package/dist/src/terminal-scoped-approval-authority.js.map +1 -0
  296. package/dist/src/terminal-selector-projection-service.d.ts +26 -0
  297. package/dist/src/terminal-selector-projection-service.js +81 -0
  298. package/dist/src/terminal-selector-projection-service.js.map +1 -0
  299. package/dist/src/terminal-status-cli-adapter.d.ts +116 -0
  300. package/dist/src/terminal-status-cli-adapter.js +495 -0
  301. package/dist/src/terminal-status-cli-adapter.js.map +1 -0
  302. package/dist/src/terminal-status-facts.d.ts +56 -0
  303. package/dist/src/terminal-status-facts.js +199 -0
  304. package/dist/src/terminal-status-facts.js.map +1 -0
  305. package/dist/src/terminal-submission-acceptance.d.ts +6 -148
  306. package/dist/src/terminal-submission-acceptance.js +2 -229
  307. package/dist/src/terminal-submission-acceptance.js.map +1 -1
  308. package/dist/src/terminal-submission-facts.d.ts +123 -0
  309. package/dist/src/terminal-submission-facts.js +205 -0
  310. package/dist/src/terminal-submission-facts.js.map +1 -0
  311. package/dist/src/terminal-turn-binding-authority-cli-adapter.d.ts +14 -0
  312. package/dist/src/terminal-turn-binding-authority-cli-adapter.js +90 -0
  313. package/dist/src/terminal-turn-binding-authority-cli-adapter.js.map +1 -0
  314. package/dist/src/value-guards.d.ts +4 -0
  315. package/dist/src/value-guards.js +12 -0
  316. package/dist/src/value-guards.js.map +1 -0
  317. package/dist/src/verified-dead-agent-policy.d.ts +192 -0
  318. package/dist/src/verified-dead-agent-policy.js +262 -0
  319. package/dist/src/verified-dead-agent-policy.js.map +1 -0
  320. package/docs/quickstart-tmux.md +1 -1
  321. package/package.json +3 -1
  322. package/templates/openclaw-skills/agent-knock-knock/SKILL.md +1 -1
@@ -0,0 +1,1427 @@
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import { fileURLToPath } from "node:url";
4
+ import { executorDefinitionForKind } from "./executors.js";
5
+ import { isRecord, nonBlankString as stringValue } from "./value-guards.js";
6
+ import { AKK_CALLBACK_METHOD, akkUsageText, buildAkkCommandCliArgs, formatAkkListCommandResult, formatAkkRespondCommandResult, formatAkkThreadsCommandResult, formatAkkThreadTransitionCommandResult, isAkkNativeSubmissionAccepted, isAkkThreadTransitionSuccess, parseAkkCommand, resolvePluginStoreDir } from "./openclaw-plugin-helpers.js";
7
+ import { approveParameters, cancelParameters, closeParameters, listParameters, listResumableThreadsParameters, nativeInspectParameters, newThreadParameters, reconcileBindingParameters, renewParameters, respondParameters, resumeThreadParameters, retryCallbackParameters, sendParameters, statusParameters } from "./openclaw-plugin-schemas.js";
8
+ const MAX_DISPLAYED_RESUME_SNAPSHOTS = 512;
9
+ const CALLBACK_METHOD = AKK_CALLBACK_METHOD;
10
+ export const defaultOpenClawRelayPath = fileURLToPath(new URL("./cli.js", import.meta.url));
11
+ const relayPathByApi = new WeakMap();
12
+ export function bindOpenClawRelayPath(api, relayPath) {
13
+ relayPathByApi.set(api, relayPath);
14
+ }
15
+ export function registerOpenClawCommands(api, displayedResumeSnapshots) {
16
+ api.registerCommand?.({
17
+ name: "akk",
18
+ description: "Send coding work through existing Codex or Claude Code shared terminals, inspect managed turns, and explicitly start, clear, list, or resume native threads.",
19
+ acceptsArgs: true,
20
+ requireAuth: true,
21
+ nativeProgressMessages: {
22
+ default: "AKK is handling the request..."
23
+ },
24
+ agentPromptGuidance: [
25
+ "Use /akk <task> when exactly one eligible idle coding-agent terminal pane should receive new work. Use /akk codex: <task>, /akk claude: <task>, or another selector returned by /akk list to target an existing pane. Use session_id only when the current list action prefills it. A rollout-backed Codex pane instead uses terminal_follow_current: an exact list-prefilled terminal selector plus expected_terminal_token, or an explicitly named/uniquely selected pane whose equivalent fresh candidate authority AKK derives under its terminal lock. AKK sends the ordinary task once within the complete exact rollout inventory and binds only the uniquely accepting native thread. Never infer or reuse a listed token. Use /akk threads, /akk new-thread or clear-thread, and /akk resume-thread only with an exact full terminal_id returned by /akk list; these switch native context without creating a Turn. Resume numbers and short IDs are bound to the last displayed snapshot, while previous is available only from the latest verified committed transition. For native Codex or Claude status, use only an advertised agent_knock_knock_native_inspect action; agent_knock_knock_status inspects AKK Turn state and does not execute /status. AKK never starts a coding-agent process."
26
+ ],
27
+ handler: async (ctx) => handleAkkCommand(api, ctx, displayedResumeSnapshots)
28
+ });
29
+ registerCliTool(api, {
30
+ name: "agent_knock_knock_list",
31
+ description: "List existing Codex and Claude Code tmux or Herdr panes as the primary terminals[] resources. Each terminal may include managed.current_turn or managed.recent_turn; all=true also includes older managed.history and retained unavailable history. By default, unavailable_managed_turns contains attention-needed records whose pane is unavailable. A live human thread switch remains management_state=conflict and exposes handoff_state as adoptable or blocked without mutating the Store. Use only each row's available_actions and authoritative prefilled arguments, except for two explicit-confirmation nested recovery paths: handoff_decision.choices.take_over_current.action and blocking_turns[].recovery_action. The latter is only for collateral blockers; an active handoff source Turn remains exclusively snapshot-fenced by handoff_decision. Refresh list immediately after either nested action. A session_exact session-scoped send targets session_id only when that action is listed and never redirects to the pane's replacement context. A rollout-backed Codex row instead advertises terminal_follow_current as a terminal-scoped follow-current send with its exact selector and expected_terminal_token; AKK pins the complete exact rollout inventory, sends the task once, and binds only the unique exact acceptor. Explicit discovery selectors remain compatible and derive equivalent fresh candidate authority under the terminal lock. respond targets the exact in-flight turn; native_inspect runs only a closed, exact-version Codex or Claude status profile with the current terminal/binding token; read-only thread listing targets the exact terminal, while new/resume mutations also require the current binding token and create no Turn; reconcile_binding remains a low-level conflict recovery action; managed controls target the exact turn; a raw terminal row may prefill its own compatibility selector for status or recovery controls. Never construct identifiers or tokens. AKK revalidates every terminal side effect and never starts a coding-agent process.",
32
+ parameters: listParameters,
33
+ buildArgs: (params) => {
34
+ const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
35
+ const args = ["list", "--reconcile"];
36
+ pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
37
+ pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ?? numberString(api.pluginConfig?.idleTimeoutMinutes));
38
+ pushOptional(args, "--agent", stringValue(params.agent));
39
+ pushOptional(args, "--status", stringValue(params.status));
40
+ if (params.all === true) {
41
+ args.push("--all");
42
+ }
43
+ if (params.noApprovalScan === true) {
44
+ args.push("--no-approval-scan");
45
+ }
46
+ if (params.terminalDebug === true) {
47
+ args.push("--terminal-debug");
48
+ }
49
+ return args;
50
+ }
51
+ });
52
+ registerCliTool(api, {
53
+ name: "agent_knock_knock_list_resumable_threads",
54
+ description: "List verified native Codex or Claude Code threads for one exact terminal. Call this only from that terminal row's available list_resumable_threads action. The result includes the current native identity, a fresh expected_binding_token, exact candidate UUIDs with opaque candidate_token fingerprints, and an exact previous action only when the latest committed transition has one uniquely resumable source. Resume only a row with resumable=true or that previous action, preserving all full prefilled arguments from the same result. Number/short/handle fields are human display aids, never exact tool arguments. This is read-only for Session/Turn state and creates no AKK Turn.",
55
+ parameters: listResumableThreadsParameters,
56
+ normalizeTurnIdentity: false,
57
+ buildArgs: (params) => {
58
+ const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
59
+ const args = [
60
+ "list-resumable-threads",
61
+ "--terminal",
62
+ requiredString(params.terminal_id, "terminal_id")
63
+ ];
64
+ pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
65
+ pushOptional(args, "--codex-home", stringValue(config.codexHome));
66
+ return args;
67
+ }
68
+ });
69
+ registerCliTool(api, {
70
+ name: "agent_knock_knock_native_inspect",
71
+ description: "Execute one closed, exact-version native status inspection in an exact Codex or Claude Code terminal without ordinary Turn delivery. Call only from that terminal row's current native_inspect action and preserve its exact terminal_id, inspection=status, and expected_binding_token. Supported profiles are Codex 0.146.0/0.146.1/0.147.0/0.148.0 and Claude Code 2.1.218/2.1.226/2.1.237; Claude's exact Status panel is parsed and safely dismissed before success. Arbitrary slash commands, /usage, /cost, /stats, /usage-credits, /model, and /compact remain unavailable. Bare Codex /usage opens an interactive menu whose later Enter can select an account-side usage-limit reset. This creates no AKK Session, Turn, receipt, monitor, or callback. agent_knock_knock_status is different: it inspects AKK Turn state and the bounded current screen without executing native /status.",
72
+ parameters: nativeInspectParameters,
73
+ normalizeTurnIdentity: false,
74
+ buildArgs: (params) => {
75
+ const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
76
+ const inspection = requiredString(params.inspection, "inspection");
77
+ if (inspection !== "status") {
78
+ throw new Error("inspection must be status");
79
+ }
80
+ const args = [
81
+ "native-inspect",
82
+ "--terminal",
83
+ requiredString(params.terminal_id, "terminal_id"),
84
+ "--inspection",
85
+ inspection,
86
+ "--expected-binding-token",
87
+ requiredString(params.expected_binding_token, "expected_binding_token")
88
+ ];
89
+ pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
90
+ pushOptional(args, "--codex-home", stringValue(config.codexHome));
91
+ return args;
92
+ }
93
+ });
94
+ registerCliTool(api, {
95
+ name: "agent_knock_knock_new_thread",
96
+ description: "Start and verify a clean native coding-agent thread in the same exact terminal. Call only from a current available new_thread action, using its exact terminal_id and expected_binding_token, or immediately after agent_knock_knock_list_resumable_threads using that result's token. Never send /clear as ordinary task text. This lifecycle transition creates a new AKK Session but no Turn; use ordinary send afterward.",
97
+ parameters: newThreadParameters,
98
+ normalizeTurnIdentity: false,
99
+ isErrorResult: (result) => !isAkkThreadTransitionSuccess(result),
100
+ buildArgs: (params) => {
101
+ const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
102
+ const args = [
103
+ "new-thread",
104
+ "--terminal",
105
+ requiredString(params.terminal_id, "terminal_id"),
106
+ "--expected-binding-token",
107
+ requiredString(params.expected_binding_token, "expected_binding_token")
108
+ ];
109
+ pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
110
+ pushOptional(args, "--codex-home", stringValue(config.codexHome));
111
+ return args;
112
+ }
113
+ });
114
+ registerCliTool(api, {
115
+ name: "agent_knock_knock_reconcile_binding",
116
+ description: "Detach one exact conflicting managed Session binding without adopting the live replacement thread. Call only after the user explicitly requests recovery and only from a current terminal row's advertised reconcile_binding action. Preserve its terminal_id, conflicting_session_id, Session revision, binding token, and terminal token exactly. This action sends no coding-agent input and creates no Turn; refresh AKK list afterward.",
117
+ parameters: reconcileBindingParameters,
118
+ normalizeTurnIdentity: false,
119
+ buildArgs: (params) => {
120
+ const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
121
+ const revision = Number(params.expected_session_revision);
122
+ if (!Number.isSafeInteger(revision) || revision < 1) {
123
+ throw new Error("expected_session_revision must be a positive safe integer");
124
+ }
125
+ const args = [
126
+ "reconcile-binding",
127
+ "--terminal",
128
+ requiredString(params.terminal_id, "terminal_id"),
129
+ "--conflicting-session",
130
+ requiredString(params.conflicting_session_id, "conflicting_session_id"),
131
+ "--expected-session-revision",
132
+ String(revision),
133
+ "--expected-binding-token",
134
+ requiredString(params.expected_binding_token, "expected_binding_token"),
135
+ "--expected-terminal-token",
136
+ requiredString(params.expected_terminal_token, "expected_terminal_token")
137
+ ];
138
+ pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
139
+ pushOptional(args, "--codex-home", stringValue(config.codexHome));
140
+ return args;
141
+ }
142
+ });
143
+ registerCliTool(api, {
144
+ name: "agent_knock_knock_resume_thread",
145
+ description: "Resume one exact verified historical native thread in the same terminal. First call agent_knock_knock_list_resumable_threads, then pass its exact terminal_id and expected_binding_token plus one complete native_thread_id whose row says resumable=true and that same row's candidate_token. Never guess, truncate, or reuse IDs or tokens, and never send a resume slash command as ordinary task text. This creates or reactivates an AKK Session but creates no Turn.",
146
+ parameters: resumeThreadParameters,
147
+ normalizeTurnIdentity: false,
148
+ isErrorResult: (result) => !isAkkThreadTransitionSuccess(result),
149
+ buildArgs: (params) => {
150
+ const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
151
+ const args = [
152
+ "resume-thread",
153
+ "--terminal",
154
+ requiredString(params.terminal_id, "terminal_id"),
155
+ "--native-thread",
156
+ requiredString(params.native_thread_id, "native_thread_id"),
157
+ "--expected-binding-token",
158
+ requiredString(params.expected_binding_token, "expected_binding_token"),
159
+ "--candidate-token",
160
+ requiredString(params.candidate_token, "candidate_token")
161
+ ];
162
+ pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
163
+ pushOptional(args, "--codex-home", stringValue(config.codexHome));
164
+ return args;
165
+ }
166
+ });
167
+ api.registerTool((_toolContext) => ({
168
+ label: "AKK Status",
169
+ name: "agent_knock_knock_status",
170
+ description: "Inspect one exact AKK-managed turn by its authoritative turn_id, or use only a raw terminal row's own prefilled compatibility selector. The deprecated conversation_id remains a legacy Turn alias and the list-prefilled raw-terminal input; never construct it. Returns live state, a bounded terminal screen, and available purpose/context with confidence and limitations. AKK never starts a coding agent.",
171
+ parameters: statusParameters,
172
+ async execute(_toolCallId, params) {
173
+ const result = runCli(api, buildStatusCliArgs(api, isRecord(params) ? params : {}));
174
+ return toolResult(result);
175
+ }
176
+ }), { name: "agent_knock_knock_status", optional: true });
177
+ api.registerTool((toolContext) => ({
178
+ label: "AKK Send",
179
+ name: "agent_knock_knock_send",
180
+ description: "Start a new AKK Turn. A session_exact session-scoped send uses session_id only when the current list action prefills it; it preserves that Session's native context and never redirects to a thread the human selected later in the pane. Rollout-backed Codex uses terminal_follow_current terminal-scoped follow-current instead: preserve the exact list-prefilled full terminal selector and fresh expected_terminal_token, or preserve a selector explicitly named by the user. AKK pins the complete exact rollout inventory under the terminal lock, sends the request once, and binds only the unique exact rollout that accepts it. Never infer, copy, or reuse a listed token. Both target fields may be omitted only when AKK should require one unique eligible idle pane and derive the same fresh candidate authority. Never pass a turn id or selector as session_id. To answer an in-flight question, use agent_knock_knock_respond instead. For ordinary use omit monitoring timeouts unless the user explicitly asks to change them. timeoutSeconds is unsupported. AKK never starts a coding agent. This is asynchronous: after acceptance, yield and wait for the callback or a later explicit status request.",
181
+ parameters: sendParameters,
182
+ async execute(toolCallId, params) {
183
+ const result = await runSendRequest(api, isRecord(params) ? params : {}, toolContext, terminalMessageIdForToolCall({
184
+ toolCallId,
185
+ sessionKey: toolContext?.sessionKey,
186
+ sessionId: toolContext?.sessionId,
187
+ toolName: "agent_knock_knock_send"
188
+ }));
189
+ return toolResult(result, { submissionErrors: true });
190
+ }
191
+ }), { name: "agent_knock_knock_send", optional: true });
192
+ registerCliTool(api, {
193
+ name: "agent_knock_knock_respond",
194
+ 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.",
195
+ parameters: respondParameters,
196
+ buildArgs: (params, toolContext, toolCallId) => {
197
+ const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
198
+ const openclawSession = stringValue(toolContext?.sessionKey) ?? "agent:main:main";
199
+ const args = [
200
+ "respond",
201
+ "--turn",
202
+ authoritativeManagedId(params.turn_id, "turn_id"),
203
+ "--message",
204
+ requiredString(params.request, "request")
205
+ ];
206
+ pushOptional(args, "--message-id", terminalMessageIdForToolCall({
207
+ toolCallId,
208
+ sessionKey: openclawSession,
209
+ sessionId: toolContext?.sessionId,
210
+ toolName: "agent_knock_knock_respond"
211
+ }));
212
+ pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
213
+ pushOptional(args, "--openclaw-session", openclawSession);
214
+ return args;
215
+ }
216
+ });
217
+ registerCliTool(api, {
218
+ name: "agent_knock_knock_approve",
219
+ description: "Manually approve a permission request only after the user reviews and explicitly confirms it. Managed approval uses exact turn_id. An unmanaged raw terminal, or a managed Codex pane whose current prompt has no usable AKK Turn owner, may be approved only through its own latest list-prefilled action; preserve its full conversation_id and expected_terminal_token exactly. Terminal-scoped approval never enters automatic approval, never changes managed Turn or Session binding, and has no durable dispatch receipt: if its result is interrupted, refresh status and inspect the live prompt instead of retrying blindly. Claude Code uses no Hooks: this manual path accepts only an exact one-time Bash permission screen, then recaptures its short-lived evidence and process identity before sending Enter. Separately, trusted default-disabled plugin configuration can auto-approve an exact Claude command/workspace match without exposing policy control to the model. Hook-free durable completion is independently verified from the local Claude transcript.",
220
+ parameters: approveParameters,
221
+ buildArgs: (params) => {
222
+ const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
223
+ const args = ["approve"];
224
+ pushTurnTarget(args, params);
225
+ pushOptional(args, "--expected-approval-fingerprint", requiredString(params.expected_approval_fingerprint, "expected_approval_fingerprint"));
226
+ const expectedTerminalToken = Object.hasOwn(params, "expected_terminal_token")
227
+ ? requiredString(params.expected_terminal_token, "expected_terminal_token")
228
+ : undefined;
229
+ if (expectedTerminalToken &&
230
+ !Object.hasOwn(params, "conversation_id")) {
231
+ throw new Error("expected_terminal_token requires the exact list-prefilled conversation_id");
232
+ }
233
+ pushOptional(args, "--expected-terminal-token", expectedTerminalToken);
234
+ pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
235
+ return args;
236
+ }
237
+ });
238
+ registerCliTool(api, {
239
+ name: "agent_knock_knock_renew",
240
+ 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.",
241
+ parameters: renewParameters,
242
+ buildArgs: (params) => {
243
+ const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
244
+ const args = ["renew"];
245
+ pushTurnTarget(args, params);
246
+ pushOptional(args, "--minutes", numberString(params.minutes) ?? numberString(config.agentTimeoutMinutes));
247
+ pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
248
+ return args;
249
+ }
250
+ });
251
+ registerCliTool(api, {
252
+ name: "agent_knock_knock_retry_callback",
253
+ description: "Retry a persisted AKK callback for an exact turn that failed before reaching OpenClaw. The original callback message id and turn identity are reused for idempotent delivery.",
254
+ parameters: retryCallbackParameters,
255
+ buildArgs: (params) => {
256
+ const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
257
+ const args = ["retry-callback"];
258
+ pushTurnTarget(args, params);
259
+ pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
260
+ return args;
261
+ }
262
+ });
263
+ registerCliTool(api, {
264
+ name: "agent_knock_knock_cancel",
265
+ 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.",
266
+ parameters: cancelParameters,
267
+ buildArgs: (params) => {
268
+ const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
269
+ const args = ["cancel"];
270
+ pushTurnTarget(args, params);
271
+ pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
272
+ pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ?? numberString(config.idleTimeoutMinutes));
273
+ return args;
274
+ }
275
+ });
276
+ registerCliTool(api, {
277
+ name: "agent_knock_knock_close",
278
+ description: "Close an AKK-managed turn record without terminating the shared terminal pane. For a list-prefilled raw-terminal recovery, the user must explicitly request it and provide the exact expected_message_id or expected_transition_id reported by that AKK list entry. When a verified human native-thread switch conflicts with one active Turn, only the complete nested handoff_decision take_over_current action is authoritative: it requires explicit user confirmation and its exact turn_id, supersede reason, and expected_handoff_token. When list exposes blocking_turns[], its complete nested recovery_action is likewise authoritative only after explicit user confirmation; copy it unchanged and refresh list after the Store-only close.",
279
+ parameters: closeParameters,
280
+ isErrorResult: isBlockedTerminalDispatchResult,
281
+ buildArgs: (params) => {
282
+ const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
283
+ const args = ["close"];
284
+ assertExclusiveRecoveryFence(params);
285
+ pushTurnTarget(args, params);
286
+ pushOptional(args, "--reason", stringValue(params.reason));
287
+ pushOptional(args, "--expected-message-id", stringValue(params.expected_message_id));
288
+ pushOptional(args, "--expected-transition-id", stringValue(params.expected_transition_id));
289
+ pushOptional(args, "--expected-handoff-token", stringValue(params.expected_handoff_token));
290
+ pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
291
+ return args;
292
+ }
293
+ });
294
+ }
295
+ function resumeSelectionScope(sessionKey, sessionId) {
296
+ const key = requiredString(sessionKey, "OpenClaw session identity is required for snapshot-bound Resume");
297
+ const incarnation = stringValue(sessionId) ?? null;
298
+ return `openclaw:${createHash("sha256")
299
+ .update(JSON.stringify([key, incarnation]))
300
+ .digest("hex")}`;
301
+ }
302
+ function resumeSnapshotCacheKey(sessionKey, sessionId, terminalId) {
303
+ const key = requiredString(sessionKey, "OpenClaw session identity is required for snapshot-bound Resume");
304
+ return JSON.stringify([key, stringValue(sessionId) ?? null, terminalId]);
305
+ }
306
+ function rememberDisplayedResumeSnapshot(snapshots, key, snapshotId, expiresAtMs) {
307
+ const now = Date.now();
308
+ for (const [candidateKey, value] of snapshots) {
309
+ if (value.expiresAtMs <= now) {
310
+ snapshots.delete(candidateKey);
311
+ }
312
+ }
313
+ snapshots.delete(key);
314
+ while (snapshots.size >= MAX_DISPLAYED_RESUME_SNAPSHOTS) {
315
+ const oldestKey = snapshots.keys().next().value;
316
+ if (typeof oldestKey !== "string") {
317
+ break;
318
+ }
319
+ snapshots.delete(oldestKey);
320
+ }
321
+ snapshots.set(key, { snapshotId, expiresAtMs });
322
+ }
323
+ function currentDisplayedResumeSnapshotId(snapshots, key) {
324
+ const value = snapshots.get(key);
325
+ if (!value) {
326
+ return undefined;
327
+ }
328
+ if (value.expiresAtMs <= Date.now()) {
329
+ snapshots.delete(key);
330
+ return undefined;
331
+ }
332
+ return value.snapshotId;
333
+ }
334
+ async function handleAkkCommand(api, ctx, displayedResumeSnapshots) {
335
+ try {
336
+ const parsed = parseAkkCommand(ctx.args);
337
+ if (parsed.action === "help") {
338
+ return { text: akkUsageText() };
339
+ }
340
+ if (parsed.action === "delegate") {
341
+ const result = await runDelegate(api, {
342
+ request: parsed.request
343
+ }, {
344
+ sessionKey: ctx.sessionKey
345
+ });
346
+ return {
347
+ text: formatDelegateCommandResult(result),
348
+ isError: result.status !== "async_pending"
349
+ };
350
+ }
351
+ const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
352
+ if (parsed.action === "list-resumable-threads" ||
353
+ parsed.action === "new-thread" ||
354
+ parsed.action === "resume-thread") {
355
+ return await handleAkkLifecycleCommand(api, ctx, parsed, config, displayedResumeSnapshots);
356
+ }
357
+ const args = buildAkkCommandCliArgs(parsed, config, {
358
+ sessionKey: ctx.sessionKey
359
+ });
360
+ if (!args) {
361
+ return { text: akkUsageText(), isError: true };
362
+ }
363
+ // Doctor calls back into the running Gateway for its independent health
364
+ // check. Keep the Gateway event loop free while that child CLI runs.
365
+ const result = parsed.action === "doctor"
366
+ ? await runCliAsync(api, args, { allowNonzeroJson: true })
367
+ : runCli(api, args);
368
+ switch (parsed.action) {
369
+ case "doctor":
370
+ return {
371
+ text: formatDoctorCommandResult(result),
372
+ isError: result.ok !== true
373
+ };
374
+ case "list":
375
+ return { text: formatAkkListCommandResult(result) };
376
+ case "status":
377
+ return { text: formatStatusCommandResult(result) };
378
+ case "send":
379
+ return {
380
+ text: formatSendCommandResult(result),
381
+ isError: terminalSubmissionReported(result)
382
+ ? !isAkkNativeSubmissionAccepted(result)
383
+ : result.status === "delivered_unfenced"
384
+ };
385
+ case "respond":
386
+ return formatAkkRespondCommandResult(result);
387
+ case "approve":
388
+ return { text: formatApproveCommandResult(result) };
389
+ case "renew":
390
+ return { text: formatRenewCommandResult(result) };
391
+ case "retry-callback":
392
+ return { text: formatRetryCallbackCommandResult(result) };
393
+ case "cancel":
394
+ return { text: formatCancelCommandResult(result) };
395
+ case "close":
396
+ return {
397
+ text: formatCloseCommandResult(result),
398
+ isError: isBlockedTerminalDispatchResult(result)
399
+ };
400
+ }
401
+ }
402
+ catch (error) {
403
+ const message = error instanceof Error ? error.message : String(error);
404
+ return {
405
+ text: `AKK command failed: ${message}`,
406
+ isError: true
407
+ };
408
+ }
409
+ }
410
+ async function handleAkkLifecycleCommand(api, ctx, parsed, config, displayedResumeSnapshots) {
411
+ const selectionScope = resumeSelectionScope(ctx.sessionKey, ctx.sessionId);
412
+ const snapshotCacheKey = resumeSnapshotCacheKey(ctx.sessionKey, ctx.sessionId, parsed.terminalId);
413
+ if (parsed.action === "resume-thread" &&
414
+ parsed.selection &&
415
+ ["number", "short-id", "snapshot-handle"].includes(parsed.selection.kind)) {
416
+ const mutationArgs = buildAkkCommandCliArgs(parsed, config, {
417
+ sessionKey: ctx.sessionKey,
418
+ selectionScope,
419
+ selectionSnapshotId: parsed.selection.kind === "snapshot-handle"
420
+ ? undefined
421
+ : currentDisplayedResumeSnapshotId(displayedResumeSnapshots, snapshotCacheKey)
422
+ });
423
+ if (!mutationArgs) {
424
+ throw new Error("could not build snapshot-bound resume command");
425
+ }
426
+ const transitionResult = runCli(api, mutationArgs);
427
+ if (isAkkThreadTransitionSuccess(transitionResult)) {
428
+ displayedResumeSnapshots.delete(snapshotCacheKey);
429
+ }
430
+ return {
431
+ text: formatAkkThreadTransitionCommandResult(transitionResult),
432
+ isError: !isAkkThreadTransitionSuccess(transitionResult)
433
+ };
434
+ }
435
+ const discoveryArgs = buildAkkCommandCliArgs({
436
+ action: "list-resumable-threads",
437
+ terminalId: parsed.terminalId
438
+ }, config, { selectionScope });
439
+ if (!discoveryArgs) {
440
+ throw new Error("could not build native-thread discovery command");
441
+ }
442
+ const discovery = runCli(api, discoveryArgs);
443
+ if (parsed.action === "list-resumable-threads" ||
444
+ (parsed.action === "resume-thread" &&
445
+ !parsed.selection)) {
446
+ const snapshotId = isRecord(discovery.selection_snapshot)
447
+ ? stringValue(discovery.selection_snapshot.snapshot_id)
448
+ : undefined;
449
+ const expiresAt = isRecord(discovery.selection_snapshot)
450
+ ? Date.parse(String(discovery.selection_snapshot.expires_at ?? ""))
451
+ : Number.NaN;
452
+ if (snapshotId && Number.isFinite(expiresAt) && expiresAt > Date.now()) {
453
+ rememberDisplayedResumeSnapshot(displayedResumeSnapshots, snapshotCacheKey, snapshotId, expiresAt);
454
+ }
455
+ else {
456
+ displayedResumeSnapshots.delete(snapshotCacheKey);
457
+ }
458
+ return { text: formatAkkThreadsCommandResult(discovery) };
459
+ }
460
+ let expectedBindingToken = requiredString(discovery.expected_binding_token, "expected_binding_token from lifecycle discovery");
461
+ let candidateToken;
462
+ let mutationCommand = parsed;
463
+ if (parsed.action === "resume-thread") {
464
+ let nativeThreadId;
465
+ if (parsed.selection?.kind === "previous") {
466
+ const previous = isRecord(discovery.previous)
467
+ ? discovery.previous
468
+ : undefined;
469
+ const action = isRecord(previous?.available_actions) &&
470
+ isRecord(previous.available_actions.resume_thread)
471
+ ? previous.available_actions.resume_thread
472
+ : undefined;
473
+ const actionArguments = isRecord(action?.arguments)
474
+ ? action.arguments
475
+ : undefined;
476
+ nativeThreadId = requiredString(actionArguments?.native_thread_id, "no verified previous native thread is available; run /akk threads");
477
+ if (stringValue(actionArguments?.terminal_id) !== parsed.terminalId) {
478
+ throw new Error("previous resume action belongs to another terminal");
479
+ }
480
+ expectedBindingToken = requiredString(actionArguments?.expected_binding_token, "expected_binding_token from the previous resume action");
481
+ candidateToken = requiredString(actionArguments?.candidate_token, "candidate_token from the previous resume action");
482
+ }
483
+ else {
484
+ if (parsed.selection?.kind !== "exact") {
485
+ throw new Error("resume selection could not be resolved");
486
+ }
487
+ nativeThreadId = parsed.selection.nativeThreadId;
488
+ const candidate = Array.isArray(discovery.threads)
489
+ ? discovery.threads.find((thread) => isRecord(thread) &&
490
+ stringValue(thread.native_thread_id) === nativeThreadId)
491
+ : undefined;
492
+ if (!isRecord(candidate) || candidate.resumable !== true) {
493
+ throw new Error(`native thread ${nativeThreadId} is not resumable in the current lifecycle snapshot`);
494
+ }
495
+ candidateToken = requiredString(candidate.candidate_token, "candidate_token from the selected resumable thread");
496
+ }
497
+ mutationCommand = {
498
+ action: "resume-thread",
499
+ terminalId: parsed.terminalId,
500
+ selection: { kind: "exact", nativeThreadId }
501
+ };
502
+ }
503
+ const mutationArgs = buildAkkCommandCliArgs(mutationCommand, config, {
504
+ sessionKey: ctx.sessionKey,
505
+ expectedBindingToken,
506
+ candidateToken,
507
+ selectionScope
508
+ });
509
+ if (!mutationArgs) {
510
+ throw new Error("could not build native-thread lifecycle command");
511
+ }
512
+ const transitionResult = runCli(api, mutationArgs);
513
+ if (isAkkThreadTransitionSuccess(transitionResult)) {
514
+ displayedResumeSnapshots.delete(snapshotCacheKey);
515
+ }
516
+ return {
517
+ text: formatAkkThreadTransitionCommandResult(transitionResult),
518
+ isError: !isAkkThreadTransitionSuccess(transitionResult)
519
+ };
520
+ }
521
+ function formatDelegateCommandResult(result) {
522
+ const agent = executorDisplayName(result.agent);
523
+ const { sessionId, turnId } = publicTurnIdentity(result);
524
+ if (result.status === "submission_unfenced") {
525
+ return [
526
+ `AKK sent the terminal input to ${agent}, but could not bind later side effects to an exact native session.`,
527
+ `session: ${sessionId}`,
528
+ `turn: ${turnId}`,
529
+ "next: do not retry or continue automatically; inspect the named pane and close this Turn before sending more work."
530
+ ].join("\n");
531
+ }
532
+ if (result.status === "submission_uncertain") {
533
+ return [
534
+ `AKK could not prove whether ${agent} received the terminal task.`,
535
+ `session: ${sessionId}`,
536
+ `turn: ${turnId}`,
537
+ "next: do not retry automatically; inspect AKK status and the named terminal pane."
538
+ ].join("\n");
539
+ }
540
+ if (result.status === "submission_aborted") {
541
+ const safeToRetry = result.safe_to_retry === true && result.do_not_retry !== true;
542
+ return [
543
+ `AKK stopped before sending the terminal task to ${agent}.`,
544
+ `session: ${sessionId}`,
545
+ `turn: ${turnId}`,
546
+ safeToRetry
547
+ ? "next: no terminal input was sent and the aborted receipt is durable, so this request may be retried."
548
+ : "next: do not retry automatically; AKK could not prove a durable safe abort, so inspect this Turn and its terminal dispatch ledger."
549
+ ].join("\n");
550
+ }
551
+ if (result.status === "submission_pending_acceptance") {
552
+ return [
553
+ `AKK dispatched terminal input to ${agent}, but native acceptance is still pending.`,
554
+ `session: ${sessionId}`,
555
+ `turn: ${turnId}`,
556
+ "next: do not retry or report success; wait for acceptance or inspect the shared terminal pane."
557
+ ].join("\n");
558
+ }
559
+ if (result.status === "submission_not_accepted") {
560
+ return [
561
+ `AKK proved that ${agent} did not accept the terminal draft.`,
562
+ `session: ${sessionId}`,
563
+ `turn: ${turnId}`,
564
+ "next: do not retry automatically; inspect the exact draft in the shared terminal pane."
565
+ ].join("\n");
566
+ }
567
+ return [
568
+ `AKK sent the task to ${agent} in the shared terminal.`,
569
+ `session: ${sessionId}`,
570
+ `turn: ${turnId}`,
571
+ `status: ${result.conversation_status ?? result.status ?? "unknown"}`,
572
+ "The result will return to this OpenClaw session through the callback."
573
+ ].join("\n");
574
+ }
575
+ function executorDisplayName(kind) {
576
+ try {
577
+ return executorDefinitionForKind(String(kind ?? "codex")).displayName;
578
+ }
579
+ catch {
580
+ return String(kind ?? "agent");
581
+ }
582
+ }
583
+ function formatStatusCommandResult(result) {
584
+ const summary = result.summary ?? result.conversation ?? result ?? {};
585
+ const terminalStatus = isRecord(result.terminal_status) ? result.terminal_status : {};
586
+ const rawCallbackDelivery = isRecord(result.conversation?.callback_delivery)
587
+ ? result.conversation.callback_delivery
588
+ : undefined;
589
+ const summarizedCallbackDelivery = isRecord(summary.callback_delivery)
590
+ ? summary.callback_delivery
591
+ : undefined;
592
+ const callbackDelivery = rawCallbackDelivery || summarizedCallbackDelivery
593
+ ? {
594
+ ...(rawCallbackDelivery ?? {}),
595
+ ...(summarizedCallbackDelivery ?? {})
596
+ }
597
+ : undefined;
598
+ const { sessionId, turnId } = publicTurnIdentity(result);
599
+ const lines = [
600
+ "AKK status:",
601
+ `session: ${sessionId}`,
602
+ `turn: ${turnId}`,
603
+ `agent: ${summary.agent ?? summary.executor?.kind ?? terminalStatus.agent ?? "unknown"}`,
604
+ `status: ${summary.status ?? terminalStatus.activity_state ?? "unknown"}`
605
+ ];
606
+ if (callbackDelivery) {
607
+ const callbackParts = [
608
+ String(callbackDelivery.status ?? "unknown"),
609
+ Number.isSafeInteger(Number(callbackDelivery.attempts))
610
+ ? `attempt ${Number(callbackDelivery.attempts)}`
611
+ : undefined,
612
+ callbackDelivery.attempt_state === "in_flight"
613
+ ? "in flight"
614
+ : undefined,
615
+ stringValue(callbackDelivery.next_attempt_at)
616
+ ? `next retry ${stringValue(callbackDelivery.next_attempt_at)}`
617
+ : undefined
618
+ ].filter(Boolean);
619
+ lines.push(`callback: ${callbackParts.join(", ")}`);
620
+ }
621
+ if (summary.request) {
622
+ lines.push(`request: ${truncateText(summary.request, 180)}`);
623
+ }
624
+ if (result.about) {
625
+ lines.push(`about: ${truncateText(result.about, 500)}`);
626
+ }
627
+ if (result.confidence) {
628
+ lines.push(`confidence: ${result.confidence}`);
629
+ }
630
+ const limitations = Array.isArray(result.limitations)
631
+ ? result.limitations.filter(Boolean)
632
+ : [];
633
+ if (limitations.length > 0) {
634
+ lines.push(`limitations: ${limitations.slice(0, 3).join("; ")}`);
635
+ }
636
+ const screen = terminalScreenExcerpt(result);
637
+ if (screen) {
638
+ lines.push(`terminal screen:\n${screen}`);
639
+ }
640
+ return lines.join("\n");
641
+ }
642
+ function formatDoctorCommandResult(result) {
643
+ const capabilities = isRecord(result.capabilities) ? result.capabilities : {};
644
+ const tmux = isRecord(capabilities.tmux) ? capabilities.tmux : {};
645
+ const herdr = isRecord(capabilities.herdr) ? capabilities.herdr : {};
646
+ const dependencyChecks = Array.isArray(result.checks)
647
+ ? result.checks.filter(isRecord)
648
+ : [];
649
+ const codingAgentLines = [
650
+ ["Codex", "codex"],
651
+ ["Claude Code", "claude"]
652
+ ].map(([label, command]) => {
653
+ const check = dependencyChecks.find((entry) => entry.command === command);
654
+ const version = typeof check?.version === "string"
655
+ ? check.version
656
+ : "unavailable";
657
+ const nativeProfile = check?.native_profile_supported === true &&
658
+ typeof check.native_profile === "string"
659
+ ? `native profile ${check.native_profile}`
660
+ : check?.available === true
661
+ ? "native lifecycle/status unverified"
662
+ : "unavailable";
663
+ return `${label}: ${version} (${nativeProfile})`;
664
+ });
665
+ const openclaw = isRecord(result.openclaw) ? result.openclaw : {};
666
+ const checks = Array.isArray(openclaw.checks) ? openclaw.checks : [];
667
+ const failures = checks
668
+ .filter((check) => isRecord(check) && check.ok !== true)
669
+ .map((check) => String(check.name ?? "unknown"));
670
+ const remediation = [...new Set(checks.flatMap((check) => isRecord(check) && Array.isArray(check.remediation)
671
+ ? check.remediation.filter((command) => typeof command === "string" && command.trim().length > 0)
672
+ : []))].slice(0, 3);
673
+ return [
674
+ `AKK doctor: ${result.ok === true ? "ready" : "needs attention"}`,
675
+ `tmux: ${tmux.status ?? "unknown"}`,
676
+ `Herdr: ${herdr.status ?? "unknown"}`,
677
+ ...codingAgentLines,
678
+ `OpenClaw package: ${openclaw.package_ready === true ? "ready" : "not ready"}`,
679
+ `Gateway: ${openclaw.gateway_ready === true ? "healthy" : "unavailable"}`,
680
+ ...(failures.length > 0 ? [`check: ${failures.join(", ")}`] : []),
681
+ ...remediation.map((command) => `next: ${command}`)
682
+ ].join("\n");
683
+ }
684
+ function formatRenewCommandResult(result) {
685
+ const { sessionId, turnId } = publicTurnIdentity(result);
686
+ return [
687
+ "AKK monitoring renewed.",
688
+ `session: ${sessionId}`,
689
+ `turn: ${turnId}`,
690
+ `inactivity timeout: ${result.agent_timeout_minutes ?? "unknown"} minutes`,
691
+ `hard lifetime: ${result.agent_hard_timeout_minutes ?? "unknown"} minutes`,
692
+ "No message or key was sent to the coding agent."
693
+ ].join("\n");
694
+ }
695
+ function formatRetryCallbackCommandResult(result) {
696
+ const { sessionId, turnId } = publicTurnIdentity(result);
697
+ return [
698
+ "AKK callback delivered.",
699
+ `session: ${sessionId}`,
700
+ `turn: ${turnId}`,
701
+ `status: ${result.conversation?.status ?? "unknown"}`,
702
+ `attempts: ${result.conversation?.callback_delivery?.attempts ?? "unknown"}`
703
+ ].join("\n");
704
+ }
705
+ function formatSendCommandResult(result) {
706
+ const conversation = result.conversation ?? {};
707
+ const conversationId = conversation.conversation_id ?? result.conversation_id ?? "unknown";
708
+ const sessionId = conversation.session_id ?? result.session_id ?? conversationId;
709
+ const turnId = conversation.turn_id ?? result.turn_id ?? conversationId;
710
+ const status = conversation.status ?? result.status ?? "unknown";
711
+ const nextAction = isRecord(result.openclaw_next_action) ? result.openclaw_next_action : undefined;
712
+ if (result.status === "delivered_unfenced") {
713
+ return [
714
+ "AKK sent the terminal input but could not bind an exact native session.",
715
+ `session: ${sessionId}`,
716
+ `turn: ${turnId}`,
717
+ `status: ${status}`,
718
+ "next: do not retry or continue automatically; inspect the shared terminal pane and close this Turn before sending more work."
719
+ ].join("\n");
720
+ }
721
+ if (result.submission_outcome === "uncertain") {
722
+ return [
723
+ "AKK terminal submission outcome is uncertain.",
724
+ `session: ${sessionId}`,
725
+ `turn: ${turnId}`,
726
+ `status: ${result.status ?? status}`,
727
+ "next: do not retry automatically; inspect AKK status and the named terminal pane."
728
+ ].join("\n");
729
+ }
730
+ if (result.submission_outcome === "aborted") {
731
+ const safeToRetry = result.safe_to_retry === true && result.do_not_retry !== true;
732
+ return [
733
+ "AKK terminal submission was aborted before terminal input.",
734
+ `session: ${sessionId}`,
735
+ `turn: ${turnId}`,
736
+ `status: ${result.status ?? status}`,
737
+ safeToRetry
738
+ ? "next: this request was not sent, the aborted receipt is durable, and it may be retried."
739
+ : "next: do not retry automatically; inspect the Turn because a durable safe abort was not proven."
740
+ ].join("\n");
741
+ }
742
+ if (result.submission_outcome === "pending_acceptance") {
743
+ return [
744
+ "AKK dispatched the terminal input but native acceptance is still pending.",
745
+ `session: ${sessionId}`,
746
+ `turn: ${turnId}`,
747
+ `status: ${result.status ?? status}`,
748
+ "next: do not retry or report success; wait for acceptance or inspect the shared terminal pane."
749
+ ].join("\n");
750
+ }
751
+ if (result.submission_outcome === "not_accepted") {
752
+ return [
753
+ "AKK proved that the agent did not accept the terminal draft.",
754
+ `session: ${sessionId}`,
755
+ `turn: ${turnId}`,
756
+ `status: ${result.status ?? status}`,
757
+ "next: do not retry automatically; inspect the exact draft in the shared terminal pane."
758
+ ].join("\n");
759
+ }
760
+ if (terminalSubmissionReported(result) &&
761
+ !isAkkNativeSubmissionAccepted(result)) {
762
+ return [
763
+ "AKK could not verify native agent acceptance for this terminal input.",
764
+ `session: ${sessionId}`,
765
+ `turn: ${turnId}`,
766
+ `status: ${result.status ?? status}`,
767
+ "next: do not retry or report success; inspect the exact Turn receipt and shared terminal pane."
768
+ ].join("\n");
769
+ }
770
+ const lines = [
771
+ "AKK turn sent.",
772
+ `session: ${sessionId}`,
773
+ `turn: ${turnId}`,
774
+ `status: ${status}`
775
+ ];
776
+ if (result.source) {
777
+ lines.push(`source: ${result.source}`);
778
+ }
779
+ return [
780
+ ...lines,
781
+ nextAction?.action === "yield"
782
+ ? "next: yield now and wait for the AKK callback or an explicit status request."
783
+ : `launched: ${result.launched === true ? "yes" : "no"}`
784
+ ].join("\n");
785
+ }
786
+ function formatCancelCommandResult(result) {
787
+ const conversation = result.conversation ?? {};
788
+ const { sessionId, turnId } = publicTurnIdentity(result);
789
+ return [
790
+ "AKK cancel requested.",
791
+ `session: ${sessionId}`,
792
+ `turn: ${turnId}`,
793
+ `agent: ${result.executor?.kind ?? conversation.executor?.kind ?? "unknown"}`,
794
+ `status: ${conversation.status ?? (result.cancel_requested === true ? "cancel requested" : "not cancelled")}`
795
+ ].join("\n");
796
+ }
797
+ function formatApproveCommandResult(result) {
798
+ const conversation = result.conversation ?? {};
799
+ const { sessionId, turnId } = publicTurnIdentity(result);
800
+ return [
801
+ result.approved === true
802
+ ? "AKK approved the current terminal request."
803
+ : "AKK did not approve the terminal request.",
804
+ `session: ${sessionId}`,
805
+ `turn: ${turnId}`,
806
+ `status: ${conversation.status ?? "unknown"}`,
807
+ ...(result.reason ? [`reason: ${result.reason}`] : [])
808
+ ].join("\n");
809
+ }
810
+ function formatCloseCommandResult(result) {
811
+ if (result.source === "terminal_control") {
812
+ const terminalControl = isRecord(result.terminal_control)
813
+ ? result.terminal_control
814
+ : {};
815
+ if (result.terminal_dispatch_resolved !== true) {
816
+ return [
817
+ "AKK did not clear the unresolved terminal dispatch fence.",
818
+ `terminal: ${terminalControl.target ?? "unknown"}`,
819
+ ...(stringValue(result.transition_id)
820
+ ? [`transition: ${stringValue(result.transition_id)}`]
821
+ : []),
822
+ `reason: ${stringValue(result.reason) ?? "the recorded lifecycle outcome could not be verified"}`,
823
+ "This terminal remains blocked. Do not retry or continue automatically; inspect the pane and use the fresh recovery action from /akk list."
824
+ ].join("\n");
825
+ }
826
+ return [
827
+ "AKK cleared the unresolved terminal dispatch fence.",
828
+ `terminal: ${terminalControl.target ?? "unknown"}`,
829
+ ...(stringValue(result.transition_id)
830
+ ? [`transition: ${stringValue(result.transition_id)}`]
831
+ : [
832
+ `previous turn: ${result.owner_turn_id ?? result.owner_conversation_id ?? "unknown"}`
833
+ ]),
834
+ "The coding agent and terminal pane remain open."
835
+ ].join("\n");
836
+ }
837
+ const conversation = result.conversation ?? {};
838
+ const { sessionId, turnId } = publicTurnIdentity(result);
839
+ return [
840
+ "AKK Turn record closed.",
841
+ `session: ${sessionId}`,
842
+ `turn: ${turnId}`,
843
+ `status: ${conversation.status ?? "unknown"}`
844
+ ].join("\n");
845
+ }
846
+ function publicTurnIdentity(result) {
847
+ const conversation = isRecord(result.conversation) ? result.conversation : {};
848
+ const summary = isRecord(result.summary) ? result.summary : {};
849
+ const compatibilityId = stringValue(conversation.conversation_id) ??
850
+ stringValue(summary.conversation_id) ??
851
+ stringValue(result.conversation_id);
852
+ return {
853
+ sessionId: stringValue(conversation.session_id) ??
854
+ stringValue(summary.session_id) ??
855
+ stringValue(result.session_id) ??
856
+ compatibilityId ??
857
+ "unknown",
858
+ turnId: stringValue(conversation.turn_id) ??
859
+ stringValue(summary.turn_id) ??
860
+ stringValue(result.turn_id) ??
861
+ compatibilityId ??
862
+ "unknown"
863
+ };
864
+ }
865
+ function buildStatusCliArgs(api, params) {
866
+ const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
867
+ const args = [
868
+ "status",
869
+ "--reconcile"
870
+ ];
871
+ pushTurnTarget(args, params);
872
+ pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
873
+ pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ??
874
+ numberString(api.pluginConfig?.idleTimeoutMinutes));
875
+ if (params.trace === true) {
876
+ args.push("--trace");
877
+ }
878
+ return args;
879
+ }
880
+ function terminalScreenExcerpt(result) {
881
+ const screen = result.terminal_screen;
882
+ const text = typeof screen === "string"
883
+ ? screen
884
+ : isRecord(screen)
885
+ ? stringValue(screen.excerpt) ??
886
+ stringValue(screen.text) ??
887
+ stringValue(screen.content)
888
+ : undefined;
889
+ if (!text) {
890
+ return undefined;
891
+ }
892
+ return text.length <= 1600
893
+ ? text
894
+ : `…${text.slice(-1599)}`;
895
+ }
896
+ async function runSendRequest(api, params, toolContext, messageId) {
897
+ const requestedType = Object.hasOwn(params, "type")
898
+ ? stringValue(params.type)
899
+ : "task";
900
+ if (requestedType !== "task") {
901
+ throw new Error("ordinary send type must be task; use agent_knock_knock_respond for an in-flight response");
902
+ }
903
+ if (Object.hasOwn(params, "session_id") && Object.hasOwn(params, "selector")) {
904
+ throw new Error("ordinary send accepts only one of session_id or selector");
905
+ }
906
+ const sessionId = Object.hasOwn(params, "session_id")
907
+ ? authoritativeManagedId(params.session_id, "session_id")
908
+ : undefined;
909
+ const selector = Object.hasOwn(params, "selector")
910
+ ? requiredString(params.selector, "selector")
911
+ : undefined;
912
+ const expectedTerminalToken = Object.hasOwn(params, "expected_terminal_token")
913
+ ? requiredString(params.expected_terminal_token, "expected_terminal_token")
914
+ : undefined;
915
+ if (expectedTerminalToken && !selector) {
916
+ throw new Error("expected_terminal_token requires a terminal-scoped selector and cannot be used with session_id");
917
+ }
918
+ if (expectedTerminalToken &&
919
+ !/^terminal:v[0-9]+:/u.test(selector ?? "")) {
920
+ throw new Error("expected_terminal_token requires the exact full terminal selector prefilled by AKK list");
921
+ }
922
+ if (!sessionId && !selector) {
923
+ return runDelegate(api, { ...params, messageId }, toolContext);
924
+ }
925
+ const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
926
+ const openclawSession = stringValue(toolContext?.sessionKey) ??
927
+ "agent:main:main";
928
+ const args = [
929
+ "send"
930
+ ];
931
+ if (sessionId) {
932
+ args.push("--session", sessionId);
933
+ }
934
+ else {
935
+ args.push("--conversation", requiredString(selector, "selector"));
936
+ }
937
+ pushOptional(args, "--expected-terminal-token", expectedTerminalToken);
938
+ args.push("--message", requiredString(params.request, "request"), "--background");
939
+ pushOptional(args, "--type", stringValue(params.type));
940
+ pushOptional(args, "--message-id", messageId);
941
+ pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
942
+ pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ??
943
+ numberString(config.idleTimeoutMinutes));
944
+ pushOptional(args, "--agent-timeout-minutes", numberString(params.agentTimeoutMinutes) ??
945
+ numberString(config.agentTimeoutMinutes));
946
+ pushOptional(args, "--agent-hard-timeout-minutes", numberString(params.agentHardTimeoutMinutes) ??
947
+ numberString(config.agentHardTimeoutMinutes));
948
+ pushOptional(args, "--openclaw-session", openclawSession);
949
+ pushOptional(args, "--gateway-method", CALLBACK_METHOD);
950
+ pushOptional(args, "--gateway-session", openclawSession);
951
+ pushOptional(args, "--openclaw-bin", stringValue(config.openclawBin));
952
+ return runCli(api, args);
953
+ }
954
+ function toolResult(result, { submissionErrors = false, normalizeTurnIdentity = true, forceError = false } = {}) {
955
+ const normalized = normalizeTurnIdentity ? withTurnIdentity(result) : result;
956
+ const submissionError = submissionErrors && isSubmissionError(normalized);
957
+ return {
958
+ content: [
959
+ {
960
+ type: "text",
961
+ text: JSON.stringify(normalized, null, 2)
962
+ }
963
+ ],
964
+ details: normalized,
965
+ ...(submissionError || forceError ? { isError: true } : {})
966
+ };
967
+ }
968
+ function isBlockedTerminalDispatchResult(result) {
969
+ return Boolean(isRecord(result) &&
970
+ result.source === "terminal_control" &&
971
+ result.terminal_dispatch_resolved !== true);
972
+ }
973
+ function isSubmissionError(result) {
974
+ if (!isRecord(result)) {
975
+ return false;
976
+ }
977
+ if (terminalSubmissionReported(result)) {
978
+ return !isAkkNativeSubmissionAccepted(result);
979
+ }
980
+ return [
981
+ "submission_unfenced",
982
+ "submission_uncertain",
983
+ "submission_aborted",
984
+ "submission_pending_acceptance",
985
+ "submission_not_accepted"
986
+ ].includes(String(result.status ?? "")) ||
987
+ ["uncertain", "aborted", "pending_acceptance", "not_accepted"].includes(String(result.submission_outcome ?? "")) ||
988
+ result.status === "delivered_unfenced";
989
+ }
990
+ function terminalSubmissionReported(result) {
991
+ return result.submission_outcome !== undefined ||
992
+ result.delivery_receipt !== undefined ||
993
+ result.delivered !== undefined;
994
+ }
995
+ function withTurnIdentity(result) {
996
+ if (!isRecord(result)) {
997
+ return result;
998
+ }
999
+ const sources = [
1000
+ { label: "result", value: result },
1001
+ { label: "result.conversation", value: result.conversation },
1002
+ { label: "result.summary", value: result.summary },
1003
+ { label: "result.message", value: result.message }
1004
+ ];
1005
+ const compatibilityId = consistentResultIdentity("conversation_id", sources);
1006
+ const explicitSessionId = consistentResultIdentity("session_id", sources);
1007
+ const explicitTurnId = consistentResultIdentity("turn_id", sources);
1008
+ const hasModernIdentity = Boolean(explicitSessionId || explicitTurnId);
1009
+ if (hasModernIdentity && (!explicitSessionId || !explicitTurnId)) {
1010
+ throw new Error("agent-knock-knock CLI returned a partial session_id/turn_id identity");
1011
+ }
1012
+ if (hasModernIdentity && !compatibilityId) {
1013
+ throw new Error("agent-knock-knock CLI returned modern identity without conversation_id");
1014
+ }
1015
+ if (hasModernIdentity && compatibilityId !== explicitTurnId) {
1016
+ throw new Error("agent-knock-knock CLI returned conversation_id that differs from turn_id");
1017
+ }
1018
+ if (!hasModernIdentity && !compatibilityId) {
1019
+ return result;
1020
+ }
1021
+ if (!hasModernIdentity &&
1022
+ (compatibilityId?.startsWith("terminal:") ||
1023
+ stringValue(result.source) === "terminal" ||
1024
+ (isRecord(result.summary) &&
1025
+ stringValue(result.summary.source) === "terminal"))) {
1026
+ return result;
1027
+ }
1028
+ const sessionId = explicitSessionId ?? compatibilityId;
1029
+ const turnId = explicitTurnId ?? compatibilityId;
1030
+ return {
1031
+ ...result,
1032
+ session_id: sessionId,
1033
+ turn_id: turnId
1034
+ };
1035
+ }
1036
+ function consistentResultIdentity(field, sources) {
1037
+ const values = sources.flatMap(({ label, value }) => {
1038
+ if (!isRecord(value) || !Object.hasOwn(value, field)) {
1039
+ return [];
1040
+ }
1041
+ const identity = stringValue(value[field]);
1042
+ if (!identity) {
1043
+ throw new Error(`agent-knock-knock CLI returned invalid ${label}.${field}`);
1044
+ }
1045
+ return [{ label, identity }];
1046
+ });
1047
+ const expected = values[0];
1048
+ for (const candidate of values.slice(1)) {
1049
+ if (candidate.identity !== expected.identity) {
1050
+ throw new Error(`agent-knock-knock CLI returned conflicting ${field} between ${expected.label} and ${candidate.label}`);
1051
+ }
1052
+ }
1053
+ return expected?.identity;
1054
+ }
1055
+ async function runDelegate(api, params, toolContext) {
1056
+ const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
1057
+ const request = requiredString(params.request, "request");
1058
+ const openclawSession = stringValue(toolContext?.sessionKey) ??
1059
+ "agent:main:main";
1060
+ const args = [
1061
+ "delegate",
1062
+ "--request",
1063
+ request,
1064
+ "--background"
1065
+ ];
1066
+ pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
1067
+ pushOptional(args, "--message-id", stringValue(params.messageId));
1068
+ pushOptional(args, "--openclaw-session", openclawSession);
1069
+ pushOptional(args, "--gateway-method", CALLBACK_METHOD);
1070
+ pushOptional(args, "--gateway-session", openclawSession);
1071
+ pushOptional(args, "--openclaw-bin", stringValue(config.openclawBin));
1072
+ pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ?? numberString(config.idleTimeoutMinutes));
1073
+ pushOptional(args, "--agent-timeout-minutes", numberString(params.agentTimeoutMinutes) ?? numberString(config.agentTimeoutMinutes));
1074
+ pushOptional(args, "--agent-hard-timeout-minutes", numberString(params.agentHardTimeoutMinutes) ?? numberString(config.agentHardTimeoutMinutes));
1075
+ const parsed = withTurnIdentity(await runCliAsync(api, args));
1076
+ if (!isRecord(parsed)) {
1077
+ throw new Error("agent-knock-knock delegate returned a non-object result");
1078
+ }
1079
+ const conversationId = stringValue(parsed.conversation_id) ??
1080
+ (isRecord(parsed.conversation)
1081
+ ? stringValue(parsed.conversation.conversation_id)
1082
+ : undefined);
1083
+ const sessionId = stringValue(parsed.session_id);
1084
+ const turnId = stringValue(parsed.turn_id);
1085
+ if (!conversationId || !sessionId || !turnId) {
1086
+ throw new Error("agent-knock-knock delegate returned incomplete Turn identity");
1087
+ }
1088
+ const parsedConversation = isRecord(parsed.conversation)
1089
+ ? parsed.conversation
1090
+ : undefined;
1091
+ const parsedPaths = isRecord(parsed.paths) ? parsed.paths : undefined;
1092
+ const parsedTerminalControl = isRecord(parsed.terminal_control)
1093
+ ? parsed.terminal_control
1094
+ : undefined;
1095
+ const statePath = parsedConversation?.state_path ??
1096
+ parsedPaths?.statePath;
1097
+ const logPath = parsedConversation?.event_log_path ??
1098
+ parsedPaths?.logPath;
1099
+ const submissionUncertain = parsed.submission_outcome === "uncertain";
1100
+ const submissionAborted = parsed.submission_outcome === "aborted";
1101
+ const submissionAccepted = parsed.submission_outcome === "agent_accepted" &&
1102
+ parsed.delivery_receipt === "agent_accepted" &&
1103
+ parsed.delivered === true;
1104
+ const submissionNotAccepted = parsed.submission_outcome === "not_accepted";
1105
+ const submissionPending = !submissionAccepted &&
1106
+ !submissionUncertain &&
1107
+ !submissionAborted &&
1108
+ !submissionNotAccepted;
1109
+ const submissionUnfenced = parsed.status === "delivered_unfenced";
1110
+ const agent = (isRecord(parsedConversation?.executor)
1111
+ ? stringValue(parsedConversation.executor.kind)
1112
+ : undefined) ??
1113
+ stringValue(parsed.agent);
1114
+ return {
1115
+ status: submissionUnfenced
1116
+ ? "submission_unfenced"
1117
+ : submissionUncertain
1118
+ ? "submission_uncertain"
1119
+ : submissionAborted
1120
+ ? "submission_aborted"
1121
+ : submissionNotAccepted
1122
+ ? "submission_not_accepted"
1123
+ : submissionPending
1124
+ ? "submission_pending_acceptance"
1125
+ : "async_pending",
1126
+ submission_status: submissionUnfenced
1127
+ ? "submitted_unfenced"
1128
+ : submissionUncertain
1129
+ ? "uncertain"
1130
+ : submissionAborted
1131
+ ? "aborted"
1132
+ : submissionNotAccepted
1133
+ ? "not_accepted"
1134
+ : submissionPending
1135
+ ? "pending_acceptance"
1136
+ : "accepted",
1137
+ conversation_id: conversationId,
1138
+ session_id: sessionId,
1139
+ turn_id: turnId,
1140
+ conversation_status: parsedConversation?.status,
1141
+ state_path: statePath,
1142
+ event_log_path: logPath,
1143
+ agent,
1144
+ executor: parsedConversation?.executor,
1145
+ session: (isRecord(parsedConversation?.executor)
1146
+ ? parsedConversation.executor.session
1147
+ : undefined) ??
1148
+ parsedTerminalControl?.target,
1149
+ openclaw_session: openclawSession,
1150
+ launched: parsed.launched === true,
1151
+ replayed: parsed.replayed === true,
1152
+ background: parsed.background === true,
1153
+ pid: parsed.pid ?? parsedTerminalControl?.panePid ?? null,
1154
+ callback_method: CALLBACK_METHOD,
1155
+ ...(submissionUnfenced
1156
+ ? {
1157
+ submission_outcome: "submitted",
1158
+ do_not_retry: true,
1159
+ reason: parsed.reason,
1160
+ openclaw_next_action: parsed.openclaw_next_action,
1161
+ 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."
1162
+ }
1163
+ : submissionUncertain
1164
+ ? {
1165
+ submission_outcome: "uncertain",
1166
+ do_not_retry: true,
1167
+ reason: parsed.reason,
1168
+ openclaw_next_action: parsed.openclaw_next_action,
1169
+ note: "AKK could not prove whether the terminal accepted Enter. Do not retry automatically; inspect the exact AKK Turn record and shared terminal."
1170
+ }
1171
+ : submissionAborted
1172
+ ? parsed.safe_to_retry === true && parsed.do_not_retry !== true
1173
+ ? {
1174
+ submission_outcome: "aborted",
1175
+ safe_to_retry: true,
1176
+ do_not_retry: false,
1177
+ reason: parsed.reason,
1178
+ openclaw_next_action: parsed.openclaw_next_action,
1179
+ note: "AKK stopped before sending terminal input and durably proved a safe abort. The request may be retried."
1180
+ }
1181
+ : {
1182
+ submission_outcome: "aborted",
1183
+ safe_to_retry: false,
1184
+ do_not_retry: true,
1185
+ reason: parsed.reason,
1186
+ openclaw_next_action: parsed.openclaw_next_action,
1187
+ note: "AKK could not prove a durable safe abort. Do not retry automatically; inspect the exact Turn and terminal dispatch ledger."
1188
+ }
1189
+ : submissionNotAccepted
1190
+ ? {
1191
+ submission_outcome: "not_accepted",
1192
+ do_not_retry: true,
1193
+ reason: parsed.reason,
1194
+ openclaw_next_action: parsed.openclaw_next_action,
1195
+ note: "AKK proved terminal transport but the exact draft is still present in the agent composer. Do not retry automatically; inspect the shared pane."
1196
+ }
1197
+ : submissionPending
1198
+ ? {
1199
+ submission_outcome: "pending_acceptance",
1200
+ do_not_retry: true,
1201
+ reason: parsed.reason,
1202
+ openclaw_next_action: parsed.openclaw_next_action,
1203
+ note: "AKK proved only terminal transport and is still waiting for native agent acceptance. Do not retry or report the task as accepted."
1204
+ }
1205
+ : {
1206
+ openclaw_next_action: {
1207
+ action: "yield",
1208
+ reason: "The coding agent is working in the shared terminal. End this OpenClaw turn now and wait for an Agent Knock Knock callback.",
1209
+ do_not: "Do not poll terminal internals while waiting. Further communication must use Agent Knock Knock tools so the same shared terminal remains authoritative.",
1210
+ expected_callback: "The callback will be injected and scheduled into this OpenClaw session by the agent-knock-knock.callback Gateway method."
1211
+ },
1212
+ note: "The task was sent to the shared terminal. OpenClaw should yield now and wait for the scheduled callback turn."
1213
+ })
1214
+ };
1215
+ }
1216
+ function terminalMessageIdForToolCall({ toolCallId: toolCallIdValue, sessionKey: sessionKeyValue, sessionId: sessionIdValue, toolName }) {
1217
+ const toolCallId = stringValue(toolCallIdValue);
1218
+ if (!toolCallId) {
1219
+ return undefined;
1220
+ }
1221
+ const sessionKey = stringValue(sessionKeyValue) ?? "agent:main:main";
1222
+ // sessionId is the OpenClaw conversation incarnation. It changes across
1223
+ // /new and /reset even when sessionKey remains stable. Keep a literal null
1224
+ // fallback so retries with the same legacy context remain deterministic.
1225
+ const sessionId = stringValue(sessionIdValue) ?? null;
1226
+ const digest = createHash("sha256")
1227
+ .update(JSON.stringify([sessionKey, sessionId, toolName, toolCallId]))
1228
+ .digest("hex");
1229
+ return `msg-openclaw-${digest}`;
1230
+ }
1231
+ function registerCliTool(api, { name, description, parameters, buildArgs, normalizeTurnIdentity = true, isErrorResult = (_result) => false }) {
1232
+ api.registerTool((toolContext) => ({
1233
+ label: toolLabel(name),
1234
+ name,
1235
+ description,
1236
+ parameters,
1237
+ async execute(toolCallId, params) {
1238
+ const result = runCli(api, buildArgs(isRecord(params) ? params : {}, toolContext, toolCallId));
1239
+ return toolResult(result, {
1240
+ submissionErrors: name === "agent_knock_knock_respond",
1241
+ normalizeTurnIdentity,
1242
+ forceError: typeof isErrorResult === "function" && isErrorResult(result) === true
1243
+ });
1244
+ }
1245
+ }), { name, optional: true });
1246
+ }
1247
+ function toolLabel(name) {
1248
+ const action = String(name)
1249
+ .replace(/^agent_knock_knock_/, "")
1250
+ .split("_")
1251
+ .filter(Boolean)
1252
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
1253
+ .join(" ");
1254
+ return `AKK ${action || "Tool"}`;
1255
+ }
1256
+ export function runCli(api, cliArgs, { cwd = process.cwd(), allowNonzeroJson = false } = {}) {
1257
+ const binPath = relayPathForApi(api);
1258
+ const spawned = spawnSync(process.execPath, [binPath, ...cliArgs], {
1259
+ encoding: "utf8",
1260
+ maxBuffer: 1024 * 1024 * 10,
1261
+ cwd
1262
+ });
1263
+ if (spawned.error) {
1264
+ throw new Error(`agent-knock-knock ${cliArgs[0]} failed to start: ${spawned.error.message}`);
1265
+ }
1266
+ if (spawned.status !== 0) {
1267
+ if (allowNonzeroJson && spawned.stdout.trim()) {
1268
+ return parseJson(spawned.stdout);
1269
+ }
1270
+ throw new Error(cleanError(spawned.stderr || spawned.stdout || `agent-knock-knock ${cliArgs[0]} exited with status ${spawned.status}`));
1271
+ }
1272
+ return parseJson(spawned.stdout);
1273
+ }
1274
+ export function runCliAsync(api, cliArgs, { cwd = process.cwd(), allowNonzeroJson = false, timeoutMs = 90_000 } = {}) {
1275
+ const binPath = relayPathForApi(api);
1276
+ const maxBuffer = 10 * 1024 * 1024;
1277
+ return new Promise((resolve, reject) => {
1278
+ const child = spawn(process.execPath, [binPath, ...cliArgs], {
1279
+ cwd,
1280
+ stdio: ["ignore", "pipe", "pipe"],
1281
+ windowsHide: true
1282
+ });
1283
+ child.stdout.setEncoding("utf8");
1284
+ child.stderr.setEncoding("utf8");
1285
+ let stdout = "";
1286
+ let stderr = "";
1287
+ let overflow = false;
1288
+ let timedOut = false;
1289
+ const append = (current, chunk) => {
1290
+ const next = current + chunk;
1291
+ if (Buffer.byteLength(next, "utf8") > maxBuffer) {
1292
+ overflow = true;
1293
+ child.kill("SIGKILL");
1294
+ }
1295
+ return next;
1296
+ };
1297
+ child.stdout.on("data", (chunk) => {
1298
+ stdout = append(stdout, chunk);
1299
+ });
1300
+ child.stderr.on("data", (chunk) => {
1301
+ stderr = append(stderr, chunk);
1302
+ });
1303
+ const timeout = setTimeout(() => {
1304
+ timedOut = true;
1305
+ child.kill("SIGKILL");
1306
+ }, timeoutMs);
1307
+ timeout.unref();
1308
+ child.once("error", (error) => {
1309
+ clearTimeout(timeout);
1310
+ reject(new Error(`agent-knock-knock ${cliArgs[0]} failed to start: ${error.message}`));
1311
+ });
1312
+ child.once("close", (status) => {
1313
+ clearTimeout(timeout);
1314
+ if (timedOut) {
1315
+ reject(new Error(`agent-knock-knock ${cliArgs[0]} timed out`));
1316
+ return;
1317
+ }
1318
+ if (overflow) {
1319
+ reject(new Error(`agent-knock-knock ${cliArgs[0]} output exceeded 10 MiB`));
1320
+ return;
1321
+ }
1322
+ if (status !== 0) {
1323
+ if (allowNonzeroJson && stdout.trim()) {
1324
+ try {
1325
+ resolve(parseJson(stdout));
1326
+ }
1327
+ catch (error) {
1328
+ reject(error);
1329
+ }
1330
+ return;
1331
+ }
1332
+ reject(new Error(cleanError(stderr ||
1333
+ stdout ||
1334
+ `agent-knock-knock ${cliArgs[0]} exited with status ${status}`)));
1335
+ return;
1336
+ }
1337
+ try {
1338
+ resolve(parseJson(stdout));
1339
+ }
1340
+ catch (error) {
1341
+ reject(error);
1342
+ }
1343
+ });
1344
+ });
1345
+ }
1346
+ function relayPathForApi(api) {
1347
+ return relayPathByApi.get(api) ?? defaultOpenClawRelayPath;
1348
+ }
1349
+ function pushTurnTarget(args, params) {
1350
+ if (Object.hasOwn(params, "turn_id") && Object.hasOwn(params, "conversation_id")) {
1351
+ throw new Error("turn-target tools accept only one of turn_id or conversation_id");
1352
+ }
1353
+ const turnId = stringValue(params.turn_id);
1354
+ if (turnId) {
1355
+ args.push("--turn", authoritativeManagedId(turnId, "turn_id"));
1356
+ return;
1357
+ }
1358
+ args.push("--conversation", requiredString(params.conversation_id, "turn_id"));
1359
+ }
1360
+ function authoritativeManagedId(value, name) {
1361
+ const id = requiredString(value, name).trim();
1362
+ if (/^(?:only|latest|codex|claude|(?:codex|claude):latest)$/iu.test(id) ||
1363
+ /^@[0-9a-f]+$/iu.test(id) ||
1364
+ /^terminal:/iu.test(id)) {
1365
+ throw new Error(`${name} must be an authoritative managed id, not a discovery selector or terminal id`);
1366
+ }
1367
+ return id;
1368
+ }
1369
+ function assertExclusiveRecoveryFence(params) {
1370
+ const expectedMessageId = stringValue(params.expected_message_id);
1371
+ const expectedTransitionId = stringValue(params.expected_transition_id);
1372
+ const expectedHandoffToken = stringValue(params.expected_handoff_token);
1373
+ const closeFenceCount = [
1374
+ expectedMessageId,
1375
+ expectedTransitionId,
1376
+ expectedHandoffToken
1377
+ ].filter(Boolean).length;
1378
+ if (closeFenceCount > 1) {
1379
+ throw new Error("close accepts only one of expected_message_id, expected_transition_id, or expected_handoff_token");
1380
+ }
1381
+ const handoffReason = stringValue(params.reason) ===
1382
+ "superseded_by_human_context_switch";
1383
+ if (expectedHandoffToken) {
1384
+ if (!stringValue(params.turn_id) || stringValue(params.conversation_id)) {
1385
+ throw new Error("expected_handoff_token requires the exact managed turn_id from the advertised handoff_decision action");
1386
+ }
1387
+ if (!handoffReason) {
1388
+ throw new Error("expected_handoff_token requires reason=superseded_by_human_context_switch");
1389
+ }
1390
+ }
1391
+ else if (handoffReason) {
1392
+ throw new Error("reason=superseded_by_human_context_switch requires expected_handoff_token from the advertised handoff_decision action");
1393
+ }
1394
+ }
1395
+ export function pushOptional(args, flag, value) {
1396
+ if (value !== undefined && value !== "") {
1397
+ args.push(flag, value);
1398
+ }
1399
+ }
1400
+ function numberString(value) {
1401
+ return typeof value === "number" && Number.isFinite(value) ? String(value) : undefined;
1402
+ }
1403
+ function truncateText(value, maxLength) {
1404
+ const text = String(value ?? "").replace(/\s+/g, " ").trim();
1405
+ if (text.length <= maxLength) {
1406
+ return text;
1407
+ }
1408
+ return `${text.slice(0, Math.max(0, maxLength - 1))}...`;
1409
+ }
1410
+ function requiredString(value, name) {
1411
+ if (typeof value !== "string" || !value.trim()) {
1412
+ throw new Error(`${name} is required`);
1413
+ }
1414
+ return value;
1415
+ }
1416
+ function parseJson(text) {
1417
+ try {
1418
+ return JSON.parse(text);
1419
+ }
1420
+ catch (error) {
1421
+ throw new Error(`agent-knock-knock CLI returned invalid JSON: ${error.message}`);
1422
+ }
1423
+ }
1424
+ function cleanError(text) {
1425
+ return String(text).trim().slice(0, 2000);
1426
+ }
1427
+ //# sourceMappingURL=openclaw-plugin-command-adapter.js.map