@scotthuang/agent-knock-knock 0.12.11 → 0.12.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (344) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +43 -30
  3. package/dist/src/approval-policy.d.ts +11 -2
  4. package/dist/src/approval-policy.js +33 -7
  5. package/dist/src/approval-policy.js.map +1 -1
  6. package/dist/src/callback-cli-adapter.d.ts +47 -0
  7. package/dist/src/callback-cli-adapter.js +156 -0
  8. package/dist/src/callback-cli-adapter.js.map +1 -0
  9. package/dist/src/callback-outbox-policy.d.ts +95 -0
  10. package/dist/src/callback-outbox-policy.js +149 -0
  11. package/dist/src/callback-outbox-policy.js.map +1 -0
  12. package/dist/src/callback-outbox-service.d.ts +247 -0
  13. package/dist/src/callback-outbox-service.js +1053 -0
  14. package/dist/src/callback-outbox-service.js.map +1 -0
  15. package/dist/src/callback-outbox-settlement.d.ts +54 -0
  16. package/dist/src/callback-outbox-settlement.js +241 -0
  17. package/dist/src/callback-outbox-settlement.js.map +1 -0
  18. package/dist/src/canonical-json.d.ts +1 -0
  19. package/dist/src/canonical-json.js +14 -0
  20. package/dist/src/canonical-json.js.map +1 -0
  21. package/dist/src/claude-lifecycle-compatibility.js +15 -0
  22. package/dist/src/claude-lifecycle-compatibility.js.map +1 -1
  23. package/dist/src/claude-local-transcript-provider.d.ts +82 -1
  24. package/dist/src/claude-local-transcript-provider.js +873 -23
  25. package/dist/src/claude-local-transcript-provider.js.map +1 -1
  26. package/dist/src/cli-command-runtime.d.ts +14 -0
  27. package/dist/src/cli-command-runtime.js +164 -0
  28. package/dist/src/cli-command-runtime.js.map +1 -0
  29. package/dist/src/cli-core.d.ts +4 -42
  30. package/dist/src/cli-core.js +958 -28684
  31. package/dist/src/cli-core.js.map +1 -1
  32. package/dist/src/cli-runtime-context.d.ts +57 -0
  33. package/dist/src/cli-runtime-context.js +118 -0
  34. package/dist/src/cli-runtime-context.js.map +1 -0
  35. package/dist/src/codex-lifecycle-compatibility.js +2 -1
  36. package/dist/src/codex-lifecycle-compatibility.js.map +1 -1
  37. package/dist/src/codex-local-session-provider.d.ts +18 -0
  38. package/dist/src/codex-local-session-provider.js +67 -0
  39. package/dist/src/codex-local-session-provider.js.map +1 -1
  40. package/dist/src/codex-session-provider.js +1 -3
  41. package/dist/src/codex-session-provider.js.map +1 -1
  42. package/dist/src/conversation-trace.d.ts +30 -0
  43. package/dist/src/conversation-trace.js +185 -0
  44. package/dist/src/conversation-trace.js.map +1 -0
  45. package/dist/src/deferred-foreground-application-service.d.ts +84 -0
  46. package/dist/src/deferred-foreground-application-service.js +742 -0
  47. package/dist/src/deferred-foreground-application-service.js.map +1 -0
  48. package/dist/src/deferred-foreground-authority-cli-adapter.d.ts +78 -0
  49. package/dist/src/deferred-foreground-authority-cli-adapter.js +795 -0
  50. package/dist/src/deferred-foreground-authority-cli-adapter.js.map +1 -0
  51. package/dist/src/deferred-foreground-boundary.d.ts +61 -0
  52. package/dist/src/deferred-foreground-boundary.js +2 -0
  53. package/dist/src/deferred-foreground-boundary.js.map +1 -0
  54. package/dist/src/deferred-foreground-capability.d.ts +4 -0
  55. package/dist/src/deferred-foreground-capability.js +184 -0
  56. package/dist/src/deferred-foreground-capability.js.map +1 -0
  57. package/dist/src/deferred-foreground-preparation-cli-adapter.d.ts +53 -0
  58. package/dist/src/deferred-foreground-preparation-cli-adapter.js +210 -0
  59. package/dist/src/deferred-foreground-preparation-cli-adapter.js.map +1 -0
  60. package/dist/src/deferred-foreground-preparation-service.d.ts +72 -0
  61. package/dist/src/deferred-foreground-preparation-service.js +179 -0
  62. package/dist/src/deferred-foreground-preparation-service.js.map +1 -0
  63. package/dist/src/deferred-foreground-recovery-cli-adapter.d.ts +139 -0
  64. package/dist/src/deferred-foreground-recovery-cli-adapter.js +1125 -0
  65. package/dist/src/deferred-foreground-recovery-cli-adapter.js.map +1 -0
  66. package/dist/src/deferred-foreground-recovery-service.d.ts +48 -0
  67. package/dist/src/deferred-foreground-recovery-service.js +91 -0
  68. package/dist/src/deferred-foreground-recovery-service.js.map +1 -0
  69. package/dist/src/deferred-foreground-transfer-policy.d.ts +2 -0
  70. package/dist/src/deferred-foreground-transfer-policy.js +10 -0
  71. package/dist/src/deferred-foreground-transfer-policy.js.map +1 -0
  72. package/dist/src/deferred-foreground-transfer.js +46 -105
  73. package/dist/src/deferred-foreground-transfer.js.map +1 -1
  74. package/dist/src/doctor-capabilities.d.ts +9 -0
  75. package/dist/src/doctor-capabilities.js +22 -0
  76. package/dist/src/doctor-capabilities.js.map +1 -1
  77. package/dist/src/durable-json-file.d.ts +22 -0
  78. package/dist/src/durable-json-file.js +127 -0
  79. package/dist/src/durable-json-file.js.map +1 -0
  80. package/dist/src/file-lock-cli-adapter.d.ts +43 -0
  81. package/dist/src/file-lock-cli-adapter.js +150 -0
  82. package/dist/src/file-lock-cli-adapter.js.map +1 -0
  83. package/dist/src/herdr-terminal-control-provider.js +1 -3
  84. package/dist/src/herdr-terminal-control-provider.js.map +1 -1
  85. package/dist/src/install-doctor-command-adapter.d.ts +17 -0
  86. package/dist/src/install-doctor-command-adapter.js +225 -0
  87. package/dist/src/install-doctor-command-adapter.js.map +1 -0
  88. package/dist/src/live-lifecycle-evidence.js +76 -46
  89. package/dist/src/live-lifecycle-evidence.js.map +1 -1
  90. package/dist/src/live-lifecycle-smoke.js +74 -50
  91. package/dist/src/live-lifecycle-smoke.js.map +1 -1
  92. package/dist/src/managed-session.d.ts +2 -0
  93. package/dist/src/managed-session.js +24 -0
  94. package/dist/src/managed-session.js.map +1 -1
  95. package/dist/src/managed-turn-recovery-service.d.ts +86 -0
  96. package/dist/src/managed-turn-recovery-service.js +145 -0
  97. package/dist/src/managed-turn-recovery-service.js.map +1 -0
  98. package/dist/src/mutation-transaction.d.ts +48 -0
  99. package/dist/src/mutation-transaction.js +73 -0
  100. package/dist/src/mutation-transaction.js.map +1 -0
  101. package/dist/src/native-thread-lifecycle-cli-adapter.d.ts +154 -0
  102. package/dist/src/native-thread-lifecycle-cli-adapter.js +786 -0
  103. package/dist/src/native-thread-lifecycle-cli-adapter.js.map +1 -0
  104. package/dist/src/native-thread-lifecycle-ledger-cli-adapter.d.ts +25 -0
  105. package/dist/src/native-thread-lifecycle-ledger-cli-adapter.js +50 -0
  106. package/dist/src/native-thread-lifecycle-ledger-cli-adapter.js.map +1 -0
  107. package/dist/src/native-thread-lifecycle-query-service.d.ts +75 -0
  108. package/dist/src/native-thread-lifecycle-query-service.js +297 -0
  109. package/dist/src/native-thread-lifecycle-query-service.js.map +1 -0
  110. package/dist/src/native-thread-lifecycle-recovery-adapter.d.ts +45 -0
  111. package/dist/src/native-thread-lifecycle-recovery-adapter.js +154 -0
  112. package/dist/src/native-thread-lifecycle-recovery-adapter.js.map +1 -0
  113. package/dist/src/native-thread-lifecycle-recovery-service.d.ts +211 -0
  114. package/dist/src/native-thread-lifecycle-recovery-service.js +1093 -0
  115. package/dist/src/native-thread-lifecycle-recovery-service.js.map +1 -0
  116. package/dist/src/native-thread-resume-snapshot-policy.d.ts +73 -0
  117. package/dist/src/native-thread-resume-snapshot-policy.js +218 -0
  118. package/dist/src/native-thread-resume-snapshot-policy.js.map +1 -0
  119. package/dist/src/native-thread-resume-snapshot.d.ts +3 -61
  120. package/dist/src/native-thread-resume-snapshot.js +2 -143
  121. package/dist/src/native-thread-resume-snapshot.js.map +1 -1
  122. package/dist/src/native-thread-transition-application.d.ts +147 -0
  123. package/dist/src/native-thread-transition-application.js +974 -0
  124. package/dist/src/native-thread-transition-application.js.map +1 -0
  125. package/dist/src/native-thread-transition-policy.d.ts +127 -0
  126. package/dist/src/native-thread-transition-policy.js +154 -0
  127. package/dist/src/native-thread-transition-policy.js.map +1 -0
  128. package/dist/src/native-thread-transition-resource-adapter.d.ts +12 -0
  129. package/dist/src/native-thread-transition-resource-adapter.js +44 -0
  130. package/dist/src/native-thread-transition-resource-adapter.js.map +1 -0
  131. package/dist/src/native-thread-transition-settlement-service.d.ts +94 -0
  132. package/dist/src/native-thread-transition-settlement-service.js +134 -0
  133. package/dist/src/native-thread-transition-settlement-service.js.map +1 -0
  134. package/dist/src/native-thread-transition-verification-adapter.d.ts +62 -0
  135. package/dist/src/native-thread-transition-verification-adapter.js +451 -0
  136. package/dist/src/native-thread-transition-verification-adapter.js.map +1 -0
  137. package/dist/src/openclaw-callback-transport.d.ts +69 -0
  138. package/dist/src/openclaw-callback-transport.js +583 -0
  139. package/dist/src/openclaw-callback-transport.js.map +1 -0
  140. package/dist/src/openclaw-plugin-callback-adapter.d.ts +1 -0
  141. package/dist/src/openclaw-plugin-callback-adapter.js +423 -0
  142. package/dist/src/openclaw-plugin-callback-adapter.js.map +1 -0
  143. package/dist/src/openclaw-plugin-command-adapter.d.ts +16 -0
  144. package/dist/src/openclaw-plugin-command-adapter.js +1868 -0
  145. package/dist/src/openclaw-plugin-command-adapter.js.map +1 -0
  146. package/dist/src/openclaw-plugin-helpers.d.ts +27 -4
  147. package/dist/src/openclaw-plugin-helpers.js +356 -81
  148. package/dist/src/openclaw-plugin-helpers.js.map +1 -1
  149. package/dist/src/openclaw-plugin-schemas.d.ts +358 -0
  150. package/dist/src/openclaw-plugin-schemas.js +366 -0
  151. package/dist/src/openclaw-plugin-schemas.js.map +1 -0
  152. package/dist/src/openclaw-plugin-supervisor.d.ts +6 -0
  153. package/dist/src/openclaw-plugin-supervisor.js +123 -0
  154. package/dist/src/openclaw-plugin-supervisor.js.map +1 -0
  155. package/dist/src/openclaw-plugin.js +7 -2212
  156. package/dist/src/openclaw-plugin.js.map +1 -1
  157. package/dist/src/openclaw-private-authority-offers.d.ts +22 -0
  158. package/dist/src/openclaw-private-authority-offers.js +177 -0
  159. package/dist/src/openclaw-private-authority-offers.js.map +1 -0
  160. package/dist/src/protocol.d.ts +5 -0
  161. package/dist/src/protocol.js +59 -3
  162. package/dist/src/protocol.js.map +1 -1
  163. package/dist/src/session-store.d.ts +1 -0
  164. package/dist/src/session-store.js +84 -106
  165. package/dist/src/session-store.js.map +1 -1
  166. package/dist/src/store.d.ts +1 -0
  167. package/dist/src/store.js +5 -2
  168. package/dist/src/store.js.map +1 -1
  169. package/dist/src/terminal-acceptance-application-service.d.ts +65 -0
  170. package/dist/src/terminal-acceptance-application-service.js +63 -0
  171. package/dist/src/terminal-acceptance-application-service.js.map +1 -0
  172. package/dist/src/terminal-acceptance-cli-adapter.d.ts +210 -0
  173. package/dist/src/terminal-acceptance-cli-adapter.js +957 -0
  174. package/dist/src/terminal-acceptance-cli-adapter.js.map +1 -0
  175. package/dist/src/terminal-action-projection.d.ts +309 -0
  176. package/dist/src/terminal-action-projection.js +376 -0
  177. package/dist/src/terminal-action-projection.js.map +1 -0
  178. package/dist/src/terminal-agent-bridge.d.ts +3 -0
  179. package/dist/src/terminal-agent-bridge.js +29 -8
  180. package/dist/src/terminal-agent-bridge.js.map +1 -1
  181. package/dist/src/terminal-authority-policy.d.ts +141 -0
  182. package/dist/src/terminal-authority-policy.js +367 -0
  183. package/dist/src/terminal-authority-policy.js.map +1 -0
  184. package/dist/src/terminal-binding-authority.d.ts +118 -0
  185. package/dist/src/terminal-binding-authority.js +245 -0
  186. package/dist/src/terminal-binding-authority.js.map +1 -0
  187. package/dist/src/terminal-binding-reconciliation-service.d.ts +75 -0
  188. package/dist/src/terminal-binding-reconciliation-service.js +73 -0
  189. package/dist/src/terminal-binding-reconciliation-service.js.map +1 -0
  190. package/dist/src/terminal-command-cli-adapter.d.ts +380 -0
  191. package/dist/src/terminal-command-cli-adapter.js +3288 -0
  192. package/dist/src/terminal-command-cli-adapter.js.map +1 -0
  193. package/dist/src/terminal-control-ref.d.ts +4 -0
  194. package/dist/src/terminal-control-ref.js +11 -8
  195. package/dist/src/terminal-control-ref.js.map +1 -1
  196. package/dist/src/terminal-delegate-cli-adapter.d.ts +41 -0
  197. package/dist/src/terminal-delegate-cli-adapter.js +242 -0
  198. package/dist/src/terminal-delegate-cli-adapter.js.map +1 -0
  199. package/dist/src/terminal-dispatch-abort.d.ts +22 -0
  200. package/dist/src/terminal-dispatch-abort.js +30 -0
  201. package/dist/src/terminal-dispatch-abort.js.map +1 -0
  202. package/dist/src/terminal-dispatch-application.d.ts +115 -0
  203. package/dist/src/terminal-dispatch-application.js +478 -0
  204. package/dist/src/terminal-dispatch-application.js.map +1 -0
  205. package/dist/src/terminal-dispatch-capability.d.ts +63 -0
  206. package/dist/src/terminal-dispatch-capability.js +133 -0
  207. package/dist/src/terminal-dispatch-capability.js.map +1 -0
  208. package/dist/src/terminal-dispatch-completion-cli-adapter.d.ts +22 -0
  209. package/dist/src/terminal-dispatch-completion-cli-adapter.js +96 -0
  210. package/dist/src/terminal-dispatch-completion-cli-adapter.js.map +1 -0
  211. package/dist/src/terminal-dispatch-composition.d.ts +79 -0
  212. package/dist/src/terminal-dispatch-composition.js +2 -0
  213. package/dist/src/terminal-dispatch-composition.js.map +1 -0
  214. package/dist/src/terminal-dispatch-execution.d.ts +290 -0
  215. package/dist/src/terminal-dispatch-execution.js +916 -0
  216. package/dist/src/terminal-dispatch-execution.js.map +1 -0
  217. package/dist/src/terminal-dispatch-ledger-codec.d.ts +110 -0
  218. package/dist/src/terminal-dispatch-ledger-codec.js +399 -0
  219. package/dist/src/terminal-dispatch-ledger-codec.js.map +1 -0
  220. package/dist/src/terminal-dispatch-policy.d.ts +93 -0
  221. package/dist/src/terminal-dispatch-policy.js +122 -0
  222. package/dist/src/terminal-dispatch-policy.js.map +1 -0
  223. package/dist/src/terminal-dispatch-presenter.d.ts +65 -0
  224. package/dist/src/terminal-dispatch-presenter.js +258 -0
  225. package/dist/src/terminal-dispatch-presenter.js.map +1 -0
  226. package/dist/src/terminal-dispatch-receipt.d.ts +79 -0
  227. package/dist/src/terminal-dispatch-receipt.js +314 -0
  228. package/dist/src/terminal-dispatch-receipt.js.map +1 -0
  229. package/dist/src/terminal-dispatch-recovery-cli-adapter.d.ts +132 -0
  230. package/dist/src/terminal-dispatch-recovery-cli-adapter.js +1330 -0
  231. package/dist/src/terminal-dispatch-recovery-cli-adapter.js.map +1 -0
  232. package/dist/src/terminal-dispatch-recovery-service.d.ts +271 -0
  233. package/dist/src/terminal-dispatch-recovery-service.js +374 -0
  234. package/dist/src/terminal-dispatch-recovery-service.js.map +1 -0
  235. package/dist/src/terminal-dispatch-repository-cli-adapter.d.ts +35 -0
  236. package/dist/src/terminal-dispatch-repository-cli-adapter.js +483 -0
  237. package/dist/src/terminal-dispatch-repository-cli-adapter.js.map +1 -0
  238. package/dist/src/terminal-handoff-application-service.d.ts +7 -0
  239. package/dist/src/terminal-handoff-application-service.js +18 -0
  240. package/dist/src/terminal-handoff-application-service.js.map +1 -0
  241. package/dist/src/terminal-handoff-cli-adapter.d.ts +229 -0
  242. package/dist/src/terminal-handoff-cli-adapter.js +1389 -0
  243. package/dist/src/terminal-handoff-cli-adapter.js.map +1 -0
  244. package/dist/src/terminal-handoff-facts.d.ts +12 -0
  245. package/dist/src/terminal-handoff-facts.js +16 -0
  246. package/dist/src/terminal-handoff-facts.js.map +1 -0
  247. package/dist/src/terminal-identity-authority-cli-adapter.d.ts +318 -0
  248. package/dist/src/terminal-identity-authority-cli-adapter.js +1072 -0
  249. package/dist/src/terminal-identity-authority-cli-adapter.js.map +1 -0
  250. package/dist/src/terminal-identity-authority-service.d.ts +48 -0
  251. package/dist/src/terminal-identity-authority-service.js +45 -0
  252. package/dist/src/terminal-identity-authority-service.js.map +1 -0
  253. package/dist/src/terminal-list-cli-adapter.d.ts +274 -0
  254. package/dist/src/terminal-list-cli-adapter.js +2444 -0
  255. package/dist/src/terminal-list-cli-adapter.js.map +1 -0
  256. package/dist/src/terminal-list-renderer.d.ts +34 -0
  257. package/dist/src/terminal-list-renderer.js +643 -0
  258. package/dist/src/terminal-list-renderer.js.map +1 -0
  259. package/dist/src/terminal-maintenance-cli-adapter.d.ts +102 -0
  260. package/dist/src/terminal-maintenance-cli-adapter.js +1059 -0
  261. package/dist/src/terminal-maintenance-cli-adapter.js.map +1 -0
  262. package/dist/src/terminal-monitor-application-service.d.ts +275 -0
  263. package/dist/src/terminal-monitor-application-service.js +982 -0
  264. package/dist/src/terminal-monitor-application-service.js.map +1 -0
  265. package/dist/src/terminal-monitor-cli-adapter.d.ts +130 -0
  266. package/dist/src/terminal-monitor-cli-adapter.js +442 -0
  267. package/dist/src/terminal-monitor-cli-adapter.js.map +1 -0
  268. package/dist/src/terminal-monitor-decision-policy.d.ts +98 -0
  269. package/dist/src/terminal-monitor-decision-policy.js +246 -0
  270. package/dist/src/terminal-monitor-decision-policy.js.map +1 -0
  271. package/dist/src/terminal-monitor-launch-plan.d.ts +45 -0
  272. package/dist/src/terminal-monitor-launch-plan.js +94 -0
  273. package/dist/src/terminal-monitor-launch-plan.js.map +1 -0
  274. package/dist/src/terminal-monitor-ownership-policy.d.ts +31 -0
  275. package/dist/src/terminal-monitor-ownership-policy.js +64 -0
  276. package/dist/src/terminal-monitor-ownership-policy.js.map +1 -0
  277. package/dist/src/terminal-monitor-poll-policy.d.ts +48 -0
  278. package/dist/src/terminal-monitor-poll-policy.js +67 -0
  279. package/dist/src/terminal-monitor-poll-policy.js.map +1 -0
  280. package/dist/src/terminal-monitor-reconciliation-eligibility.d.ts +53 -0
  281. package/dist/src/terminal-monitor-reconciliation-eligibility.js +114 -0
  282. package/dist/src/terminal-monitor-reconciliation-eligibility.js.map +1 -0
  283. package/dist/src/terminal-monitor-state-cli-adapter.d.ts +106 -0
  284. package/dist/src/terminal-monitor-state-cli-adapter.js +1492 -0
  285. package/dist/src/terminal-monitor-state-cli-adapter.js.map +1 -0
  286. package/dist/src/terminal-monitor-state-reconciliation-service.d.ts +87 -0
  287. package/dist/src/terminal-monitor-state-reconciliation-service.js +106 -0
  288. package/dist/src/terminal-monitor-state-reconciliation-service.js.map +1 -0
  289. package/dist/src/terminal-monitor-supervision-cli-adapter.d.ts +122 -0
  290. package/dist/src/terminal-monitor-supervision-cli-adapter.js +685 -0
  291. package/dist/src/terminal-monitor-supervision-cli-adapter.js.map +1 -0
  292. package/dist/src/terminal-process-facts.d.ts +2 -0
  293. package/dist/src/terminal-process-facts.js +19 -0
  294. package/dist/src/terminal-process-facts.js.map +1 -0
  295. package/dist/src/terminal-process-source.d.ts +6 -4
  296. package/dist/src/terminal-process-source.js +25 -20
  297. package/dist/src/terminal-process-source.js.map +1 -1
  298. package/dist/src/terminal-runtime-cli-adapter.d.ts +80 -0
  299. package/dist/src/terminal-runtime-cli-adapter.js +488 -0
  300. package/dist/src/terminal-runtime-cli-adapter.js.map +1 -0
  301. package/dist/src/terminal-scoped-approval-authority.d.ts +62 -0
  302. package/dist/src/terminal-scoped-approval-authority.js +281 -0
  303. package/dist/src/terminal-scoped-approval-authority.js.map +1 -0
  304. package/dist/src/terminal-selector-projection-service.d.ts +26 -0
  305. package/dist/src/terminal-selector-projection-service.js +81 -0
  306. package/dist/src/terminal-selector-projection-service.js.map +1 -0
  307. package/dist/src/terminal-status-cli-adapter.d.ts +126 -0
  308. package/dist/src/terminal-status-cli-adapter.js +577 -0
  309. package/dist/src/terminal-status-cli-adapter.js.map +1 -0
  310. package/dist/src/terminal-status-facts.d.ts +56 -0
  311. package/dist/src/terminal-status-facts.js +199 -0
  312. package/dist/src/terminal-status-facts.js.map +1 -0
  313. package/dist/src/terminal-submission-acceptance.d.ts +62 -145
  314. package/dist/src/terminal-submission-acceptance.js +555 -222
  315. package/dist/src/terminal-submission-acceptance.js.map +1 -1
  316. package/dist/src/terminal-submission-facts.d.ts +123 -0
  317. package/dist/src/terminal-submission-facts.js +205 -0
  318. package/dist/src/terminal-submission-facts.js.map +1 -0
  319. package/dist/src/terminal-turn-binding-authority-cli-adapter.d.ts +14 -0
  320. package/dist/src/terminal-turn-binding-authority-cli-adapter.js +90 -0
  321. package/dist/src/terminal-turn-binding-authority-cli-adapter.js.map +1 -0
  322. package/dist/src/terminal-watch-callback-cli-adapter.d.ts +20 -0
  323. package/dist/src/terminal-watch-callback-cli-adapter.js +55 -0
  324. package/dist/src/terminal-watch-callback-cli-adapter.js.map +1 -0
  325. package/dist/src/terminal-watch-cli-adapter.d.ts +64 -0
  326. package/dist/src/terminal-watch-cli-adapter.js +508 -0
  327. package/dist/src/terminal-watch-cli-adapter.js.map +1 -0
  328. package/dist/src/terminal-watch-service.d.ts +97 -0
  329. package/dist/src/terminal-watch-service.js +714 -0
  330. package/dist/src/terminal-watch-service.js.map +1 -0
  331. package/dist/src/terminal-watch-store.d.ts +149 -0
  332. package/dist/src/terminal-watch-store.js +861 -0
  333. package/dist/src/terminal-watch-store.js.map +1 -0
  334. package/dist/src/value-guards.d.ts +4 -0
  335. package/dist/src/value-guards.js +12 -0
  336. package/dist/src/value-guards.js.map +1 -0
  337. package/dist/src/verified-dead-agent-policy.d.ts +192 -0
  338. package/dist/src/verified-dead-agent-policy.js +262 -0
  339. package/dist/src/verified-dead-agent-policy.js.map +1 -0
  340. package/docs/quickstart-herdr.md +8 -0
  341. package/docs/quickstart-tmux.md +33 -6
  342. package/openclaw.plugin.json +11 -3
  343. package/package.json +3 -1
  344. package/templates/openclaw-skills/agent-knock-knock/SKILL.md +58 -40
@@ -0,0 +1,1492 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import path from "node:path";
3
+ import { classifyCallbackProcessFailure } from "./callback-outbox-policy.js";
4
+ import { validateCodexRolloutAcceptanceAnchor } from "./terminal-submission-acceptance.js";
5
+ import { listDeferredForegroundTransfers, loadDeferredForegroundTransfer } from "./deferred-foreground-transfer.js";
6
+ import { isFinalDeferredForegroundTransferStatus } from "./deferred-foreground-transfer-policy.js";
7
+ import { executorForConversation, createMessage, isSessionSendBlockingStatus, isWaitingForAgentStatus, sessionIdForConversation, turnIdForConversation } from "./protocol.js";
8
+ import { executorDefinitionForKind } from "./executors.js";
9
+ import { redactString } from "./runtime-log.js";
10
+ import { appendEvent, listConversations, loadState, logPathForStatePath, pathsForConversationDir, saveState, statePathForConversationId, withStoreWriterLease, withStoreWriterLeaseAsync } from "./store.js";
11
+ import { readNdjsonLog } from "./transcript.js";
12
+ import { createFileLockCliAdapter } from "./file-lock-cli-adapter.js";
13
+ import { cliEnv, cliExit, cliNow, cliNowMs, cliPid, cliRuntimeLog, cliSleepSync } from "./cli-runtime-context.js";
14
+ import { expandHome } from "./cli-command-runtime.js";
15
+ import { terminalControlFromTakeover } from "./terminal-runtime-cli-adapter.js";
16
+ import { terminalControlsShareIncarnation } from "./terminal-authority-policy.js";
17
+ import { applyTerminalBridgeSubmission, terminalBridgeEnabled, terminalBridgeRequestFingerprint, terminalBridgeSubmission } from "./terminal-dispatch-receipt.js";
18
+ import { terminalMonitorActivityPersistIntervalMs, terminalMonitorApprovalCandidate, validTerminalMonitorTimestampMs } from "./terminal-monitor-decision-policy.js";
19
+ import { runTerminalMonitor as runTerminalMonitorService } from "./terminal-monitor-application-service.js";
20
+ import { pollTerminalMonitor, presentTerminalMonitor, reconcileMonitorAcceptance, recordMonitorApprovalNotification, recoverPreparedMonitorSubmission, terminalMonitorStoreLeaseTimeout, terminalMonitorStoreOperationTimeout } from "./terminal-monitor-cli-adapter.js";
21
+ import { terminalMonitorReconciliationEligibility } from "./terminal-monitor-reconciliation-eligibility.js";
22
+ import { reconcileTerminalMonitorStateCandidate } from "./terminal-monitor-state-reconciliation-service.js";
23
+ import { isRecord, nonBlankString } from "./value-guards.js";
24
+ const COLLATERAL_STALL_REASON = "a newer terminal submission has an uncertain outcome; inspect the shared terminal pane before continuing";
25
+ /** Invocation-local monitor state and reconciliation CLI boundary. */
26
+ export function createTerminalMonitorStateCliAdapter(dependencies) {
27
+ const application = new TerminalMonitorStateCliApplication(dependencies);
28
+ return Object.freeze({
29
+ runService: (input) => application.runService(input),
30
+ deferralPorts: (paths) => application.deferralPorts(paths),
31
+ reconcileCollateral: (storeDir, conversationId) => application.reconcileCollateral(storeDir, conversationId),
32
+ stallOther: (input) => application.stallOther(input),
33
+ statePaths: (listed, storeDir) => application.statePaths(listed, storeDir),
34
+ reconcileState: (input) => application.reconcileState(input),
35
+ eligibility: (conversation) => application.eligibility(conversation),
36
+ prepareLaunch: (input) => application.prepareLaunch(input)
37
+ });
38
+ }
39
+ class TerminalMonitorStateCliApplication {
40
+ #dependencies;
41
+ #stateFileLock = createFileLockCliAdapter({
42
+ now: cliNow,
43
+ nowMs: cliNowMs,
44
+ pid: cliPid,
45
+ sleepSync: cliSleepSync
46
+ });
47
+ constructor(dependencies) {
48
+ this.#dependencies = dependencies;
49
+ }
50
+ async runService(input) {
51
+ await runTerminalMonitorService({
52
+ initialConversation: input.initialConversation,
53
+ expectedTerminalMessageId: input.expectedTerminalMessageId,
54
+ lifecycle: input.lifecycle,
55
+ configuration: () => {
56
+ const configuration = input.configuration();
57
+ return {
58
+ ...configuration,
59
+ activityPersistIntervalMs: terminalMonitorActivityPersistIntervalMs(configuration.timeoutMinutes, configuration.pollIntervalMs)
60
+ };
61
+ },
62
+ ports: this.#servicePorts(input)
63
+ });
64
+ }
65
+ deferralPorts(paths) {
66
+ return {
67
+ state: {
68
+ load: () => loadState(paths.statePath),
69
+ appendEvent: (event) => appendEvent(paths.logPath, event)
70
+ },
71
+ authority: {
72
+ terminalControl: (conversation) => terminalControlFromTakeover(takeoverFor(conversation)),
73
+ bindingSuperseded: this.#dependencies.runtime.bindingSuperseded,
74
+ storeOperationTimeout: terminalMonitorStoreOperationTimeout
75
+ },
76
+ runtime: monitorRuntimePort(),
77
+ presentation: {
78
+ emit: (result) => presentTerminalMonitor(result, this.#dependencies.runtime.print)
79
+ }
80
+ };
81
+ }
82
+ statePaths(listed, storeDir) {
83
+ const statePath = expandHome(nonBlankString(listed.state_path) ??
84
+ statePathForConversationId(listed.conversation_id, storeDir));
85
+ return {
86
+ statePath,
87
+ logPath: expandHome(nonBlankString(listed.event_log_path) ?? logPathForStatePath(statePath))
88
+ };
89
+ }
90
+ eligibility(conversation) {
91
+ const staged = terminalMonitorReconciliationEligibility(conversation);
92
+ let step = staged.next();
93
+ while (!step.done) {
94
+ const request = step.value;
95
+ step = staged.next(request.kind === "control"
96
+ ? {
97
+ kind: "control",
98
+ terminalControl: terminalControlFromTakeover(request.nativeTakeover)
99
+ }
100
+ : request.kind === "dispatch"
101
+ ? {
102
+ kind: "dispatch",
103
+ ledger: this.#dependencies.dispatch.repository.load(request.terminalControl)
104
+ }
105
+ : request.kind === "store"
106
+ ? {
107
+ kind: "store",
108
+ storeDir: this.#dependencies.acceptance.storeDirForConversation(conversation)
109
+ }
110
+ : request.kind === "runtime"
111
+ ? {
112
+ kind: "runtime",
113
+ runtime: this.#dependencies.authority.identity
114
+ .terminalRuntimeIdentityForConversation(conversation, request.terminalControl)
115
+ }
116
+ : {
117
+ kind: "deferred",
118
+ transfer: loadDeferredForegroundTransfer(request.storeDir, request.transferId)
119
+ });
120
+ }
121
+ return step.value;
122
+ }
123
+ async reconcileState(input) {
124
+ return reconcileTerminalMonitorStateCandidate({
125
+ storeDir: input.storeDir,
126
+ listed: input.listed,
127
+ paths: input.paths,
128
+ includeCallbackRecovery: input.includeCallbackRecovery,
129
+ callbackRetryDelayMs: input.options.callbackRetryDelayMs,
130
+ ports: this.#stateReconciliationPorts(input.options)
131
+ });
132
+ }
133
+ #stateReconciliationPorts(options) {
134
+ return {
135
+ state: {
136
+ isTerminalBridge: terminalBridgeEnabled
137
+ },
138
+ completion: {
139
+ settleLocal: (storeDir, paths) => this.#dependencies.dispatch.recovery.settleLocalCompletion({
140
+ storeDir,
141
+ statePath: paths.statePath,
142
+ logPath: paths.logPath
143
+ }),
144
+ verifiedDead: ({ storeDir, paths, conversation }) => this.#dependencies.dispatch.recovery.stallAccepted({
145
+ options,
146
+ storeDir,
147
+ statePath: paths.statePath,
148
+ logPath: paths.logPath,
149
+ expectedConversationId: conversation.conversation_id,
150
+ expectedMessageId: nonBlankString(takeoverFor(conversation)?.terminal_bridge_message_id)
151
+ })
152
+ },
153
+ callbacks: {
154
+ reconcile: (storeDir, paths, delayMs) => withStoreWriterLease(storeDir, () => this.#dependencies.callbacks.reconcileDelivery({
155
+ statePath: paths.statePath,
156
+ logPath: paths.logPath,
157
+ delayMs
158
+ })),
159
+ run: (prepared, callbackOptions) => this.#dependencies.callbacks.runPrepared(prepared, callbackOptions)
160
+ },
161
+ authority: {
162
+ migrateIdentity: (listed, paths) => this.#dependencies.authority.identity
163
+ .migrateLegacyTerminalAgentIdentity({
164
+ conversation: loadState(paths.statePath),
165
+ statePath: paths.statePath,
166
+ logPath: paths.logPath,
167
+ options
168
+ }),
169
+ recoverDeferred: (storeDir, conversation, paths) => this.#recoverDeferred(options, storeDir, conversation, paths),
170
+ recoverVirgin: async (conversation, paths) => (await this.#dependencies.acceptance.recoverVirgin({
171
+ options,
172
+ conversation,
173
+ statePath: paths.statePath,
174
+ logPath: paths.logPath
175
+ })).conversation,
176
+ assertBindingCurrent: (storeDir, conversation) => {
177
+ const transferId = nonBlankString(takeoverFor(conversation)?.deferred_foreground_transfer_id);
178
+ const transfer = transferId
179
+ ? loadDeferredForegroundTransfer(storeDir, transferId)
180
+ : undefined;
181
+ if (!transfer || isFinalDeferredForegroundTransferStatus(transfer.status)) {
182
+ this.#dependencies.authority.assertBindingCurrent(conversation, "reconcile monitor for");
183
+ }
184
+ },
185
+ eligibility: (conversation) => this.eligibility(conversation)
186
+ }
187
+ };
188
+ }
189
+ async #recoverDeferred(options, storeDir, initialConversation, paths) {
190
+ const takeover = takeoverFor(initialConversation);
191
+ const transferId = nonBlankString(takeover?.deferred_foreground_transfer_id);
192
+ if (!transferId) {
193
+ return initialConversation;
194
+ }
195
+ const transfer = loadDeferredForegroundTransfer(storeDir, transferId);
196
+ if (isFinalDeferredForegroundTransferStatus(transfer.status)) {
197
+ return initialConversation;
198
+ }
199
+ const control = terminalControlFromTakeover(takeover);
200
+ const pid = Number(takeover?.terminal_agent_pid);
201
+ if (!control || !Number.isSafeInteger(pid) || pid <= 1) {
202
+ throw new Error(`deferred foreground Turn ${initialConversation.conversation_id} ` +
203
+ "lost its exact terminal process authority");
204
+ }
205
+ const terminal = await this.#dependencies.authority.createBridge(options)
206
+ .resolveStoredTerminal("codex", pid, control, { pid });
207
+ const release = this.#dependencies.dispatch.repository.acquire(storeDir, control, { timeoutMs: 30000 });
208
+ try {
209
+ await this.#dependencies.authority.handoff
210
+ .recoverDeferredCodexForegroundTransferBeforeMutation({
211
+ options,
212
+ terminal: terminal
213
+ });
214
+ }
215
+ finally {
216
+ release();
217
+ }
218
+ const conversation = loadState(paths.statePath);
219
+ this.#assertExactPendingDeferred(conversation, storeDir, transferId);
220
+ return conversation;
221
+ }
222
+ #assertExactPendingDeferred(conversation, storeDir, transferId) {
223
+ const transfer = loadDeferredForegroundTransfer(storeDir, transferId);
224
+ if (isFinalDeferredForegroundTransferStatus(transfer.status)) {
225
+ return;
226
+ }
227
+ const takeover = takeoverFor(conversation);
228
+ const submission = terminalBridgeSubmission(conversation);
229
+ const anchor = validateCodexRolloutAcceptanceAnchor(takeover?.codex_rollout_acceptance_anchor);
230
+ if (anchor?.version !== 3 ||
231
+ conversation.status !== "waiting_for_agent" ||
232
+ submission?.status !== "enter_dispatched" ||
233
+ nonBlankString(submission.message_id) !==
234
+ nonBlankString(takeover?.terminal_bridge_message_id) ||
235
+ transfer.status !== "dispatch_started") {
236
+ throw new Error(`deferred foreground Turn ${conversation.conversation_id} ` +
237
+ "is not an exact pending candidate-set acceptance");
238
+ }
239
+ }
240
+ prepareLaunch(input) {
241
+ const release = this.#stateFileLock.acquire(`${input.statePath}.lock`);
242
+ try {
243
+ const conversation = loadState(input.statePath);
244
+ if (input.requireWaitingForAgentStatus &&
245
+ conversation.status !== "waiting_for_agent") {
246
+ return unprepared(`conversation_status_${String(conversation.status ?? "missing")}`);
247
+ }
248
+ const eligibility = this.eligibility(conversation);
249
+ if (!eligibility.eligible) {
250
+ return unprepared(eligibility.reason);
251
+ }
252
+ if (eligibility.terminalMessageId !== input.expectedMessageId) {
253
+ return unprepared("terminal_bridge_task_replaced");
254
+ }
255
+ const owner = input.activeOwner(input.statePath, eligibility.terminalMessageId);
256
+ if (owner) {
257
+ return {
258
+ prepared: false,
259
+ alreadyRunning: true,
260
+ reason: "monitor_lock_owner_alive",
261
+ ownerPid: owner.ownerPid
262
+ };
263
+ }
264
+ return this.#persistMonitorLockVersion(conversation, eligibility, input.monitorLockVersion, input.statePath);
265
+ }
266
+ finally {
267
+ release();
268
+ }
269
+ }
270
+ #persistMonitorLockVersion(conversation, eligibility, lockVersion, statePath) {
271
+ const needsSave = eligibility.nativeTakeover.terminal_bridge_monitor_lock_version !==
272
+ lockVersion;
273
+ const preparedConversation = needsSave
274
+ ? {
275
+ ...conversation,
276
+ native_session_takeover: {
277
+ ...eligibility.nativeTakeover,
278
+ terminal_bridge_monitor_lock_version: lockVersion
279
+ },
280
+ updated_at: cliNow().toISOString()
281
+ }
282
+ : conversation;
283
+ if (needsSave) {
284
+ saveState(statePath, preparedConversation);
285
+ }
286
+ return {
287
+ prepared: true,
288
+ conversation: preparedConversation,
289
+ terminalControl: eligibility.terminalControl,
290
+ inactivityTimeoutMinutes: eligibility.inactivityTimeoutMinutes,
291
+ hardTimeoutMinutes: eligibility.hardTimeoutMinutes
292
+ };
293
+ }
294
+ #servicePorts(input) {
295
+ let resolvedStateStoreDir;
296
+ const commandStoreDir = () => this.#dependencies.runtime.storeDir(input.options);
297
+ const stateStoreDir = () => resolvedStateStoreDir ??=
298
+ pathsForConversationDir(path.dirname(input.statePath)).storeDir;
299
+ const bridge = input.terminalBridge;
300
+ return {
301
+ state: {
302
+ load: () => loadState(input.statePath),
303
+ appendEvent: (event) => appendEvent(input.logPath, event),
304
+ markStalled: (reason, detail) => {
305
+ const conversation = this.#markStalled({
306
+ statePath: input.statePath,
307
+ logPath: input.logPath,
308
+ reason,
309
+ detail
310
+ });
311
+ if (!conversation) {
312
+ throw new Error("terminal monitor stall transaction returned no conversation");
313
+ }
314
+ return conversation;
315
+ },
316
+ persistActivity: (request) => this.#persistActivity({
317
+ ...request,
318
+ statePath: input.statePath,
319
+ logPath: input.logPath
320
+ }),
321
+ persistDetectorDiagnostic: (request) => this.#persistDetectorDiagnostic({
322
+ ...request,
323
+ statePath: input.statePath,
324
+ logPath: input.logPath
325
+ }),
326
+ markApprovalPromptCleared: (request) => this.#markApprovalPromptCleared({
327
+ ...request,
328
+ statePath: input.statePath,
329
+ logPath: input.logPath
330
+ }),
331
+ recordApprovalNotification: (request) => recordMonitorApprovalNotification({
332
+ ...request,
333
+ ports: {
334
+ record: (recordRequest) => this.#recordApprovalNotification({
335
+ ...recordRequest,
336
+ statePath: input.statePath,
337
+ logPath: input.logPath
338
+ }),
339
+ prepare: (prepareRequest) => this.#dependencies.callbacks.prepareApprovalNotification({
340
+ options: { ...input.options, statePath: input.statePath },
341
+ statePath: input.statePath,
342
+ logPath: input.logPath,
343
+ ...prepareRequest
344
+ }),
345
+ approvalInstructions: terminalBridgeApprovalInstructions,
346
+ approvalCandidate: terminalMonitorApprovalCandidate
347
+ }
348
+ })
349
+ },
350
+ authority: {
351
+ initialize: () => { bridge(); },
352
+ terminalControl: (conversation) => terminalControlFromTakeover(takeoverFor(conversation)),
353
+ submission: terminalBridgeSubmission,
354
+ isWaitingForAgent: isWaitingForAgentStatus,
355
+ isProcessAlive: this.#dependencies.runtime.isProcessAlive,
356
+ markAcceptanceUncertain: (request) => this.#dependencies.acceptance.markUncertain({
357
+ ...request,
358
+ statePath: input.statePath,
359
+ logPath: input.logPath
360
+ }),
361
+ reconcileAcceptance: (request) => reconcileMonitorAcceptance({
362
+ terminalControl: request.terminalControl,
363
+ acquireTerminal: (control) => this.#dependencies.dispatch.repository.acquire(commandStoreDir(), control, { timeoutMs: 30000 }),
364
+ reconcile: () => this.#dependencies.acceptance.reconcileMonitor({
365
+ ...request,
366
+ options: input.options,
367
+ statePath: input.statePath,
368
+ logPath: input.logPath,
369
+ terminalBridge: bridge()
370
+ }),
371
+ apply: request.apply,
372
+ recover: request.recover
373
+ }),
374
+ recoverPreparedSubmission: (request) => recoverPreparedMonitorSubmission({
375
+ ...request,
376
+ statePath: input.statePath,
377
+ logPath: input.logPath,
378
+ ports: {
379
+ acquireTerminal: (control) => this.#dependencies.dispatch.repository.acquire(commandStoreDir(), control, { timeoutMs: 30000 }),
380
+ withWriter: (use) => withStoreWriterLeaseAsync(stateStoreDir(), use),
381
+ acquireState: () => this.#stateFileLock.acquire(`${input.statePath}.lock`),
382
+ loadConversation: () => loadState(input.statePath),
383
+ loadLedger: this.#dependencies.dispatch.repository.load,
384
+ saveLedger: this.#dependencies.dispatch.repository.save,
385
+ saveConversation: (conversation) => saveState(input.statePath, conversation),
386
+ submission: terminalBridgeSubmission,
387
+ applySubmission: (mutation) => applyTerminalBridgeSubmission(mutation, {
388
+ dispatcherPid: cliPid(),
389
+ storeDir: this.#dependencies.acceptance
390
+ .storeDirForConversation(mutation.conversation),
391
+ terminalControl: terminalControlFromTakeover(takeoverFor(mutation.conversation))
392
+ }),
393
+ requestFingerprint: terminalBridgeRequestFingerprint,
394
+ now: cliNow,
395
+ appendEvent: (event) => appendEvent(input.logPath, event),
396
+ stallCollateral: (stallRequest) => {
397
+ this.#stallOtherConversations({
398
+ storeDir: stateStoreDir(),
399
+ ...stallRequest
400
+ });
401
+ }
402
+ }
403
+ }),
404
+ assertBindingCurrent: (conversation) => this.#dependencies.authority.assertBindingCurrent(conversation, "monitor"),
405
+ bindingSuperseded: this.#dependencies.runtime.bindingSuperseded,
406
+ storeOperationTimeout: terminalMonitorStoreOperationTimeout,
407
+ storeLeaseTimeout: terminalMonitorStoreLeaseTimeout,
408
+ poll: (request) => pollTerminalMonitor({
409
+ ...request,
410
+ terminalBridge: bridge(),
411
+ scrollbackLines: Number(input.options.scrollbackLines ?? 120),
412
+ ports: {
413
+ acquireTerminal: (control) => this.#dependencies.dispatch.repository.acquire(commandStoreDir(), control, { timeoutMs: 30000 }),
414
+ reconcileLedger: this.#dependencies.dispatch.recovery.reconcilePrepared,
415
+ loadLedger: this.#dependencies.dispatch.repository.load,
416
+ saveLedger: this.#dependencies.dispatch.repository.save,
417
+ submission: terminalBridgeSubmission,
418
+ loadConversation: () => loadState(input.statePath),
419
+ terminalControl: (conversation) => terminalControlFromTakeover(takeoverFor(conversation)),
420
+ sameIncarnation: terminalControlsShareIncarnation,
421
+ runtime: (conversation, control) => this.#dependencies.authority.identity
422
+ .terminalRuntimeIdentityForConversation(conversation, control),
423
+ durableRequest: (conversation, control) => this.#dependencies.authority.identity
424
+ .terminalDurableRequestForConversation(conversation, control),
425
+ appendEvent: (event) => appendEvent(input.logPath, event),
426
+ now: cliNow
427
+ }
428
+ })
429
+ },
430
+ callbacks: {
431
+ prepareCompletion: (request) => this.#dependencies.dispatch.recovery.prepareCompletion({
432
+ options: input.options,
433
+ statePath: input.statePath,
434
+ logPath: input.logPath,
435
+ ...request
436
+ }),
437
+ verifiedDead: (request) => this.#dependencies.dispatch.recovery.stallAccepted({
438
+ options: input.options,
439
+ storeDir: stateStoreDir(),
440
+ statePath: input.statePath,
441
+ logPath: input.logPath,
442
+ expectedConversationId: request.conversationId,
443
+ expectedMessageId: request.messageId
444
+ }),
445
+ run: (prepared, callbackOptions) => this.#dependencies.callbacks.runPrepared(prepared, callbackOptions),
446
+ emit: this.#dependencies.callbacks.emitPreparedResult
447
+ },
448
+ runtime: monitorRuntimePort(),
449
+ presentation: {
450
+ emit: (result) => presentTerminalMonitor(result, this.#dependencies.runtime.print)
451
+ }
452
+ };
453
+ }
454
+ #stallOtherConversations(input) {
455
+ const stalledConversationIds = [];
456
+ for (const listed of listConversations(input.storeDir)) {
457
+ if (listed.conversation_id === input.currentConversationId ||
458
+ !isSessionSendBlockingStatus(listed.status)) {
459
+ continue;
460
+ }
461
+ const listedTakeover = takeoverFor(listed);
462
+ if (listedTakeover?.terminal_bridge !== true ||
463
+ !terminalControlsShareIncarnation(terminalControlFromTakeover(listedTakeover), input.terminalControl)) {
464
+ continue;
465
+ }
466
+ const statePath = nonBlankString(listed.state_path);
467
+ if (!statePath) {
468
+ continue;
469
+ }
470
+ const release = this.#stateFileLock.acquire(`${statePath}.lock`);
471
+ try {
472
+ const current = loadState(statePath);
473
+ const currentTakeover = takeoverFor(current);
474
+ if (!isSessionSendBlockingStatus(current.status) ||
475
+ currentTakeover?.terminal_bridge !== true ||
476
+ !terminalControlsShareIncarnation(terminalControlFromTakeover(currentTakeover), input.terminalControl)) {
477
+ continue;
478
+ }
479
+ const stalledAt = cliNow().toISOString();
480
+ const stalledConversation = {
481
+ ...current,
482
+ status: "stalled",
483
+ stalled_at: stalledAt,
484
+ stalled_reason: COLLATERAL_STALL_REASON,
485
+ native_session_takeover: {
486
+ ...currentTakeover,
487
+ terminal_bridge_uncertain_dispatch_fence: {
488
+ message_id: input.uncertainMessageId,
489
+ observed_at: stalledAt,
490
+ previous_status: current.status
491
+ }
492
+ },
493
+ updated_at: stalledAt
494
+ };
495
+ saveState(statePath, stalledConversation);
496
+ try {
497
+ appendEvent(logPathForStatePath(statePath), {
498
+ ts: stalledAt,
499
+ conversation_id: current.conversation_id,
500
+ event: "terminal_bridge_stalled_by_uncertain_dispatch",
501
+ terminal_control: input.terminalControl,
502
+ uncertain_message_id: input.uncertainMessageId
503
+ });
504
+ }
505
+ catch {
506
+ // State plus the terminal ledger are the authoritative fence.
507
+ }
508
+ stalledConversationIds.push(current.conversation_id);
509
+ }
510
+ finally {
511
+ release();
512
+ }
513
+ }
514
+ return stalledConversationIds;
515
+ }
516
+ stallOther(input) {
517
+ return this.#stallOtherConversations(input);
518
+ }
519
+ #exactCollateralRepairEvidence(conversation, storeDir) {
520
+ if (conversation.status !== "stalled" ||
521
+ conversation.stalled_reason !== COLLATERAL_STALL_REASON) {
522
+ return undefined;
523
+ }
524
+ const takeover = takeoverFor(conversation);
525
+ const fence = isRecord(takeover?.terminal_bridge_uncertain_dispatch_fence)
526
+ ? takeover.terminal_bridge_uncertain_dispatch_fence
527
+ : undefined;
528
+ const uncertainMessageId = nonBlankString(fence?.message_id);
529
+ const fenceObservedAt = nonBlankString(fence?.observed_at);
530
+ const previousStatus = nonBlankString(fence?.previous_status);
531
+ const ownMessageId = nonBlankString(takeover?.terminal_bridge_message_id);
532
+ const ownSubmission = terminalBridgeSubmission(conversation);
533
+ const completionClaim = isRecord(takeover?.terminal_bridge_completion_claim)
534
+ ? takeover.terminal_bridge_completion_claim
535
+ : undefined;
536
+ const callbackDelivery = isRecord(conversation.callback_delivery)
537
+ ? conversation.callback_delivery
538
+ : undefined;
539
+ const callbackMessage = isRecord(callbackDelivery?.message)
540
+ ? callbackDelivery.message
541
+ : undefined;
542
+ const idleSince = nonBlankString(conversation.idle_since);
543
+ const deliveredAt = nonBlankString(callbackDelivery?.delivered_at);
544
+ const claimedAt = nonBlankString(completionClaim?.claimed_at);
545
+ const fenceAtMs = validTerminalMonitorTimestampMs(fenceObservedAt);
546
+ const idleAtMs = validTerminalMonitorTimestampMs(idleSince);
547
+ const deliveredAtMs = validTerminalMonitorTimestampMs(deliveredAt);
548
+ const claimedAtMs = validTerminalMonitorTimestampMs(claimedAt);
549
+ if (takeover?.terminal_bridge !== true ||
550
+ !uncertainMessageId ||
551
+ !fenceObservedAt ||
552
+ fenceAtMs === undefined ||
553
+ uncertainMessageId === ownMessageId ||
554
+ (previousStatus !== undefined && previousStatus !== "idle") ||
555
+ conversation.stalled_at !== fenceObservedAt ||
556
+ conversation.updated_at !== fenceObservedAt ||
557
+ !idleSince ||
558
+ idleAtMs === undefined ||
559
+ idleAtMs > fenceAtMs ||
560
+ !ownMessageId ||
561
+ ownSubmission?.status !== "agent_accepted" ||
562
+ nonBlankString(ownSubmission.message_id) !== ownMessageId ||
563
+ nonBlankString(ownSubmission.session_id) !==
564
+ sessionIdForConversation(conversation) ||
565
+ nonBlankString(ownSubmission.turn_id) !== turnIdForConversation(conversation) ||
566
+ !completionClaim ||
567
+ nonBlankString(completionClaim.terminal_bridge_message_id) !== ownMessageId ||
568
+ completionClaim.outcome !== "success" ||
569
+ !claimedAt ||
570
+ claimedAtMs === undefined ||
571
+ claimedAtMs > fenceAtMs ||
572
+ callbackDelivery?.status !== "delivered" ||
573
+ callbackDelivery.final_status !== "idle" ||
574
+ callbackDelivery.preserve_conversation_status !== true ||
575
+ !callbackMessage ||
576
+ callbackMessage.type !== "done" ||
577
+ callbackMessage.requires_response !== false ||
578
+ nonBlankString(callbackMessage.id) !==
579
+ nonBlankString(completionClaim.callback_message_id) ||
580
+ nonBlankString(callbackMessage.conversation_id) !==
581
+ conversation.conversation_id ||
582
+ nonBlankString(callbackMessage.session_id) !==
583
+ sessionIdForConversation(conversation) ||
584
+ nonBlankString(callbackMessage.turn_id) !== turnIdForConversation(conversation) ||
585
+ nonBlankString(isRecord(callbackMessage.metadata)
586
+ ? callbackMessage.metadata.terminal_bridge_message_id
587
+ : undefined) !== ownMessageId ||
588
+ !deliveredAt ||
589
+ deliveredAtMs === undefined ||
590
+ deliveredAtMs > fenceAtMs) {
591
+ return undefined;
592
+ }
593
+ const control = this.#dependencies.authority
594
+ .terminalControlForConversation(conversation);
595
+ if (!control) {
596
+ return undefined;
597
+ }
598
+ const ownerCandidates = listConversations(storeDir).filter((candidate) => {
599
+ if (candidate.conversation_id === conversation.conversation_id) {
600
+ return false;
601
+ }
602
+ const candidateTakeover = takeoverFor(candidate);
603
+ const candidateSubmission = terminalBridgeSubmission(candidate);
604
+ return candidateTakeover?.terminal_bridge === true &&
605
+ nonBlankString(candidateTakeover.terminal_bridge_message_id) ===
606
+ uncertainMessageId &&
607
+ nonBlankString(candidateSubmission?.message_id) === uncertainMessageId &&
608
+ terminalControlsShareIncarnation(this.#dependencies.authority.terminalControlForConversation(candidate), control);
609
+ });
610
+ if (ownerCandidates.length !== 1) {
611
+ return undefined;
612
+ }
613
+ return this.#verifyCollateralOwner({
614
+ conversation,
615
+ takeover,
616
+ ownMessageId,
617
+ completionClaim,
618
+ callbackMessage,
619
+ callbackMessageId: nonBlankString(callbackMessage.id),
620
+ deliveredAt,
621
+ claimedAt,
622
+ fenceObservedAt,
623
+ fenceAtMs,
624
+ uncertainMessageId,
625
+ control,
626
+ ownerListed: ownerCandidates[0]
627
+ });
628
+ }
629
+ #verifyCollateralOwner(input) {
630
+ const ownerStatePath = nonBlankString(input.ownerListed.state_path);
631
+ if (!ownerStatePath) {
632
+ return undefined;
633
+ }
634
+ let owner;
635
+ let ownerEvents;
636
+ let events;
637
+ try {
638
+ owner = loadState(ownerStatePath);
639
+ ownerEvents = readNdjsonLog(nonBlankString(owner.event_log_path) ??
640
+ logPathForStatePath(ownerStatePath));
641
+ const statePath = nonBlankString(input.conversation.state_path);
642
+ if (!statePath) {
643
+ return undefined;
644
+ }
645
+ events = readNdjsonLog(nonBlankString(input.conversation.event_log_path) ??
646
+ logPathForStatePath(statePath));
647
+ }
648
+ catch {
649
+ return undefined;
650
+ }
651
+ const ownerTakeover = takeoverFor(owner);
652
+ const ownerSubmission = terminalBridgeSubmission(owner);
653
+ const ownerClosedAt = nonBlankString(owner.closed_at);
654
+ const ownerClosedAtMs = validTerminalMonitorTimestampMs(ownerClosedAt);
655
+ if (owner.conversation_id !== input.ownerListed.conversation_id ||
656
+ owner.status !== "closed" ||
657
+ !ownerClosedAt ||
658
+ ownerClosedAtMs === undefined ||
659
+ ownerClosedAtMs < input.fenceAtMs ||
660
+ !nonBlankString(owner.close_reason) ||
661
+ owner.updated_at !== ownerClosedAt ||
662
+ ownerTakeover?.terminal_bridge !== true ||
663
+ nonBlankString(ownerTakeover.terminal_bridge_message_id) !==
664
+ input.uncertainMessageId ||
665
+ ownerSubmission?.status !== "uncertain" ||
666
+ nonBlankString(ownerSubmission.message_id) !== input.uncertainMessageId ||
667
+ nonBlankString(ownerSubmission.session_id) !== sessionIdForConversation(owner) ||
668
+ nonBlankString(ownerSubmission.turn_id) !== turnIdForConversation(owner) ||
669
+ isRecord(ownerTakeover.terminal_bridge_uncertain_dispatch_fence) ||
670
+ !terminalControlsShareIncarnation(this.#dependencies.authority.terminalControlForConversation(owner), input.control)) {
671
+ return undefined;
672
+ }
673
+ if (!this.#hasExactCollateralEvents(input, owner, ownerClosedAt, events, ownerEvents)) {
674
+ return undefined;
675
+ }
676
+ return {
677
+ uncertainMessageId: input.uncertainMessageId,
678
+ ownerConversationId: owner.conversation_id,
679
+ restoredStatus: "idle"
680
+ };
681
+ }
682
+ #hasExactCollateralEvents(input, owner, ownerClosedAt, events, ownerEvents) {
683
+ const hasCompletionClaim = events.some((event) => event.event === "terminal_bridge_completion_claimed" &&
684
+ event.conversation_id === input.conversation.conversation_id &&
685
+ event.terminal_bridge_message_id === input.ownMessageId &&
686
+ event.callback_message_id === input.callbackMessageId &&
687
+ event.completion_fingerprint === input.completionClaim.completion_fingerprint &&
688
+ event.completion_id === input.completionClaim.completion_id &&
689
+ event.outcome === "success" &&
690
+ event.ts === input.claimedAt);
691
+ const hasCompletionDetected = events.some((event) => event.event === "terminal_bridge_completion_detected" &&
692
+ event.conversation_id === input.conversation.conversation_id &&
693
+ event.terminal_bridge_message_id === input.ownMessageId &&
694
+ event.callback_message_id === input.callbackMessageId &&
695
+ event.completion_id === input.completionClaim.completion_id &&
696
+ event.completion_outcome === "success" &&
697
+ validTerminalMonitorTimestampMs(event.ts) !== undefined &&
698
+ validTerminalMonitorTimestampMs(event.ts) <= input.fenceAtMs);
699
+ const hasDeliveredCallback = events.some((event) => event.event === "callback_delivery_succeeded" &&
700
+ event.conversation_id === input.conversation.conversation_id &&
701
+ event.message_id === input.callbackMessageId &&
702
+ event.status === "idle" &&
703
+ event.ts === input.deliveredAt);
704
+ const hasExactFence = events.some((event) => event.event === "terminal_bridge_stalled_by_uncertain_dispatch" &&
705
+ event.conversation_id === input.conversation.conversation_id &&
706
+ event.uncertain_message_id === input.uncertainMessageId &&
707
+ event.ts === input.fenceObservedAt);
708
+ const hasExactOwnerClose = ownerEvents.some((event) => event.event === "conversation_closed" &&
709
+ event.conversation_id === owner.conversation_id &&
710
+ event.status === "closed" &&
711
+ event.ts === ownerClosedAt &&
712
+ event.reason === owner.close_reason);
713
+ return hasCompletionClaim && hasCompletionDetected &&
714
+ hasDeliveredCallback && hasExactFence && hasExactOwnerClose;
715
+ }
716
+ async reconcileCollateral(storeDir, conversationId) {
717
+ return withStoreWriterLeaseAsync(storeDir, async () => this.#reconcileCollateralLocked(storeDir, conversationId));
718
+ }
719
+ #reconcileCollateralLocked(storeDir, conversationId) {
720
+ const reservedSourceTurnIds = new Set(listDeferredForegroundTransfers(storeDir)
721
+ .filter((transfer) => transfer.version === 2 &&
722
+ transfer.source_kind === "candidate_rollout_quiescent" &&
723
+ !isFinalDeferredForegroundTransferStatus(transfer.status))
724
+ .flatMap((transfer) => (transfer.source_turn_history ?? []).map((turn) => turn.turn_id)));
725
+ const candidates = listConversations(storeDir).filter((conversation) => {
726
+ const takeover = takeoverFor(conversation);
727
+ return (conversationId === undefined ||
728
+ conversation.conversation_id === conversationId) &&
729
+ !reservedSourceTurnIds.has(turnIdForConversation(conversation)) &&
730
+ conversation.status === "stalled" &&
731
+ isRecord(takeover?.terminal_bridge_uncertain_dispatch_fence);
732
+ });
733
+ const result = {
734
+ checked: candidates.length,
735
+ repaired: 0,
736
+ skipped: 0,
737
+ errors: [],
738
+ items: []
739
+ };
740
+ for (const listed of candidates) {
741
+ this.#reconcileCollateralCandidate(storeDir, listed, result);
742
+ }
743
+ return result;
744
+ }
745
+ #reconcileCollateralCandidate(storeDir, listed, result) {
746
+ const statePath = nonBlankString(listed.state_path);
747
+ if (!statePath) {
748
+ result.skipped += 1;
749
+ return;
750
+ }
751
+ let release;
752
+ try {
753
+ release = this.#stateFileLock.acquire(`${statePath}.lock`);
754
+ const current = loadState(statePath);
755
+ const evidence = this.#exactCollateralRepairEvidence(current, storeDir);
756
+ if (!evidence) {
757
+ result.skipped += 1;
758
+ return;
759
+ }
760
+ const repaired = this.#persistCollateralRepair(statePath, current, evidence);
761
+ result.repaired += 1;
762
+ result.items.push(repaired);
763
+ }
764
+ catch (error) {
765
+ result.skipped += 1;
766
+ const reason = error instanceof Error ? error.message : String(error);
767
+ result.errors.push(`${listed.conversation_id}: ${reason}`);
768
+ result.items.push({
769
+ conversation_id: listed.conversation_id,
770
+ status: "error",
771
+ reason
772
+ });
773
+ }
774
+ finally {
775
+ release?.();
776
+ }
777
+ }
778
+ #persistCollateralRepair(statePath, current, evidence) {
779
+ const takeover = { ...takeoverFor(current) };
780
+ delete takeover.terminal_bridge_uncertain_dispatch_fence;
781
+ const repairedAt = cliNow().toISOString();
782
+ takeover.terminal_bridge_collateral_stall_repair = {
783
+ repaired_at: repairedAt,
784
+ uncertain_message_id: evidence.uncertainMessageId,
785
+ uncertain_owner_conversation_id: evidence.ownerConversationId,
786
+ restored_status: evidence.restoredStatus,
787
+ evidence: "foreign_uncertain_fence+completion_claim+delivered_callback+closed_owner"
788
+ };
789
+ const conversation = {
790
+ ...current,
791
+ status: evidence.restoredStatus,
792
+ native_session_takeover: takeover,
793
+ updated_at: repairedAt
794
+ };
795
+ delete conversation.stalled_at;
796
+ delete conversation.stalled_reason;
797
+ saveState(statePath, conversation);
798
+ const eventWarning = this.#appendCollateralRepairEvent(statePath, current, evidence, repairedAt);
799
+ return {
800
+ conversation_id: current.conversation_id,
801
+ status: "repaired",
802
+ reason: "legacy_terminal_bridge_collateral_stall",
803
+ uncertain_message_id: evidence.uncertainMessageId,
804
+ uncertain_owner_conversation_id: evidence.ownerConversationId,
805
+ restored_status: evidence.restoredStatus,
806
+ ...(eventWarning ? { event_warning: eventWarning } : {})
807
+ };
808
+ }
809
+ #appendCollateralRepairEvent(statePath, current, evidence, repairedAt) {
810
+ try {
811
+ appendEvent(nonBlankString(current.event_log_path) ??
812
+ logPathForStatePath(statePath), {
813
+ ts: repairedAt,
814
+ conversation_id: current.conversation_id,
815
+ event: "terminal_bridge_collateral_stall_repaired",
816
+ uncertain_message_id: evidence.uncertainMessageId,
817
+ uncertain_owner_conversation_id: evidence.ownerConversationId,
818
+ previous_status: "stalled",
819
+ restored_status: evidence.restoredStatus,
820
+ evidence: "foreign_uncertain_fence+completion_claim+delivered_callback+closed_owner"
821
+ });
822
+ return undefined;
823
+ }
824
+ catch (error) {
825
+ const warning = error instanceof Error ? error.message : String(error);
826
+ cliRuntimeLog("warn", "terminal_bridge_collateral_stall_repair_event_failed", {
827
+ conversation_id: current.conversation_id,
828
+ uncertain_message_id: evidence.uncertainMessageId,
829
+ error: warning
830
+ });
831
+ return warning;
832
+ }
833
+ }
834
+ #recordApprovalNotification(input) {
835
+ const storeDir = pathsForConversationDir(path.dirname(input.statePath))
836
+ .storeDir;
837
+ return withStoreWriterLease(storeDir, () => {
838
+ const release = this.#stateFileLock.acquire(`${input.statePath}.lock`);
839
+ try {
840
+ const conversation = loadState(input.statePath);
841
+ if (!this.#approvalSnapshotMatches(conversation, input)) {
842
+ return {
843
+ conversation,
844
+ duplicate: false,
845
+ stale: true,
846
+ recorded: undefined
847
+ };
848
+ }
849
+ return this.#recordMatchingApproval(this.#approvalPersistenceContext(conversation, input));
850
+ }
851
+ finally {
852
+ release();
853
+ }
854
+ });
855
+ }
856
+ #approvalSnapshotMatches(conversation, input) {
857
+ const takeover = takeoverFor(conversation);
858
+ const currentControl = terminalControlFromTakeover(takeover);
859
+ return isWaitingForAgentStatus(conversation.status) &&
860
+ conversation.conversation_id === input.expectedConversation.conversationId &&
861
+ conversation.status === input.expectedConversation.status &&
862
+ conversation.updated_at === input.expectedConversation.updatedAt &&
863
+ takeover?.terminal_bridge === true &&
864
+ nonBlankString(takeover.terminal_bridge_message_id) ===
865
+ input.expectedConversation.messageId &&
866
+ currentControl !== undefined &&
867
+ terminalControlsShareIncarnation(currentControl, input.terminalControl);
868
+ }
869
+ #approvalPersistenceContext(conversation, input) {
870
+ const nativeTakeover = { ...takeoverFor(conversation) };
871
+ const screen = isRecord(input.terminalStatus.screen)
872
+ ? input.terminalStatus.screen
873
+ : undefined;
874
+ const approvalScreenDigest = nonBlankString(screen?.digest);
875
+ const previousApproval = isRecord(nativeTakeover.terminal_bridge_approval)
876
+ ? nativeTakeover.terminal_bridge_approval
877
+ : undefined;
878
+ const previousNotifiedAt = validTerminalMonitorTimestampMs(previousApproval?.notified_at);
879
+ const callbackDelivery = isRecord(conversation.callback_delivery)
880
+ ? conversation.callback_delivery
881
+ : undefined;
882
+ const callbackMessage = isRecord(callbackDelivery?.message)
883
+ ? callbackDelivery.message
884
+ : undefined;
885
+ const previousCallbackMessageId = nonBlankString(previousApproval?.callback_message_id);
886
+ const matchingApprovalOutbox = callbackDelivery?.kind === "approval_notification" &&
887
+ previousCallbackMessageId !== undefined &&
888
+ callbackMessage?.id === previousCallbackMessageId;
889
+ const deliveryStatus = nonBlankString(callbackDelivery?.status);
890
+ const deliveryAttempts = Number(callbackDelivery?.attempts ?? 0);
891
+ const conflictingActiveOutbox = !matchingApprovalOutbox && (deliveryStatus === "pending" ||
892
+ (deliveryStatus === "failed" &&
893
+ Number.isFinite(deliveryAttempts) &&
894
+ deliveryAttempts <= this.#dependencies.runtime.callbackRetryLimit));
895
+ return {
896
+ input,
897
+ conversation,
898
+ nativeTakeover,
899
+ approvalScreenDigest,
900
+ previousApproval,
901
+ previousNotifiedAt,
902
+ previousCallbackMessageId,
903
+ matchingApprovalOutbox,
904
+ conflictingActiveOutbox
905
+ };
906
+ }
907
+ #recordMatchingApproval(context) {
908
+ const duplicate = context.previousApproval?.fingerprint === context.input.fingerprint &&
909
+ context.previousNotifiedAt !== undefined &&
910
+ cliNowMs() - context.previousNotifiedAt <=
911
+ this.#dependencies.runtime.approvalTtlMs;
912
+ if (!duplicate) {
913
+ return this.#recordNewApproval(context);
914
+ }
915
+ if (context.conflictingActiveOutbox) {
916
+ return {
917
+ conversation: context.conversation,
918
+ duplicate: false,
919
+ stale: true,
920
+ deferred: true,
921
+ previousApproval: context.previousApproval,
922
+ recorded: undefined
923
+ };
924
+ }
925
+ if (!context.matchingApprovalOutbox) {
926
+ return this.#recoverApprovalOutbox(context);
927
+ }
928
+ return {
929
+ conversation: context.conversation,
930
+ duplicate: true,
931
+ stale: false,
932
+ previousApproval: context.previousApproval,
933
+ recorded: undefined
934
+ };
935
+ }
936
+ #recoverApprovalOutbox(context) {
937
+ const messageId = context.previousCallbackMessageId ?? `msg-${randomUUID()}`;
938
+ const messageTs = nonBlankString(context.previousApproval?.callback_message_ts) ?? nonBlankString(context.previousApproval?.notified_at) ??
939
+ cliNow().toISOString();
940
+ const conversation = context.previousCallbackMessageId
941
+ ? context.conversation
942
+ : {
943
+ ...context.conversation,
944
+ native_session_takeover: {
945
+ ...context.nativeTakeover,
946
+ terminal_bridge_approval: {
947
+ ...context.previousApproval,
948
+ callback_message_id: messageId,
949
+ callback_message_ts: messageTs
950
+ }
951
+ }
952
+ };
953
+ if (!context.previousCallbackMessageId) {
954
+ saveState(context.input.statePath, conversation);
955
+ }
956
+ const recorded = context.input.onRecorded(conversation, {
957
+ recoverMissingOutbox: true
958
+ });
959
+ appendEvent(context.input.logPath, {
960
+ ts: cliNow().toISOString(),
961
+ conversation_id: conversation.conversation_id,
962
+ event: "terminal_bridge_approval_notification_outbox_recovered",
963
+ terminal_control: context.input.terminalControl,
964
+ fingerprint: context.input.fingerprint,
965
+ callback_message_id: messageId
966
+ });
967
+ return {
968
+ conversation: recorded.prepared?.conversation ?? conversation,
969
+ duplicate: false,
970
+ recovered: true,
971
+ stale: false,
972
+ previousApproval: context.previousApproval,
973
+ recorded
974
+ };
975
+ }
976
+ #recordNewApproval(context) {
977
+ const now = cliNow().toISOString();
978
+ const callbackMessageId = `msg-${randomUUID()}`;
979
+ const conversation = {
980
+ ...context.conversation,
981
+ native_session_takeover: {
982
+ ...context.nativeTakeover,
983
+ terminal_bridge_approval: {
984
+ fingerprint: context.input.fingerprint,
985
+ screen_digest: context.approvalScreenDigest,
986
+ notified_at: now,
987
+ terminal_control: context.input.terminalControl,
988
+ approval_state: context.input.terminalStatus.approval_state,
989
+ callback_message_id: callbackMessageId,
990
+ callback_message_ts: now
991
+ }
992
+ },
993
+ updated_at: now
994
+ };
995
+ saveState(context.input.statePath, conversation);
996
+ appendEvent(context.input.logPath, {
997
+ ts: now,
998
+ conversation_id: context.conversation.conversation_id,
999
+ event: "terminal_bridge_approval_notification_recorded",
1000
+ terminal_control: context.input.terminalControl,
1001
+ fingerprint: context.input.fingerprint,
1002
+ screen_digest: context.approvalScreenDigest
1003
+ });
1004
+ const recorded = context.input.onRecorded(conversation);
1005
+ return {
1006
+ conversation: recorded.prepared?.conversation ?? conversation,
1007
+ duplicate: false,
1008
+ stale: false,
1009
+ recorded
1010
+ };
1011
+ }
1012
+ #markApprovalPromptCleared(input) {
1013
+ const storeDir = pathsForConversationDir(path.dirname(input.statePath))
1014
+ .storeDir;
1015
+ return withStoreWriterLease(storeDir, () => {
1016
+ const release = this.#stateFileLock.acquire(`${input.statePath}.lock`);
1017
+ try {
1018
+ const conversation = loadState(input.statePath);
1019
+ const takeover = takeoverFor(conversation);
1020
+ if (!approvalCanBeCleared(conversation, takeover, input)) {
1021
+ return { conversation, marked: false };
1022
+ }
1023
+ const clearedAt = cliNow().toISOString();
1024
+ const nextConversation = {
1025
+ ...conversation,
1026
+ native_session_takeover: {
1027
+ ...takeover,
1028
+ terminal_bridge_last_approval_prompt_cleared_at: clearedAt
1029
+ },
1030
+ updated_at: clearedAt
1031
+ };
1032
+ saveState(input.statePath, nextConversation);
1033
+ appendEvent(input.logPath, {
1034
+ ts: clearedAt,
1035
+ conversation_id: conversation.conversation_id,
1036
+ event: "terminal_bridge_approval_prompt_cleared",
1037
+ terminal_bridge_message_id: input.expectedMessageId
1038
+ });
1039
+ return { conversation: nextConversation, marked: true };
1040
+ }
1041
+ finally {
1042
+ release();
1043
+ }
1044
+ });
1045
+ }
1046
+ #persistActivity(input) {
1047
+ const storeDir = pathsForConversationDir(path.dirname(input.statePath))
1048
+ .storeDir;
1049
+ return withStoreWriterLease(storeDir, () => {
1050
+ const release = this.#stateFileLock.acquire(`${input.statePath}.lock`);
1051
+ try {
1052
+ const current = loadState(input.statePath);
1053
+ if (!isWaitingForAgentStatus(current.status)) {
1054
+ return current;
1055
+ }
1056
+ const expectedTakeover = takeoverFor(input.conversation);
1057
+ const takeover = takeoverFor(current);
1058
+ if (takeover.terminal_bridge_message_id !==
1059
+ expectedTakeover.terminal_bridge_message_id) {
1060
+ return current;
1061
+ }
1062
+ return this.#persistMatchingActivity(input, current, takeover);
1063
+ }
1064
+ finally {
1065
+ release();
1066
+ }
1067
+ });
1068
+ }
1069
+ #persistMatchingActivity(input, current, takeover) {
1070
+ const previousActivityAtMs = validTerminalMonitorTimestampMs(takeover.terminal_bridge_last_activity_at);
1071
+ const observedAt = new Date(input.observedAtMs).toISOString();
1072
+ const inactivityDeadlineAt = Number.isFinite(input.timeoutMinutes) && input.timeoutMinutes > 0
1073
+ ? new Date(input.observedAtMs + input.timeoutMinutes * 60 * 1000).toISOString()
1074
+ : undefined;
1075
+ const nextConversation = {
1076
+ ...current,
1077
+ native_session_takeover: {
1078
+ ...takeover,
1079
+ terminal_bridge_last_activity_at: observedAt,
1080
+ terminal_bridge_last_activity_reason: input.reason,
1081
+ terminal_bridge_inactivity_deadline_at: inactivityDeadlineAt,
1082
+ terminal_bridge_inactivity_timeout_minutes: input.timeoutMinutes,
1083
+ terminal_bridge_hard_timeout_minutes: input.hardTimeoutMinutes
1084
+ },
1085
+ updated_at: observedAt
1086
+ };
1087
+ saveState(input.statePath, nextConversation);
1088
+ appendEvent(input.logPath, {
1089
+ ts: observedAt,
1090
+ conversation_id: current.conversation_id,
1091
+ event: "terminal_bridge_activity_observed",
1092
+ reason: input.reason,
1093
+ last_activity_at: observedAt,
1094
+ terminal_activity_state: input.activityState
1095
+ });
1096
+ if (inactivityDeadlineAt) {
1097
+ appendEvent(input.logPath, {
1098
+ ts: observedAt,
1099
+ conversation_id: current.conversation_id,
1100
+ event: "terminal_bridge_inactivity_deadline_extended",
1101
+ reason: input.reason,
1102
+ previous_last_activity_at: previousActivityAtMs === undefined
1103
+ ? null
1104
+ : new Date(previousActivityAtMs).toISOString(),
1105
+ last_activity_at: observedAt,
1106
+ inactivity_deadline_at: inactivityDeadlineAt,
1107
+ agent_timeout_minutes: input.timeoutMinutes
1108
+ });
1109
+ }
1110
+ return nextConversation;
1111
+ }
1112
+ #persistDetectorDiagnostic(input) {
1113
+ const storeDir = pathsForConversationDir(path.dirname(input.statePath))
1114
+ .storeDir;
1115
+ return withStoreWriterLease(storeDir, () => {
1116
+ const release = this.#stateFileLock.acquire(`${input.statePath}.lock`);
1117
+ try {
1118
+ const conversation = loadState(input.statePath);
1119
+ const takeover = takeoverFor(conversation);
1120
+ if (conversation.conversation_id !== input.expectedConversationId ||
1121
+ nonBlankString(takeover.terminal_bridge_message_id) !==
1122
+ input.expectedMessageId) {
1123
+ return {
1124
+ persisted: false,
1125
+ conversation,
1126
+ reason: "terminal_bridge_task_replaced"
1127
+ };
1128
+ }
1129
+ return this.#persistMatchingDetectorDiagnostic(input, conversation, takeover);
1130
+ }
1131
+ finally {
1132
+ release();
1133
+ }
1134
+ });
1135
+ }
1136
+ #persistMatchingDetectorDiagnostic(input, conversation, takeover) {
1137
+ const existing = isRecord(takeover.terminal_bridge_detector_diagnostic)
1138
+ ? takeover.terminal_bridge_detector_diagnostic
1139
+ : undefined;
1140
+ const now = cliNow().toISOString();
1141
+ const nextDiagnostic = detectorDiagnostic(input, existing, now);
1142
+ if (!nextDiagnostic || sameDetectorDiagnostic(existing, nextDiagnostic)) {
1143
+ return {
1144
+ persisted: false,
1145
+ conversation,
1146
+ diagnostic: existing,
1147
+ reason: "detector_diagnostic_unchanged"
1148
+ };
1149
+ }
1150
+ const nextConversation = {
1151
+ ...conversation,
1152
+ native_session_takeover: {
1153
+ ...takeover,
1154
+ terminal_bridge_detector_diagnostic: nextDiagnostic
1155
+ },
1156
+ updated_at: now
1157
+ };
1158
+ saveState(input.statePath, nextConversation);
1159
+ this.#recordDetectorDiagnostic(input, conversation, nextDiagnostic, now);
1160
+ return {
1161
+ persisted: true,
1162
+ conversation: nextConversation,
1163
+ diagnostic: nextDiagnostic
1164
+ };
1165
+ }
1166
+ #recordDetectorDiagnostic(input, conversation, diagnostic, now) {
1167
+ const event = diagnostic.status === "limited"
1168
+ ? "terminal_bridge_completion_detector_limited"
1169
+ : "terminal_bridge_completion_detector_recovered";
1170
+ appendEvent(input.logPath, {
1171
+ ts: now,
1172
+ conversation_id: conversation.conversation_id,
1173
+ event,
1174
+ terminal_bridge_message_id: input.expectedMessageId,
1175
+ detector_source: diagnostic.source,
1176
+ diagnostic_fingerprint: diagnostic.fingerprint,
1177
+ detail: diagnostic.status === "limited" ? diagnostic.detail : undefined
1178
+ });
1179
+ cliRuntimeLog(diagnostic.status === "limited" ? "warn" : "info", event, {
1180
+ conversation_id: conversation.conversation_id,
1181
+ terminal_bridge_message_id: input.expectedMessageId,
1182
+ detector_source: diagnostic.source,
1183
+ diagnostic_fingerprint: diagnostic.fingerprint
1184
+ });
1185
+ }
1186
+ #markStalled(input) {
1187
+ const storeDir = pathsForConversationDir(path.dirname(input.statePath))
1188
+ .storeDir;
1189
+ let stalledConversation;
1190
+ let stalledMessage;
1191
+ let unchangedConversation;
1192
+ withStoreWriterLease(storeDir, () => {
1193
+ const release = this.#stateFileLock.acquire(`${input.statePath}.lock`);
1194
+ try {
1195
+ const conversation = loadState(input.statePath);
1196
+ if (!isWaitingForAgentStatus(conversation.status)) {
1197
+ cliRuntimeLog("info", "executor_monitor_finished", {
1198
+ conversation_id: conversation.conversation_id,
1199
+ status: conversation.status,
1200
+ reason: "conversation_changed_before_stall"
1201
+ });
1202
+ unchangedConversation = conversation;
1203
+ return;
1204
+ }
1205
+ const stalled = this.#stalledState(conversation, input);
1206
+ stalledConversation = stalled.conversation;
1207
+ stalledMessage = stalled.message;
1208
+ }
1209
+ finally {
1210
+ release();
1211
+ }
1212
+ });
1213
+ if (unchangedConversation) {
1214
+ return unchangedConversation;
1215
+ }
1216
+ if (stalledConversation && stalledMessage) {
1217
+ this.#deliverStalledNotification({
1218
+ statePath: input.statePath,
1219
+ logPath: input.logPath,
1220
+ conversation: stalledConversation,
1221
+ message: stalledMessage
1222
+ });
1223
+ }
1224
+ return stalledConversation;
1225
+ }
1226
+ #stalledState(conversation, input) {
1227
+ const now = cliNow().toISOString();
1228
+ const executor = executorForConversation(conversation);
1229
+ const terminalBridge = terminalBridgeEnabled(conversation);
1230
+ const shouldNotify = Boolean(conversation.gateway_method && !conversation.stalled_notification_sent_at);
1231
+ const message = shouldNotify
1232
+ ? stalledMessage(conversation, input.reason, executor, terminalBridge)
1233
+ : undefined;
1234
+ const nextConversation = {
1235
+ ...conversation,
1236
+ status: "stalled",
1237
+ stalled_at: now,
1238
+ stalled_reason: input.reason,
1239
+ stalled_notification_sent_at: shouldNotify
1240
+ ? now
1241
+ : conversation.stalled_notification_sent_at,
1242
+ stalled_notification_message_id: message?.id ?? conversation.stalled_notification_message_id,
1243
+ updated_at: now
1244
+ };
1245
+ saveState(input.statePath, nextConversation);
1246
+ appendEvent(input.logPath, {
1247
+ ts: now,
1248
+ conversation_id: conversation.conversation_id,
1249
+ event: "conversation_stalled",
1250
+ status: "stalled",
1251
+ reason: input.reason,
1252
+ ...input.detail
1253
+ });
1254
+ cliRuntimeLog("warn", "conversation_stalled", {
1255
+ conversation_id: conversation.conversation_id,
1256
+ agent: executorForConversation(conversation).kind,
1257
+ executor_session: executorForConversation(conversation).session,
1258
+ state_path: input.statePath,
1259
+ event_log_path: input.logPath,
1260
+ reason: input.reason,
1261
+ ...input.detail
1262
+ });
1263
+ return { conversation: nextConversation, message };
1264
+ }
1265
+ #deliverStalledNotification(input) {
1266
+ if (!input.conversation.gateway_method) {
1267
+ return;
1268
+ }
1269
+ const eventPrefix = input.eventPrefix ?? "stalled";
1270
+ const token = input.conversation.gateway_token;
1271
+ const gatewayUrl = token ? input.conversation.gateway_url : undefined;
1272
+ const delivery = this.#dependencies.callbacks.deliverGatewayMethod({
1273
+ method: input.conversation.gateway_method,
1274
+ openclawBin: input.conversation.openclaw_bin,
1275
+ gatewayUrl,
1276
+ token,
1277
+ sessionKey: input.conversation.gateway_session ??
1278
+ input.conversation.openclaw_session,
1279
+ statePath: input.statePath,
1280
+ logPath: input.logPath,
1281
+ conversation: input.conversation,
1282
+ message: input.message
1283
+ });
1284
+ recordStalledDelivery(input, eventPrefix, "gateway_method_delivery", delivery, true);
1285
+ if (delivery.status !== 0) {
1286
+ return;
1287
+ }
1288
+ const payload = parseOptionalJson(delivery.stdout);
1289
+ const chatSend = isRecord(payload?.chat_send)
1290
+ ? payload.chat_send
1291
+ : undefined;
1292
+ if (!chatSend) {
1293
+ return;
1294
+ }
1295
+ const chatDelivery = this.#dependencies.callbacks.deliverChatSend({
1296
+ openclawBin: input.conversation.openclaw_bin,
1297
+ gatewayUrl,
1298
+ token,
1299
+ params: chatSend
1300
+ });
1301
+ recordStalledDelivery(input, eventPrefix, "chat_send_delivery", chatDelivery, false);
1302
+ }
1303
+ }
1304
+ function monitorRuntimePort() {
1305
+ return {
1306
+ now: cliNow,
1307
+ nowMs: cliNowMs,
1308
+ pid: cliPid,
1309
+ sleep: cliSleepSync,
1310
+ log: cliRuntimeLog,
1311
+ exitAfterApprovalCallback: () => cliEnv().AKK_TEST_EXIT_AFTER_APPROVAL_CALLBACK_DELIVERED === "1",
1312
+ exit: cliExit
1313
+ };
1314
+ }
1315
+ function takeoverFor(conversation) {
1316
+ return isRecord(conversation.native_session_takeover)
1317
+ ? conversation.native_session_takeover
1318
+ : {};
1319
+ }
1320
+ function unprepared(reason) {
1321
+ return { prepared: false, alreadyRunning: false, reason };
1322
+ }
1323
+ function approvalCanBeCleared(conversation, takeover, input) {
1324
+ return conversation.conversation_id === input.expectedConversationId &&
1325
+ conversation.status === "waiting_for_agent" &&
1326
+ takeover.terminal_bridge === true &&
1327
+ nonBlankString(takeover.terminal_bridge_message_id) ===
1328
+ input.expectedMessageId &&
1329
+ nonBlankString(takeover.terminal_bridge_last_approval_message_id) ===
1330
+ input.expectedMessageId &&
1331
+ validTerminalMonitorTimestampMs(takeover.terminal_bridge_approval_resolved_at) !== undefined &&
1332
+ validTerminalMonitorTimestampMs(takeover.terminal_bridge_last_approval_prompt_cleared_at) === undefined;
1333
+ }
1334
+ function detectorDiagnostic(input, existing, now) {
1335
+ if (input.limitation && input.fingerprint) {
1336
+ return {
1337
+ status: "limited",
1338
+ source: "terminal_completion_detector",
1339
+ fingerprint: input.fingerprint,
1340
+ detail: truncateText(redactString(input.limitation), 1000),
1341
+ observed_at: now
1342
+ };
1343
+ }
1344
+ return existing && nonBlankString(existing.status) === "limited"
1345
+ ? { ...existing, status: "recovered", recovered_at: now }
1346
+ : undefined;
1347
+ }
1348
+ function sameDetectorDiagnostic(existing, next) {
1349
+ return nonBlankString(existing?.status) === nonBlankString(next.status) &&
1350
+ nonBlankString(existing?.fingerprint) === nonBlankString(next.fingerprint);
1351
+ }
1352
+ function terminalBridgeApprovalInstructions(input) {
1353
+ const approval = isRecord(input.terminalStatus.approval_state)
1354
+ ? input.terminalStatus.approval_state
1355
+ : {};
1356
+ const screen = isRecord(input.terminalStatus.screen)
1357
+ ? input.terminalStatus.screen
1358
+ : {};
1359
+ const executor = executorForConversation(input.conversation);
1360
+ const agentName = executorDefinitionForKind(executor.kind).displayName;
1361
+ const label = nonBlankString(approval.label) ??
1362
+ `the current ${agentName} approval prompt`;
1363
+ const keys = Array.isArray(approval.keys)
1364
+ ? approval.keys.filter((value) => typeof value === "string")
1365
+ : [];
1366
+ const decisionMode = nonBlankString(approval.decision_mode);
1367
+ const keyDescription = keys.length > 0
1368
+ ? keys.join(" then ")
1369
+ : nonBlankString(approval.key) ?? "the detected approve key sequence";
1370
+ const promptKind = nonBlankString(approval.prompt_kind);
1371
+ const command = nonBlankString(approval.command);
1372
+ const toolName = nonBlankString(approval.tool_name);
1373
+ const requestDetail = nonBlankString(approval.request_detail);
1374
+ const requestId = nonBlankString(approval.request_id);
1375
+ const excerpt = nonBlankString(screen.excerpt) ??
1376
+ "(No terminal excerpt was available.)";
1377
+ const directReview = executor.kind === "claude" && decisionMode === "keys";
1378
+ return approvalInstructionLines({
1379
+ ...input,
1380
+ approval,
1381
+ agentName,
1382
+ label,
1383
+ keyDescription,
1384
+ promptKind,
1385
+ command,
1386
+ toolName,
1387
+ requestDetail,
1388
+ requestId,
1389
+ excerpt,
1390
+ directReview
1391
+ }).filter((line) => line !== undefined).join("\n");
1392
+ }
1393
+ function approvalInstructionLines(input) {
1394
+ const turnId = turnIdForConversation(input.conversation);
1395
+ return [
1396
+ `${input.agentName} is waiting for approval in a terminal-controlled AKK session.`,
1397
+ "",
1398
+ `Turn: ${turnId}`,
1399
+ `Terminal: ${input.terminalControl.kind}:${input.terminalControl.target}`,
1400
+ `Approval option: ${input.label} (${input.keyDescription})`,
1401
+ input.promptKind ? `Request kind: ${input.promptKind}` : undefined,
1402
+ input.toolName ? `Tool: ${input.toolName}` : undefined,
1403
+ input.requestDetail ? `Request: ${input.requestDetail}` : undefined,
1404
+ input.command ? `Command: ${input.command}` : undefined,
1405
+ input.requestId ? `Request id: ${input.requestId}` : undefined,
1406
+ "",
1407
+ "Safe terminal excerpt:",
1408
+ "```text",
1409
+ input.excerpt,
1410
+ "```",
1411
+ "",
1412
+ input.directReview
1413
+ ? `Before asking for approval, have the user personally inspect the live ${input.terminalControl.kind} pane ${input.terminalControl.target}.`
1414
+ : undefined,
1415
+ input.directReview
1416
+ ? "This hookless callback intentionally omits raw command details; do not approve from the summary alone."
1417
+ : undefined,
1418
+ input.directReview ? "" : undefined,
1419
+ `Ask the user whether to approve or deny this ${input.agentName} request.`,
1420
+ "",
1421
+ "If the user approves, call `agent_knock_knock_approve` with only:",
1422
+ `- turn_id: ${turnId}`,
1423
+ "",
1424
+ "If the user denies or wants to stop this request, call `agent_knock_knock_cancel` with:",
1425
+ `- turn_id: ${turnId}`,
1426
+ "",
1427
+ "Do not use raw tmux, shell, or manual key presses for this approval. Do not approve without explicit user confirmation."
1428
+ ];
1429
+ }
1430
+ function stalledMessage(conversation, reason, executor, terminalBridge) {
1431
+ return createMessage({
1432
+ conversation,
1433
+ from: executor.actor,
1434
+ to: "openclaw",
1435
+ type: "error",
1436
+ requiresResponse: false,
1437
+ body: [
1438
+ `AKK marked this ${executor.display_name} task as stalled: ${reason}.`,
1439
+ "",
1440
+ `Turn: ${turnIdForConversation(conversation)}`,
1441
+ `AKK session: ${sessionIdForConversation(conversation)}`,
1442
+ `Agent session: ${executor.session}`,
1443
+ terminalBridge
1444
+ ? `Use \`AKK status --turn ${turnIdForConversation(conversation)}\` for details, \`AKK renew --turn ${turnIdForConversation(conversation)}\` to resume monitoring in this Turn, or \`AKK close --turn ${turnIdForConversation(conversation)}\` to close it. Start any independent retry with \`AKK send --session ${sessionIdForConversation(conversation)}\`.`
1445
+ : `Use \`AKK status --turn ${turnIdForConversation(conversation)}\` for details or \`AKK close --turn ${turnIdForConversation(conversation)}\` to close this Turn.`
1446
+ ].join("\n")
1447
+ });
1448
+ }
1449
+ function recordStalledDelivery(input, eventPrefix, suffix, delivery, includeMethod) {
1450
+ appendEvent(input.logPath, {
1451
+ ts: cliNow().toISOString(),
1452
+ conversation_id: input.conversation.conversation_id,
1453
+ event: `${eventPrefix}_${suffix}`,
1454
+ ...(includeMethod ? { method: input.conversation.gateway_method } : {}),
1455
+ message_id: input.message.id,
1456
+ status: delivery.status,
1457
+ stdout: redactString(delivery.stdout),
1458
+ stderr: redactString(delivery.stderr)
1459
+ });
1460
+ cliRuntimeLog("info", `${eventPrefix}_${suffix}`, {
1461
+ conversation_id: input.conversation.conversation_id,
1462
+ ...(includeMethod ? { method: input.conversation.gateway_method } : {}),
1463
+ message_id: input.message.id,
1464
+ status: delivery.status,
1465
+ failure_kind: classifyCallbackProcessFailure(delivery),
1466
+ stdout: textSummary(delivery.stdout),
1467
+ stderr: textSummary(delivery.stderr)
1468
+ });
1469
+ }
1470
+ function parseOptionalJson(text) {
1471
+ try {
1472
+ const parsed = JSON.parse(String(text));
1473
+ return isRecord(parsed) ? parsed : undefined;
1474
+ }
1475
+ catch {
1476
+ return undefined;
1477
+ }
1478
+ }
1479
+ function textSummary(text, maxLength = 240) {
1480
+ const value = String(text ?? "");
1481
+ return {
1482
+ length: value.length,
1483
+ preview: value ? value.slice(0, maxLength) : undefined
1484
+ };
1485
+ }
1486
+ function truncateText(value, maxLength) {
1487
+ const text = String(value ?? "").replace(/\s+/g, " ").trim();
1488
+ return text.length <= maxLength
1489
+ ? text
1490
+ : `${text.slice(0, Math.max(0, maxLength - 3))}...`;
1491
+ }
1492
+ //# sourceMappingURL=terminal-monitor-state-cli-adapter.js.map