@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,2444 @@
1
+ // Infrastructure composition for terminal list discovery and selector projection.
2
+ import { AsyncLocalStorage } from "node:async_hooks";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { listDeferredForegroundTransfers } from "./deferred-foreground-transfer.js";
6
+ import { isFinalDeferredForegroundTransferStatus } from "./deferred-foreground-transfer-policy.js";
7
+ import { humanObservedHandoffBindingToken, isExactNativeThreadId, managedSessionBindingToken, managedSessionRevision, unmanagedTerminalBindingToken } from "./managed-session.js";
8
+ import { executorForConversation, isSessionSendBlockingStatus, isTerminalDispatchOwnerReleasedStatus, resolveExecutor, sessionIdForConversation, turnIdForConversation } from "./protocol.js";
9
+ import { resolveSessionSelector, sessionShortRef } from "./session-selector.js";
10
+ import { projectSessionSelectorCandidate } from "./terminal-selector-projection-service.js";
11
+ import { listManagedSessions, loadNativeThreadTransition, nativeThreadTransitionsDir, tryLoadManagedSession } from "./session-store.js";
12
+ import { defaultStoreDir, ensureStoreWritable, inspectStoreCompatibility, listConversations, STORE_SESSION_AUTHORITY_PROTOCOL } from "./store.js";
13
+ import { exactCodexReadyStyledComposerCapture } from "./terminal-agent-bridge.js";
14
+ import { captureCodexHumanStartedActiveTaskAnchor } from "./terminal-submission-acceptance.js";
15
+ import { applySessionAuthorityToDispatch, authoritativeTerminalIdentity, compareManagedConversationRecency, decideManagedTerminalAssociation, decideLocalTerminalDispatchOwnership, decideTerminalSendAuthority, decideTerminalSessionAuthorityConflict, managedTurnNeedsAttention as terminalManagedTurnNeedsAttention, nonOwnerTerminalActions, projectBlockingTurn, projectHandoffDecision, projectHandoffPresentation, projectPublicManagementConflict, projectReconcileBindingAction, projectTerminalDispatchConflict, projectTerminalManagement, selectManagedTerminalHistory, selectTerminalAvailableActions } from "./terminal-action-projection.js";
16
+ import { childProcessIdsForRoot, decideManagedBindingConflict, deferredCodexForegroundBindingToken, exactBoundCodexSendSource, isCompleteNativeRollout, nativeAgentIdentityMatchesTurn, processIncarnationRelationship, selectRootTerminalProcesses, terminalControlAliasMatches, terminalControlsShareIncarnation, verifiedEmptyCodexHandoffToken } from "./terminal-authority-policy.js";
17
+ import { decideTerminalBindingMatch, terminalObservationFromListEntry } from "./terminal-binding-authority.js";
18
+ import { decideTerminalScopedCodexApprovalAuthority, terminalScopedCodexApprovalPromptSnapshot } from "./terminal-scoped-approval-authority.js";
19
+ import { sameCanonicalStatePath } from "./terminal-dispatch-ledger-codec.js";
20
+ import * as dispatch from "./terminal-dispatch-policy.js";
21
+ import { actionsForManagedSessionBinding, currentTerminalActions, listActionContracts, readOnlyListActions, readOnlyManagedTurn, renderAvailableListActions, renderCurrentManagedTurn, renderHistoricalManagedTurn, renderManagedTurnListEntry, safeUnavailableManagedTurnActions, sendActionForManagedSession, withoutGenericHandoffSourceClose } from "./terminal-list-renderer.js";
22
+ import { hasCanonicalTerminalEndpoint, terminalControlEvidenceMatches } from "./terminal-control-ref.js";
23
+ import { validTerminalMonitorTimestampMs as validTimestampMs } from "./terminal-monitor-decision-policy.js";
24
+ import { isRecord, nonBlankString as stringValue } from "./value-guards.js";
25
+ import { expandHome, writeCliJson as printJson } from "./cli-command-runtime.js";
26
+ import { cliCwd, cliNowMs, cliRuntimeLog as runtimeLog } from "./cli-runtime-context.js";
27
+ const terminalListRuntimeContext = new AsyncLocalStorage();
28
+ function terminalListRuntime() {
29
+ const runtime = terminalListRuntimeContext.getStore();
30
+ if (!runtime) {
31
+ throw new Error("Terminal list facade runtime is unavailable");
32
+ }
33
+ return runtime;
34
+ }
35
+ function withTerminalListRuntime(runtime, operation) {
36
+ return terminalListRuntimeContext.run(runtime, operation);
37
+ }
38
+ export function createTerminalListCliFacade(dependencies) {
39
+ const runtime = {
40
+ ...dependencies.reconciliation,
41
+ ...dependencies.discovery,
42
+ ...dependencies.store,
43
+ ...dependencies.authority,
44
+ ...dependencies.policy
45
+ };
46
+ const call = (operation) => withTerminalListRuntime(runtime, operation);
47
+ return {
48
+ runList: (options) => call(() => runList(options)),
49
+ buildTerminalListGroup: (request) => call(() => buildTerminalListGroup(request)),
50
+ observeExactTerminal: (request) => call(() => observeExactTerminal(request)),
51
+ provisionalManagedBindingTurnCount: (storeDir, session) => call(() => provisionalManagedBindingTurnCount(storeDir, session)),
52
+ managedSessionHasUnresolvedNativeTransition: (storeDir, session) => call(() => managedSessionHasUnresolvedNativeTransition(storeDir, session)),
53
+ managedSessionHasAnyNativeTransition: (storeDir, session) => call(() => managedSessionHasAnyNativeTransition(storeDir, session)),
54
+ terminalControlForManagedConversation: (conversation) => call(() => terminalControlForManagedConversation(conversation)),
55
+ terminalIncarnationBlockingTurns: (storeDir, terminalControl) => call(() => terminalIncarnationBlockingTurns(storeDir, terminalControl)),
56
+ managedTurnNeedsAttention: (conversation) => call(() => managedTurnNeedsAttention(conversation)),
57
+ assertTerminalIncarnationCanStartTurn: (storeDir, terminalControl) => call(() => assertTerminalIncarnationCanStartTurn(storeDir, terminalControl)),
58
+ terminalDispatchOwnership: (terminalControl) => call(() => terminalDispatchOwnership(terminalControl)),
59
+ resolveTerminalScopedCodexApproval: (request) => call(() => resolveTerminalScopedCodexApproval(request)),
60
+ resolveConversationSelectorOption: (commandName, options) => call(() => resolveConversationSelectorOption(commandName, options))
61
+ };
62
+ }
63
+ function projectSelectorCandidate(entry, commandName, observedAtMs, options) {
64
+ return projectSessionSelectorCandidate(entry, commandName, observedAtMs, options, { isActiveStatus: terminalListRuntime().isActiveStatus });
65
+ }
66
+ async function runList(options) {
67
+ const storeDir = expandHome(options.storeDir ?? options.logDir ?? defaultStoreDir(cliCwd()));
68
+ const store = inspectStoreCompatibility(storeDir);
69
+ const reconciliation = options.reconcile === true
70
+ ? await reconcileStoreForList(storeDir, options)
71
+ : {
72
+ status: "disabled",
73
+ reason: "standalone list is read-only unless --reconcile is supplied"
74
+ };
75
+ const agentFilter = options.agent ? resolveExecutor({ kind: options.agent }).kind : undefined;
76
+ const statusFilter = options.status;
77
+ const terminalScan = await buildTerminalListGroup({ options, agentFilter, statusFilter });
78
+ const projected = projectTerminalListScan({
79
+ options,
80
+ storeDir,
81
+ store,
82
+ terminalScan,
83
+ agentFilter,
84
+ statusFilter
85
+ });
86
+ printJson({
87
+ store_dir: storeDir,
88
+ store,
89
+ reconciliation,
90
+ action_contracts: listActionContracts(),
91
+ terminals: projected.terminals,
92
+ terminal_watches: projected.terminalWatches,
93
+ unavailable_managed_turns: projected.unavailableManagedTurns,
94
+ terminal_scan: {
95
+ ...terminalScan.summary,
96
+ terminal_count: projected.terminals.length
97
+ }
98
+ });
99
+ runtimeLog("info", "terminals_listed", {
100
+ store_dir: storeDir,
101
+ terminal_count: projected.terminals.length,
102
+ unavailable_managed_turn_count: projected.unavailableManagedTurns.length,
103
+ terminal_scan_error: terminalScan.summary.error,
104
+ include_all: projected.includeAll,
105
+ agent_filter: agentFilter,
106
+ status_filter: statusFilter,
107
+ reconciliation
108
+ });
109
+ }
110
+ function projectTerminalListScan(input) {
111
+ const { options, storeDir, store, terminalScan, agentFilter, statusFilter, tolerateInvalidWatchRecords } = input;
112
+ const includeAll = Boolean(options.all);
113
+ const allManagedConversations = listConversations(storeDir)
114
+ .filter(terminalListRuntime().isDiscoverableTmuxConversation);
115
+ const managedSessions = store.readable
116
+ ? listManagedSessions(storeDir)
117
+ : [];
118
+ const displayedConversations = allManagedConversations
119
+ .filter((conversation) => includeAll || terminalListRuntime().isActiveStatus(conversation.status))
120
+ .filter((conversation) => terminalListRuntime().matchesConfiguredWorkspace(options.workspace, conversation.workspace))
121
+ .filter((conversation) => !agentFilter || executorForConversation(conversation).kind === agentFilter)
122
+ .filter((conversation) => !statusFilter || conversation.status === statusFilter);
123
+ const workspaceConversations = allManagedConversations.filter((conversation) => terminalListRuntime().matchesConfiguredWorkspace(options.workspace, conversation.workspace));
124
+ const physicalTerminals = terminalScan.terminalControlled.filter((entry) => terminalListRuntime().matchesConfiguredWorkspace(options.workspace, entry.workspace ?? entry.cwd));
125
+ const projection = terminalFirstListProjection({
126
+ storeDir,
127
+ terminals: physicalTerminals,
128
+ managedSessions,
129
+ sessionAuthorityRequired: Number(store.writer_protocol) >= STORE_SESSION_AUTHORITY_PROTOCOL,
130
+ allConversations: workspaceConversations,
131
+ displayedConversations,
132
+ includeAll,
133
+ managedOnly: options.managedOnly === true,
134
+ statusFilter,
135
+ mutationsAllowed: store.writable === true
136
+ });
137
+ const exactWatchScan = terminalListRuntime()
138
+ .scanTerminalWatchesForExactObservation;
139
+ const watchObservation = tolerateInvalidWatchRecords && exactWatchScan
140
+ ? exactWatchScan(storeDir, { includeAll })
141
+ : {
142
+ watches: terminalListRuntime().listTerminalWatches?.(storeDir, { includeAll }) ?? [],
143
+ activeOverlayTrusted: true
144
+ };
145
+ const observedTerminalWatches = watchObservation.watches;
146
+ const terminalWatches = options.managedOnly
147
+ ? []
148
+ : observedTerminalWatches
149
+ .filter((watch) => !agentFilter || watch.agent === agentFilter)
150
+ .filter((watch) => terminalListRuntime().matchesConfiguredWorkspace(options.workspace, watch.workspace))
151
+ .filter((watch) => !statusFilter || watch.status === statusFilter);
152
+ const activeWatchedTerminals = new Set(observedTerminalWatches
153
+ .filter((watch) => watch.status === "active")
154
+ .map((watch) => stringValue(watch.terminal_id))
155
+ .filter((terminalId) => terminalId !== undefined));
156
+ const terminals = projection.terminals.map((terminal) => !watchObservation.activeOverlayTrusted ||
157
+ activeWatchedTerminals.has(stringValue(terminal.id) ?? "")
158
+ ? withoutTerminalWatchAuthority(terminal)
159
+ : terminal);
160
+ return {
161
+ includeAll,
162
+ terminals,
163
+ terminalWatches,
164
+ unavailableManagedTurns: projection.unavailableManagedTurns
165
+ };
166
+ }
167
+ async function observeExactTerminal(request) {
168
+ const { options, terminalId } = request;
169
+ const storeDir = expandHome(options.storeDir ?? options.logDir ?? defaultStoreDir(cliCwd()));
170
+ const store = inspectStoreCompatibility(storeDir);
171
+ const scan = await buildTerminalListGroup({ options, terminalId });
172
+ const matches = scan.terminalControlled.filter((terminal) => stringValue(terminal.id) === terminalId);
173
+ if (matches.length !== 1) {
174
+ return scan.summary.error
175
+ ? {
176
+ state: "unavailable",
177
+ reason: scan.summary.error,
178
+ summary: scan.summary
179
+ }
180
+ : { state: "absent", summary: scan.summary };
181
+ }
182
+ const projected = projectTerminalListScan({
183
+ options,
184
+ storeDir,
185
+ store,
186
+ terminalScan: scan,
187
+ tolerateInvalidWatchRecords: true
188
+ });
189
+ const projectedMatches = projected.terminals.filter((terminal) => stringValue(terminal.id) === terminalId);
190
+ if (projectedMatches.length !== 1) {
191
+ return {
192
+ state: "unavailable",
193
+ reason: "the exact terminal could not be projected authoritatively",
194
+ summary: scan.summary
195
+ };
196
+ }
197
+ return {
198
+ state: "available",
199
+ rawTerminal: matches[0],
200
+ terminal: projectedMatches[0],
201
+ summary: scan.summary
202
+ };
203
+ }
204
+ function withoutAvailableAction(terminal, action) {
205
+ if (!isRecord(terminal.available_actions))
206
+ return terminal;
207
+ const actions = { ...terminal.available_actions };
208
+ delete actions[action];
209
+ return { ...terminal, available_actions: actions };
210
+ }
211
+ function withoutTerminalWatchAuthority(terminal) {
212
+ const projected = withoutAvailableAction(terminal, "watch");
213
+ if (!Object.hasOwn(projected, "terminal_watch_hint"))
214
+ return projected;
215
+ const safe = { ...projected };
216
+ delete safe.terminal_watch_hint;
217
+ return safe;
218
+ }
219
+ async function reconcileStoreForList(storeDir, options) {
220
+ try {
221
+ ensureStoreWritable(storeDir);
222
+ }
223
+ catch (error) {
224
+ if (isRecord(error) && error.code === "AKK_STORE_INCOMPATIBLE") {
225
+ return {
226
+ status: "skipped",
227
+ reason: error instanceof Error ? error.message : String(error),
228
+ store: inspectStoreCompatibility(storeDir)
229
+ };
230
+ }
231
+ throw error;
232
+ }
233
+ const monitors = await terminalListRuntime().reconcileMonitors(options, {
234
+ includeCallbackRecovery: false,
235
+ reason: "list_reconciliation",
236
+ conversationId: undefined
237
+ });
238
+ const idle = terminalListRuntime().reconcileIdleConversations(storeDir, options);
239
+ return {
240
+ status: "completed",
241
+ checked: Math.max(idle.checked, monitors.checked),
242
+ changed: idle.closed + monitors.launched + monitors.repaired,
243
+ closed: idle.closed,
244
+ repaired: monitors.repaired,
245
+ collateral_stalls_checked: monitors.collateral_stalls_checked,
246
+ collateral_stalls_skipped: monitors.collateral_stalls_skipped,
247
+ collateral_stall_repairs: monitors.items.filter((item) => item.status === "repaired"),
248
+ monitors_launched: monitors.launched,
249
+ monitors_already_running: monitors.already_running,
250
+ skipped: idle.skipped + monitors.skipped,
251
+ errors: monitors.errors,
252
+ idle_timeout_minutes: idle.idle_timeout_minutes
253
+ };
254
+ }
255
+ async function buildTerminalListGroup({ options, agentFilter, statusFilter, terminalId }) {
256
+ const empty = {
257
+ terminalControlled: [],
258
+ summary: {
259
+ enabled: false,
260
+ agents: [],
261
+ error: undefined
262
+ }
263
+ };
264
+ if (options.managedOnly) {
265
+ return empty;
266
+ }
267
+ const registry = terminalListRuntime().createRuntimeTerminalAgentRegistry(options);
268
+ const adapters = agentFilter
269
+ ? [registry.get(agentFilter)].filter((adapter) => adapter !== undefined)
270
+ : registry.list();
271
+ if (agentFilter && adapters.length === 0) {
272
+ return {
273
+ ...empty,
274
+ summary: {
275
+ enabled: true,
276
+ agents: [],
277
+ skipped: `terminal agent adapter is not registered for ${agentFilter}`
278
+ }
279
+ };
280
+ }
281
+ const terminalProvider = terminalListRuntime().createTerminalControlProvider(options);
282
+ const bridge = terminalListRuntime().createTerminalAgentBridge(options, terminalProvider, registry);
283
+ const terminalDiagnostics = options.terminalDebug
284
+ ? await terminalControlDiagnostics(terminalProvider)
285
+ : undefined;
286
+ const terminalControlled = [];
287
+ let activeCount = 0;
288
+ const errors = [];
289
+ try {
290
+ const processSource = terminalListRuntime().createTerminalProcessSource(options);
291
+ const snapshots = await processSource.listProcessSnapshots((snapshot) => adapters.some((adapter) => adapter.capabilities.processDiscovery && adapter.classifyProcess(snapshot) !== undefined), { includeAncestors: true });
292
+ const activeSessions = await bridge.listProcesses(snapshots, adapters.map((adapter) => adapter.agent));
293
+ const rootSessions = selectRootTerminalProcesses(activeSessions);
294
+ const controlledSessions = rootSessions.filter((session) => session.terminalControl !== undefined);
295
+ activeCount = controlledSessions.length;
296
+ const selectedSessions = terminalId
297
+ ? controlledSessions.filter((session) => bridge.terminalConversationId(session) === terminalId)
298
+ : controlledSessions;
299
+ for (const session of selectedSessions) {
300
+ try {
301
+ terminalControlled.push(await terminalControlledListEntry(session, activeSessions, options, bridge));
302
+ }
303
+ catch (error) {
304
+ errors.push(`terminal process ${session.pid}: ` +
305
+ (error instanceof Error ? error.message : String(error)));
306
+ }
307
+ }
308
+ }
309
+ catch (error) {
310
+ errors.push(error instanceof Error ? error.message : String(error));
311
+ }
312
+ return {
313
+ terminalControlled,
314
+ summary: {
315
+ enabled: true,
316
+ agents: adapters.map((adapter) => adapter.agent),
317
+ active_count: activeCount,
318
+ terminal_count: terminalControlled.length,
319
+ approval_scan: options.noApprovalScan ? "disabled" : "enabled",
320
+ diagnostics: terminalDiagnostics,
321
+ error: errors.length > 0 ? errors.join("; ") : undefined
322
+ }
323
+ };
324
+ }
325
+ async function terminalControlDiagnostics(provider) {
326
+ return provider.diagnostics();
327
+ }
328
+ function managedTurnListEntry(task, { terminalBridge = false, approvalState, conversation } = {}) {
329
+ return renderManagedTurnListEntry(task, {
330
+ terminalBridge,
331
+ approvalState,
332
+ actionFacts: managedTurnListActionFacts(task, conversation)
333
+ });
334
+ }
335
+ function managedTurnListActionFacts(task, conversation) {
336
+ const nativeTakeover = isRecord(conversation?.native_session_takeover)
337
+ ? conversation.native_session_takeover
338
+ : undefined;
339
+ const managedApprovalPending = isRecord(nativeTakeover?.terminal_bridge_approval);
340
+ const terminalBridgeReady = Boolean(conversation &&
341
+ terminalListRuntime().terminalBridgeEnabled(conversation) &&
342
+ terminalListRuntime().terminalControlFromTakeover(nativeTakeover) !== undefined);
343
+ const renewEligible = Boolean(terminalBridgeReady &&
344
+ task.status === "stalled" &&
345
+ terminalListRuntime().terminalBridgeSubmission(conversation)?.status !== "uncertain" &&
346
+ !terminalListRuntime().isVerifiedDeadTerminalAgentProcess(conversation ?? {}));
347
+ const callbackDelivery = isRecord(conversation?.callback_delivery)
348
+ ? conversation.callback_delivery
349
+ : undefined;
350
+ const retryCallbackEligible = Boolean(conversation &&
351
+ conversation.legacy_callback_status_error === undefined &&
352
+ terminalListRuntime().callbackRetryDisposition(callbackDelivery).state ===
353
+ "retryable");
354
+ return {
355
+ terminalBridgeReady,
356
+ managedApprovalPending,
357
+ renewEligible,
358
+ retryCallbackEligible
359
+ };
360
+ }
361
+ async function terminalControlledListEntry(session, activeSessions, options, bridge = terminalListRuntime().createTerminalAgentBridge(options)) {
362
+ const terminalControl = session.terminalControl;
363
+ if (!terminalControl) {
364
+ throw new Error(`process ${session.pid} is not terminal-controlled`);
365
+ }
366
+ const terminalState = await listStateForTerminal(session.agent, terminalControl, options, bridge, {
367
+ pid: session.pid,
368
+ cwd: session.cwd,
369
+ // Raw Codex status and approval use the exact full terminal selector as
370
+ // their runtime conversation identity. Use the same identity while
371
+ // projecting list actions so a prompt-bound terminal token can be
372
+ // revalidated by the later status/approve calls.
373
+ ...(session.agent === "codex"
374
+ ? { conversationId: bridge.terminalConversationId(session) }
375
+ : { sessionId: session.sessionId }),
376
+ terminalTarget: terminalControl.target
377
+ });
378
+ const { orphanedDispatch, nativeIdentityObservation, nativeAgentIdentity, codexOpenRootRolloutInventory, nativeProcessUuid, nativeProcessBirth, nativeProcessEvidence } = await observeTerminalNativeListIdentity(session, terminalControl, options, bridge);
379
+ const effectiveTerminalState = effectiveTerminalListState({
380
+ session,
381
+ terminalState,
382
+ nativeIdentityObservation,
383
+ nativeAgentIdentity,
384
+ nativeProcessUuid,
385
+ nativeProcessBirth
386
+ });
387
+ const terminalStatusSnapshot = effectiveTerminalState._terminal_status_snapshot
388
+ ? terminalBridgeStatusWithActivity(effectiveTerminalState._terminal_status_snapshot, effectiveTerminalState.activity_state, effectiveTerminalState.activity_reason)
389
+ : undefined;
390
+ const statusCardObservation = session.agent === "codex" &&
391
+ typeof effectiveTerminalState.screen_excerpt === "string"
392
+ ? bridge.registry.require("codex").observeThreadLifecycle?.({
393
+ operation: { kind: "new_thread" },
394
+ phase: "before",
395
+ screen: effectiveTerminalState.screen_excerpt
396
+ })
397
+ : undefined;
398
+ const statusCardNativeThreadId = statusCardObservation?.status === "observed" &&
399
+ effectiveTerminalState.activity_state === "idle" &&
400
+ effectiveTerminalState.approval_state.blocked !== true &&
401
+ isExactNativeThreadId(statusCardObservation.nativeThreadId)
402
+ ? statusCardObservation.nativeThreadId
403
+ : undefined;
404
+ const agentVersion = terminalListRuntime().agentVersionForRunningProcess(session.agent, session.pid, options);
405
+ const lifecycleCapability = bridge.registry.require(session.agent)
406
+ .probeThreadLifecycle?.(agentVersion) ?? {
407
+ status: "unsupported",
408
+ agentVersion,
409
+ newThread: false,
410
+ resumeExact: false,
411
+ reason: "native thread lifecycle is unavailable"
412
+ };
413
+ const nativeInspectionCapability = bridge.registry.require(session.agent)
414
+ .probeNativeInspection?.(agentVersion) ?? {
415
+ status: "unsupported",
416
+ agentVersion,
417
+ statusInspection: false,
418
+ reason: "native inspection is unavailable"
419
+ };
420
+ const codexLatentClearResumeObservationValue = session.agent === "codex"
421
+ ? terminalListRuntime().codexLatentClearResumeObservation({
422
+ screen: effectiveTerminalState.screen_excerpt,
423
+ agentVersion
424
+ })
425
+ : undefined;
426
+ const lifecycleBindingToken = unmanagedTerminalBindingToken({
427
+ terminalId: bridge.terminalConversationId(session),
428
+ terminalControl,
429
+ agent: session.agent,
430
+ pid: session.pid,
431
+ workspace: session.cwd ?? terminalControl.currentPath ?? cliCwd(),
432
+ nativeThreadId: nativeAgentIdentity?.sessionId,
433
+ processUuid: nativeProcessUuid,
434
+ processBirth: nativeProcessBirth,
435
+ rollout: nativeAgentIdentity?.rollout
436
+ });
437
+ const codexLifecycleIncarnationAvailable = session.agent !== "codex" ||
438
+ Boolean(nativeProcessUuid && nativeProcessBirth);
439
+ const terminalHasBlockingTurn = terminalIncarnationBlockingTurns(terminalListRuntime().storeDirFromOptions(options), terminalControl).length > 0;
440
+ const automatedInputComposerReady = await observeAutomatedInputComposerReady({
441
+ session,
442
+ terminalControl,
443
+ terminalState: effectiveTerminalState,
444
+ nativeIdentityObservation,
445
+ codexOpenRootRolloutInventory,
446
+ options
447
+ });
448
+ const entry = {
449
+ id: bridge.terminalConversationId(session),
450
+ short_ref: sessionShortRef(bridge.terminalConversationId(session)),
451
+ source: "terminal",
452
+ agent: session.agent,
453
+ process_state: "active",
454
+ pid: session.pid,
455
+ child_pids: childProcessIdsForRoot(session, activeSessions),
456
+ command: session.command,
457
+ cwd: session.cwd,
458
+ workspace: session.cwd,
459
+ elapsed: session.elapsed,
460
+ native_agent_session_id: nativeAgentIdentity?.sessionId,
461
+ native_agent_status_card_session_id: statusCardNativeThreadId,
462
+ native_agent_process_uuid: nativeProcessUuid,
463
+ native_agent_process_birth: nativeProcessBirth,
464
+ native_agent_rollout: nativeAgentIdentity?.rollout,
465
+ native_agent_identity_evidence: nativeProcessEvidence,
466
+ native_agent_identity_observation: {
467
+ status: nativeIdentityObservation.status,
468
+ ...(nativeIdentityObservation.status === "unavailable"
469
+ ? { reason: nativeIdentityObservation.reason }
470
+ : nativeIdentityObservation.status === "verified_absent"
471
+ ? { evidence: nativeIdentityObservation.evidence }
472
+ : {})
473
+ },
474
+ agent_version: agentVersion,
475
+ native_thread_lifecycle: lifecycleCapability,
476
+ native_inspection: nativeInspectionCapability,
477
+ lifecycle_binding_token: lifecycleBindingToken,
478
+ confidence: session.confidence,
479
+ reason: session.reason,
480
+ terminal_control: terminalControl,
481
+ approval_state: effectiveTerminalState.approval_state,
482
+ activity_state: effectiveTerminalState.activity_state,
483
+ activity_reason: effectiveTerminalState.activity_reason,
484
+ // Internal exact-observation evidence. The public projection strips this
485
+ // object; raw terminal status reuses it so screen, approval, and activity
486
+ // all describe the same capture.
487
+ ...(terminalStatusSnapshot
488
+ ? { _terminal_status_snapshot: terminalStatusSnapshot }
489
+ : {}),
490
+ // Internal action-projection evidence. terminalFirstListProjection strips
491
+ // this field after gating every automated-input action that can follow a
492
+ // human native-thread switch.
493
+ _automated_input_composer_ready: automatedInputComposerReady,
494
+ ...(codexLatentClearResumeObservationValue
495
+ ? {
496
+ _codex_latent_clear_resume: {
497
+ source_native_thread_id: codexLatentClearResumeObservationValue.sourceNativeThreadId,
498
+ fingerprint: codexLatentClearResumeObservationValue.fingerprint
499
+ }
500
+ }
501
+ : {}),
502
+ ...(codexOpenRootRolloutInventory
503
+ ? {
504
+ _codex_open_root_rollout_inventory: codexOpenRootRolloutInventory
505
+ }
506
+ : {}),
507
+ ...(orphanedDispatch
508
+ ? {
509
+ orphaned_terminal_dispatch: {
510
+ kind: stringValue(orphanedDispatch.kind) ?? "turn",
511
+ status: orphanedDispatch.status,
512
+ owner_conversation_id: stringValue(orphanedDispatch.conversation_id),
513
+ message_id: stringValue(orphanedDispatch.message_id),
514
+ transition_id: stringValue(orphanedDispatch.transition_id),
515
+ recovery: `/akk close ${bridge.terminalConversationId(session)} ` +
516
+ (orphanedDispatch.kind === "lifecycle"
517
+ ? `--expected-transition-id ${String(orphanedDispatch.transition_id)}`
518
+ : `--expected-message-id ${String(orphanedDispatch.message_id)}`)
519
+ }
520
+ }
521
+ : {}),
522
+ commands: terminalListCommands({
523
+ agent: session.agent,
524
+ terminalControl,
525
+ terminalState: effectiveTerminalState,
526
+ lifecycleCapability,
527
+ nativeInspectionCapability,
528
+ nativeAgentIdentity,
529
+ nativeProcessUuid,
530
+ nativeProcessBirth,
531
+ codexLifecycleIncarnationAvailable,
532
+ automatedInputComposerReady,
533
+ hasOrphanedDispatch: orphanedDispatch !== undefined,
534
+ terminalHasBlockingTurn
535
+ })
536
+ };
537
+ const availableActions = renderAvailableListActions(entry);
538
+ const { commands: _commands, ...publicEntry } = entry;
539
+ return {
540
+ ...publicEntry,
541
+ available_actions: availableActions
542
+ };
543
+ }
544
+ function terminalListCommands(input) {
545
+ const { agent, terminalControl, terminalState, lifecycleCapability, nativeInspectionCapability, nativeAgentIdentity, nativeProcessUuid, nativeProcessBirth, codexLifecycleIncarnationAvailable, automatedInputComposerReady, hasOrphanedDispatch, terminalHasBlockingTurn } = input;
546
+ return {
547
+ send: !terminalHasBlockingTurn,
548
+ approve: terminalControl.capabilities.includes("terminal_approval") &&
549
+ terminalState.approval_state.approvable === true,
550
+ status: true,
551
+ cancel: terminalControl.capabilities.includes("terminal_cancel"),
552
+ close: hasOrphanedDispatch,
553
+ new_thread: lifecycleCapability.status === "supported" &&
554
+ lifecycleCapability.newThread === true &&
555
+ codexLifecycleIncarnationAvailable &&
556
+ !terminalHasBlockingTurn,
557
+ list_resumable_threads: lifecycleCapability.status === "supported" &&
558
+ lifecycleCapability.resumeExact === true &&
559
+ codexLifecycleIncarnationAvailable,
560
+ native_inspect: nativeInspectionCapability.status === "supported" &&
561
+ nativeInspectionCapability.statusInspection === true &&
562
+ terminalState.activity_state === "idle" &&
563
+ automatedInputComposerReady &&
564
+ terminalControl.capabilities.includes("send_keys") &&
565
+ terminalControl.capabilities.includes("screen_status") &&
566
+ (agent === "codex"
567
+ ? codexLifecycleIncarnationAvailable
568
+ : Boolean(nativeAgentIdentity?.sessionId &&
569
+ nativeAgentIdentity.processUuid)) &&
570
+ !hasOrphanedDispatch &&
571
+ !terminalHasBlockingTurn,
572
+ watch: lifecycleCapability.status === "supported" &&
573
+ (terminalState.activity_state === "working" ||
574
+ terminalState.activity_state === "awaiting_approval") &&
575
+ Boolean(nativeAgentIdentity?.sessionId) &&
576
+ (agent !== "codex" || Boolean(nativeAgentIdentity?.rollout)) &&
577
+ Boolean(nativeProcessUuid) &&
578
+ Boolean(nativeProcessBirth) &&
579
+ !hasOrphanedDispatch &&
580
+ !terminalHasBlockingTurn
581
+ };
582
+ }
583
+ function effectiveTerminalListState(input) {
584
+ const { session, terminalState, nativeIdentityObservation, nativeAgentIdentity, nativeProcessUuid, nativeProcessBirth } = input;
585
+ if (session.agent !== "codex" ||
586
+ terminalState.activity_state === "working" ||
587
+ terminalState.activity_state === "awaiting_approval" ||
588
+ terminalState.approval_state.blocked === true) {
589
+ return terminalState;
590
+ }
591
+ if (nativeIdentityObservation.status === "verified_absent") {
592
+ return terminalState;
593
+ }
594
+ if (nativeIdentityObservation.status !== "resolved" ||
595
+ !nativeAgentIdentity?.rollout ||
596
+ !nativeAgentIdentity.sessionId ||
597
+ !nativeProcessUuid ||
598
+ !nativeProcessBirth) {
599
+ return terminalListStateWithUnavailableDurableActivity(terminalState, nativeIdentityObservation.status === "unavailable"
600
+ ? nativeIdentityObservation.reason ??
601
+ "exact Codex identity observation failed"
602
+ : "exact Codex rollout/process identity is incomplete");
603
+ }
604
+ const currentIdentity = {
605
+ sessionId: nativeAgentIdentity.sessionId,
606
+ processUuid: nativeProcessUuid,
607
+ processBirth: nativeProcessBirth,
608
+ rollout: nativeAgentIdentity.rollout
609
+ };
610
+ try {
611
+ if (!captureCodexHumanStartedActiveTaskAnchor({ currentIdentity })) {
612
+ return terminalState;
613
+ }
614
+ return {
615
+ ...terminalState,
616
+ activity_state: "working",
617
+ activity_reason: "Codex rollout contains an exact unfinished human-started task"
618
+ };
619
+ }
620
+ catch (error) {
621
+ const reason = error instanceof Error ? error.message : String(error);
622
+ runtimeLog("warn", "terminal_durable_activity_unavailable", {
623
+ agent: session.agent,
624
+ pid: session.pid,
625
+ reason
626
+ });
627
+ return terminalListStateWithUnavailableDurableActivity(terminalState, reason);
628
+ }
629
+ }
630
+ function terminalListStateWithUnavailableDurableActivity(terminalState, reason) {
631
+ return {
632
+ ...terminalState,
633
+ activity_state: "unknown",
634
+ activity_reason: `durable Codex activity evidence is unavailable: ${reason}`
635
+ };
636
+ }
637
+ function terminalBridgeStatusWithActivity(status, activityState, activityReason) {
638
+ const descriptors = Object.getOwnPropertyDescriptors(status);
639
+ descriptors.activity_state = {
640
+ configurable: true,
641
+ enumerable: true,
642
+ value: activityState,
643
+ writable: true
644
+ };
645
+ descriptors.activity_reason = {
646
+ configurable: true,
647
+ enumerable: true,
648
+ value: activityReason,
649
+ writable: true
650
+ };
651
+ return Object.create(Object.getPrototypeOf(status), descriptors);
652
+ }
653
+ async function observeTerminalNativeListIdentity(session, terminalControl, options, bridge) {
654
+ const orphanedDispatch = terminalListRuntime().orphanedTerminalDispatchForRecovery(terminalControl);
655
+ let codexIdentityContext;
656
+ if (session.agent === "codex") {
657
+ try {
658
+ codexIdentityContext = terminalListRuntime().codexManagedIdentityResolutionContext({
659
+ storeDir: terminalListRuntime().storeDirFromOptions(options),
660
+ terminal: {
661
+ conversationId: bridge.terminalConversationId(session),
662
+ agent: session.agent,
663
+ pid: session.pid,
664
+ terminalControl
665
+ }
666
+ });
667
+ }
668
+ catch (error) {
669
+ // Preserve generic discovery when Store hints are unavailable. Once an
670
+ // exact hint exists, however, the constrained resolver below must fail
671
+ // closed rather than retrying without its managed lineage fences.
672
+ runtimeLog("warn", "terminal_managed_identity_context_unavailable", {
673
+ agent: session.agent,
674
+ terminal_target: terminalControl.target,
675
+ pid: session.pid,
676
+ error: error instanceof Error ? error.message : String(error)
677
+ });
678
+ }
679
+ }
680
+ const nativeIdentityObservation = await terminalListRuntime().observeCurrentNativeAgentSessionIdentity({
681
+ options,
682
+ agent: session.agent,
683
+ pid: session.pid,
684
+ cwd: session.cwd ?? terminalControl.currentPath,
685
+ preferredSessionId: codexIdentityContext?.preferredSessionId,
686
+ allowedCompanionIdentity: codexIdentityContext?.companions.primary,
687
+ allowedAdditionalIdentities: codexIdentityContext?.companions.additional
688
+ });
689
+ const nativeAgentIdentity = nativeIdentityObservation.status === "resolved"
690
+ ? nativeIdentityObservation.identity
691
+ : undefined;
692
+ let codexOpenRootRolloutInventory;
693
+ if (session.agent === "codex") {
694
+ try {
695
+ const inventory = await terminalListRuntime().inspectCodexOpenRootRolloutInventory({
696
+ options,
697
+ pid: session.pid,
698
+ cwd: session.cwd ?? terminalControl.currentPath
699
+ });
700
+ if (inventory.roots.length > 0) {
701
+ codexOpenRootRolloutInventory = inventory;
702
+ }
703
+ }
704
+ catch (error) {
705
+ runtimeLog("warn", "terminal_codex_open_root_inventory_unavailable", {
706
+ terminal_target: terminalControl.target,
707
+ pid: session.pid,
708
+ error: error instanceof Error ? error.message : String(error)
709
+ });
710
+ }
711
+ }
712
+ if (nativeIdentityObservation.status === "unavailable") {
713
+ runtimeLog("warn", "terminal_native_session_identity_unavailable", {
714
+ agent: session.agent,
715
+ terminal_target: terminalControl.target,
716
+ pid: session.pid,
717
+ error: nativeIdentityObservation.reason
718
+ });
719
+ }
720
+ let nativeProcessUuid = nativeAgentIdentity?.processUuid;
721
+ let nativeProcessBirth = nativeAgentIdentity?.processBirth;
722
+ let nativeProcessEvidence = nativeAgentIdentity?.evidence;
723
+ if (codexOpenRootRolloutInventory) {
724
+ nativeProcessUuid = codexOpenRootRolloutInventory.processUuid;
725
+ nativeProcessBirth = codexOpenRootRolloutInventory.processBirth;
726
+ nativeProcessEvidence = "codex_open_root_rollout_inventory";
727
+ }
728
+ if (session.agent === "codex" &&
729
+ (!nativeProcessUuid || !nativeProcessBirth)) {
730
+ try {
731
+ const incarnation = terminalListRuntime().codexProcessIncarnationForPid(session.pid);
732
+ nativeProcessUuid = incarnation.processUuid;
733
+ nativeProcessBirth = incarnation.processBirth;
734
+ nativeProcessEvidence = nativeProcessEvidence ?? incarnation.evidence;
735
+ }
736
+ catch (error) {
737
+ runtimeLog("warn", "terminal_process_incarnation_unavailable", {
738
+ agent: session.agent,
739
+ terminal_target: terminalControl.target,
740
+ pid: session.pid,
741
+ error: error instanceof Error ? error.message : String(error)
742
+ });
743
+ }
744
+ }
745
+ return {
746
+ orphanedDispatch,
747
+ nativeIdentityObservation,
748
+ nativeAgentIdentity,
749
+ codexOpenRootRolloutInventory,
750
+ nativeProcessUuid,
751
+ nativeProcessBirth,
752
+ nativeProcessEvidence
753
+ };
754
+ }
755
+ async function observeAutomatedInputComposerReady({ session, terminalControl, terminalState, nativeIdentityObservation, codexOpenRootRolloutInventory, options }) {
756
+ let ready = terminalListRuntime().nativeInspectionComposerEmpty(session.agent, terminalState.screen_excerpt);
757
+ if (session.agent !== "codex" ||
758
+ (terminalState.activity_state !== "idle" &&
759
+ (terminalState.activity_state !== "unknown" ||
760
+ (nativeIdentityObservation.status !== "verified_absent" &&
761
+ codexOpenRootRolloutInventory === undefined))) ||
762
+ terminalState.approval_state.blocked === true ||
763
+ !terminalControl.capabilities.includes("send_keys") ||
764
+ !terminalControl.capabilities.includes("screen_status")) {
765
+ return ready;
766
+ }
767
+ try {
768
+ const provider = terminalListRuntime().createTerminalControlProvider(options);
769
+ const resolvedTerminal = await provider.resolve(provider.endpoint(terminalControl));
770
+ const styledScreen = await provider.capture(resolvedTerminal, { scrollbackLines: 40, preserveEscapes: true });
771
+ ready = exactCodexReadyStyledComposerCapture(styledScreen) !== undefined;
772
+ }
773
+ catch {
774
+ // Advertising an input action is optional. The action itself repeats the
775
+ // same styled composer proof under the terminal lock before any input.
776
+ ready = false;
777
+ }
778
+ return ready;
779
+ }
780
+ function observeTerminalListBindingAuthority(terminal, context) {
781
+ const { storeDir, managedSessions, allConversations, displayedConversations, mutationsAllowed, nonterminalDeferredTransfers } = context;
782
+ const { _automated_input_composer_ready: automatedInputComposerReady, _codex_open_root_rollout_inventory: codexOpenRootRolloutInventoryValue, _codex_latent_clear_resume: codexLatentClearResumeValue, _terminal_status_snapshot: _terminalStatusSnapshot, ...publicTerminal } = terminal;
783
+ const codexOpenRootRolloutInventory = isRecord(codexOpenRootRolloutInventoryValue)
784
+ ? codexOpenRootRolloutInventoryValue
785
+ : undefined;
786
+ const terminalControl = isRecord(terminal.terminal_control)
787
+ ? terminal.terminal_control
788
+ : undefined;
789
+ const terminalHasNonterminalDeferredTransfer = Boolean(terminalControl && nonterminalDeferredTransfers.some((transfer) => transfer.terminal_id === String(terminal.id) &&
790
+ transfer.process_pid === Number(terminal.pid) &&
791
+ terminalControlEvidenceMatches(transfer.terminal_endpoint, terminalControl)));
792
+ const allRelated = terminalControl
793
+ ? allConversations.filter((conversation) => terminalControlsShareIncarnation(terminalControlForManagedConversation(conversation), terminalControl))
794
+ : [];
795
+ const displayedRelated = terminalControl
796
+ ? displayedConversations.filter((conversation) => terminalControlsShareIncarnation(terminalControlForManagedConversation(conversation), terminalControl))
797
+ : [];
798
+ const relatedSessions = terminalControl
799
+ ? managedSessions.filter((session) => terminalControlsShareIncarnation(session.binding?.terminal_control, terminalControl))
800
+ : [];
801
+ const matchingSessions = relatedSessions.filter((session) => managedSessionMatchesLiveTerminalEntry(session, terminal, storeDir));
802
+ const conflictingBoundSessionClaims = relatedSessions.flatMap((session) => {
803
+ const kind = managedBindingConflictKindForLiveTerminalEntry({
804
+ storeDir,
805
+ session,
806
+ terminal
807
+ });
808
+ return kind && kind !== "stale_process_incarnation"
809
+ ? [{ session, kind }]
810
+ : [];
811
+ });
812
+ const unresolvedSessionClaims = relatedSessions.filter((session) => ["transitioning", "quarantined"].includes(session.status) &&
813
+ managedSessionClaimsLiveTerminalEntry(session, terminal));
814
+ const sessionAuthorityConflict = decideTerminalSessionAuthorityConflict({
815
+ unresolvedSessionClaims,
816
+ conflictingBoundSessionClaims,
817
+ matchingSessions
818
+ });
819
+ const authoritativeSession = matchingSessions[0];
820
+ const discoveredOwnership = terminalControl
821
+ ? terminalDispatchOwnership(terminalControl)
822
+ : { state: "none" };
823
+ const localOwnership = discoveredOwnership.state === "current"
824
+ ? localTerminalDispatchOwnership(discoveredOwnership.conversation, allRelated, terminal)
825
+ : discoveredOwnership;
826
+ const dispatchOwnerMismatch = !sessionAuthorityConflict &&
827
+ localOwnership.state === "current" &&
828
+ authoritativeSession &&
829
+ sessionIdForConversation(localOwnership.conversation) !==
830
+ authoritativeSession.session_id
831
+ ? {
832
+ ownerSessionId: sessionIdForConversation(discoveredOwnership.state === "current"
833
+ ? discoveredOwnership.conversation
834
+ : localOwnership.conversation)
835
+ }
836
+ : undefined;
837
+ const ownership = applySessionAuthorityToDispatch({
838
+ localOwnership,
839
+ sessionAuthorityConflict,
840
+ authoritativeSession,
841
+ dispatchOwnerMismatch
842
+ });
843
+ const discoveredRawActions = isRecord(terminal.available_actions)
844
+ ? terminal.available_actions
845
+ : {};
846
+ const rawActions = mutationsAllowed
847
+ ? discoveredRawActions
848
+ : readOnlyListActions(discoveredRawActions);
849
+ const rawSendAction = isRecord(rawActions.send)
850
+ ? rawActions.send
851
+ : {};
852
+ const bindingAwareRawActions = authoritativeSession
853
+ ? actionsForManagedSessionBinding(rawActions, authoritativeSession)
854
+ : rawActions;
855
+ const sessionAwareRawActionsBase = authoritativeSession &&
856
+ managedSessionHasUnresolvedNativeTransition(storeDir, authoritativeSession)
857
+ ? Object.fromEntries(Object.entries(bindingAwareRawActions).filter(([actionName]) => actionName !== "native_inspect"))
858
+ : bindingAwareRawActions;
859
+ const sessionAwareRawActions = terminalHasNonterminalDeferredTransfer
860
+ ? readOnlyListActions(sessionAwareRawActionsBase)
861
+ : sessionAwareRawActionsBase;
862
+ const soleBindingConflict = conflictingBoundSessionClaims.length === 1
863
+ ? conflictingBoundSessionClaims[0]
864
+ : undefined;
865
+ const externalHandoffDetected = conflictingBoundSessionClaims.some(({ kind }) => kind === "live_external_thread_change");
866
+ const conflictingSessionRevision = Number(soleBindingConflict?.session.revision);
867
+ const conflictingSessionTurns = soleBindingConflict
868
+ ? terminalListRuntime().managedTurnsForSession(storeDir, soleBindingConflict.session.session_id)
869
+ : [];
870
+ const expectedTerminalToken = stringValue(terminal.lifecycle_binding_token);
871
+ const externalHandoffNativeThreadId = stringValue(terminal.native_agent_status_card_session_id) ?? stringValue(terminal.native_agent_session_id);
872
+ const resolvedNativeThreadId = stringValue(terminal.native_agent_session_id);
873
+ const externalHandoffTerminalToken = terminalControl &&
874
+ externalHandoffNativeThreadId &&
875
+ isExactNativeThreadId(externalHandoffNativeThreadId)
876
+ ? unmanagedTerminalBindingToken({
877
+ terminalId: stringValue(terminal.id),
878
+ terminalControl,
879
+ agent: terminal.agent,
880
+ pid: Number(terminal.pid),
881
+ workspace: terminal.workspace ?? terminal.cwd ?? cliCwd(),
882
+ nativeThreadId: externalHandoffNativeThreadId,
883
+ processUuid: stringValue(terminal.native_agent_process_uuid),
884
+ processBirth: stringValue(terminal.native_agent_process_birth),
885
+ rollout: resolvedNativeThreadId === externalHandoffNativeThreadId &&
886
+ isRecord(terminal.native_agent_rollout)
887
+ ? terminal.native_agent_rollout
888
+ : undefined
889
+ })
890
+ : undefined;
891
+ const externalHandoffTarget = soleBindingConflict?.kind === "live_external_thread_change" &&
892
+ externalHandoffNativeThreadId &&
893
+ isExactNativeThreadId(externalHandoffNativeThreadId)
894
+ ? terminalListRuntime().observedHandoffTargetResolution({
895
+ storeDir,
896
+ agent: terminal.agent,
897
+ workspace: terminal.workspace ?? terminal.cwd ?? cliCwd(),
898
+ nativeThreadId: externalHandoffNativeThreadId.toLowerCase(),
899
+ sourceSessionId: soleBindingConflict.session.session_id
900
+ })
901
+ : undefined;
902
+ const externalHandoffSnapshotToken = externalHandoffTerminalToken &&
903
+ soleBindingConflict?.kind === "live_external_thread_change" &&
904
+ externalHandoffTarget?.status === "eligible"
905
+ ? humanObservedHandoffBindingToken({
906
+ terminal_token: externalHandoffTerminalToken,
907
+ source_session_id: soleBindingConflict.session.session_id,
908
+ source_revision: managedSessionRevision(soleBindingConflict.session),
909
+ source_binding_token: managedSessionBindingToken(soleBindingConflict.session),
910
+ target: externalHandoffTarget.snapshot
911
+ })
912
+ : undefined;
913
+ const blockingHandoffTurns = conflictingSessionTurns.filter((turn) => isSessionSendBlockingStatus(turn.status));
914
+ const terminalBlockingTurns = terminalControl
915
+ ? terminalIncarnationBlockingTurns(storeDir, terminalControl, allRelated)
916
+ : [];
917
+ const externalHandoffSourceSessionIds = new Set(conflictingBoundSessionClaims
918
+ .filter(({ kind }) => kind === "live_external_thread_change")
919
+ .map(({ session }) => session.session_id));
920
+ const handoffSourceBlockingTurns = terminalBlockingTurns.filter((turn) => externalHandoffSourceSessionIds.has(sessionIdForConversation(turn)));
921
+ const nativeIdentityObservation = isRecord(terminal.native_agent_identity_observation)
922
+ ? terminal.native_agent_identity_observation
923
+ : undefined;
924
+ const codexProcessUuid = stringValue(terminal.native_agent_process_uuid);
925
+ const codexProcessBirth = stringValue(terminal.native_agent_process_birth);
926
+ const codexWorkspace = stringValue(terminal.workspace ?? terminal.cwd);
927
+ const codexSendAuthorityContext = terminalControl
928
+ ? {
929
+ terminalId: String(terminal.id),
930
+ terminalControl,
931
+ pid: Number(terminal.pid),
932
+ workspace: codexWorkspace,
933
+ liveProcessUuid: codexProcessUuid,
934
+ liveProcessBirth: codexProcessBirth
935
+ }
936
+ : undefined;
937
+ return {
938
+ automatedInputComposerReady,
939
+ codexOpenRootRolloutInventory,
940
+ codexLatentClearResumeValue,
941
+ publicTerminal,
942
+ terminalControl,
943
+ terminalHasNonterminalDeferredTransfer,
944
+ allRelated,
945
+ displayedRelated,
946
+ relatedSessions,
947
+ matchingSessions,
948
+ conflictingBoundSessionClaims,
949
+ unresolvedSessionClaims,
950
+ sessionAuthorityConflict,
951
+ authoritativeSession,
952
+ discoveredOwnership,
953
+ ownership,
954
+ rawActions,
955
+ rawSendAction,
956
+ sessionAwareRawActions,
957
+ soleBindingConflict,
958
+ externalHandoffDetected,
959
+ conflictingSessionRevision,
960
+ conflictingSessionTurns,
961
+ expectedTerminalToken,
962
+ externalHandoffNativeThreadId,
963
+ externalHandoffTarget,
964
+ externalHandoffSnapshotToken,
965
+ blockingHandoffTurns,
966
+ terminalBlockingTurns,
967
+ handoffSourceBlockingTurns,
968
+ nativeIdentityObservation,
969
+ codexProcessUuid,
970
+ codexProcessBirth,
971
+ codexWorkspace,
972
+ codexSendAuthorityContext
973
+ };
974
+ }
975
+ function observeVerifiedEmptyTerminalAuthority(terminal, context, binding) {
976
+ const { storeDir, terminals, mutationsAllowed } = context;
977
+ const { automatedInputComposerReady, terminalControl, matchingSessions, unresolvedSessionClaims, discoveredOwnership, rawActions, soleBindingConflict, blockingHandoffTurns, terminalBlockingTurns, nativeIdentityObservation, codexProcessUuid, codexProcessBirth, codexWorkspace, codexSendAuthorityContext } = binding;
978
+ const verifiedEmptySourceNativeThreadId = stringValue(soleBindingConflict?.session.binding?.native_thread_id)?.toLowerCase();
979
+ const verifiedEmptySourceActiveElsewhere = Boolean(verifiedEmptySourceNativeThreadId &&
980
+ terminals.some((candidate) => candidate !== terminal &&
981
+ candidate.agent === "codex" &&
982
+ stringValue(candidate.native_agent_session_id)?.toLowerCase() ===
983
+ verifiedEmptySourceNativeThreadId));
984
+ const verifiedEmptyRawSendAction = isRecord(rawActions.send)
985
+ ? rawActions.send
986
+ : {
987
+ tool: "agent_knock_knock_send",
988
+ arguments: { selector: stringValue(terminal.id) },
989
+ missing_required: ["request"]
990
+ };
991
+ const verifiedEmptyCodexHandoffEligible = Boolean(mutationsAllowed &&
992
+ terminal.agent === "codex" &&
993
+ terminalControl &&
994
+ discoveredOwnership.state === "none" &&
995
+ unresolvedSessionClaims.length === 0 &&
996
+ matchingSessions.length === 0 &&
997
+ soleBindingConflict?.kind === "unverifiable" &&
998
+ nativeIdentityObservation?.status === "verified_absent" &&
999
+ codexSendAuthorityContext &&
1000
+ codexProcessUuid &&
1001
+ codexProcessBirth &&
1002
+ codexWorkspace &&
1003
+ exactBoundCodexSendSource({
1004
+ kind: "verified_empty",
1005
+ sourceSession: soleBindingConflict.session,
1006
+ context: codexSendAuthorityContext
1007
+ }) &&
1008
+ ["idle", "unknown"].includes(String(terminal.activity_state)) &&
1009
+ automatedInputComposerReady === true &&
1010
+ !(isRecord(terminal.approval_state) &&
1011
+ terminal.approval_state.blocked === true) &&
1012
+ blockingHandoffTurns.length === 0 &&
1013
+ terminalBlockingTurns.length === 0 &&
1014
+ !managedSessionHasUnresolvedNativeTransition(storeDir, soleBindingConflict.session) &&
1015
+ !verifiedEmptySourceActiveElsewhere &&
1016
+ terminal.orphaned_terminal_dispatch === undefined &&
1017
+ terminalControl?.capabilities.includes("send_keys") &&
1018
+ terminalControl.capabilities.includes("screen_status"));
1019
+ const verifiedEmptyCodexSnapshotToken = verifiedEmptyCodexHandoffEligible &&
1020
+ codexSendAuthorityContext &&
1021
+ soleBindingConflict &&
1022
+ codexProcessUuid &&
1023
+ codexProcessBirth &&
1024
+ codexWorkspace
1025
+ ? verifiedEmptyCodexHandoffToken({
1026
+ terminalId: String(terminal.id),
1027
+ terminalControl: codexSendAuthorityContext.terminalControl,
1028
+ pid: Number(terminal.pid),
1029
+ workspace: codexWorkspace,
1030
+ processUuid: codexProcessUuid,
1031
+ processBirth: codexProcessBirth,
1032
+ sourceSession: soleBindingConflict.session
1033
+ })
1034
+ : undefined;
1035
+ return {
1036
+ ...binding,
1037
+ verifiedEmptyRawSendAction,
1038
+ verifiedEmptyCodexSnapshotToken
1039
+ };
1040
+ }
1041
+ function observeDeferredSourceAuthority(terminal, context, observation) {
1042
+ const { terminals } = context;
1043
+ const { codexLatentClearResumeValue, codexOpenRootRolloutInventory, authoritativeSession, matchingSessions, conflictingBoundSessionClaims, unresolvedSessionClaims, soleBindingConflict, nativeIdentityObservation } = observation;
1044
+ const deferredCodexCandidateInventory = codexOpenRootRolloutInventory &&
1045
+ codexOpenRootRolloutInventory.roots.length > 0
1046
+ ? codexOpenRootRolloutInventory
1047
+ : undefined;
1048
+ const abandonedConflictSource = !authoritativeSession &&
1049
+ soleBindingConflict?.kind === "unverifiable" &&
1050
+ matchingSessions.length === 0 &&
1051
+ conflictingBoundSessionClaims.length === 1 &&
1052
+ unresolvedSessionClaims.length === 0 &&
1053
+ terminal.agent === "codex"
1054
+ ? soleBindingConflict.session
1055
+ : undefined;
1056
+ const deferredCodexSource = (authoritativeSession ??
1057
+ abandonedConflictSource) &&
1058
+ terminal.agent === "codex" &&
1059
+ (nativeIdentityObservation?.status === "verified_absent" ||
1060
+ deferredCodexCandidateInventory !== undefined)
1061
+ ? authoritativeSession ?? abandonedConflictSource
1062
+ : undefined;
1063
+ const deferredCodexSourceNativeThreadId = stringValue(deferredCodexSource?.binding?.native_thread_id)?.toLowerCase();
1064
+ const deferredCodexLatentClearResumeFingerprint = isRecord(codexLatentClearResumeValue) &&
1065
+ stringValue(codexLatentClearResumeValue.source_native_thread_id)?.toLowerCase() === deferredCodexSourceNativeThreadId
1066
+ ? stringValue(codexLatentClearResumeValue.fingerprint)
1067
+ : undefined;
1068
+ if (deferredCodexLatentClearResumeFingerprint) {
1069
+ // The resume hint is useful operational context, but it is not durable
1070
+ // foreground authority: it can scroll away while the latent thread is
1071
+ // still current. Candidate routing and its token rely on the complete
1072
+ // rollout inventory and Store authority below instead.
1073
+ runtimeLog("info", "terminal_codex_latent_clear_hint_observed", {
1074
+ terminal_id: String(terminal.id),
1075
+ source_session_id: deferredCodexSource?.session_id,
1076
+ source_native_thread_id: deferredCodexSourceNativeThreadId
1077
+ });
1078
+ }
1079
+ const deferredCodexSourceActiveElsewhere = Boolean(deferredCodexSourceNativeThreadId &&
1080
+ terminals.some((candidate) => candidate !== terminal &&
1081
+ candidate.agent === "codex" &&
1082
+ stringValue(candidate.native_agent_session_id)?.toLowerCase() ===
1083
+ deferredCodexSourceNativeThreadId));
1084
+ return {
1085
+ ...observation,
1086
+ deferredCodexCandidateInventory,
1087
+ abandonedConflictSource,
1088
+ deferredCodexSource,
1089
+ deferredCodexSourceActiveElsewhere
1090
+ };
1091
+ }
1092
+ function observeDeferredTerminalAuthority(terminal, context, observation) {
1093
+ const { storeDir, mutationsAllowed } = context;
1094
+ const { automatedInputComposerReady, terminalControl, terminalHasNonterminalDeferredTransfer, matchingSessions, conflictingBoundSessionClaims, unresolvedSessionClaims, discoveredOwnership, terminalBlockingTurns, codexProcessUuid, codexProcessBirth, codexWorkspace, codexSendAuthorityContext, deferredCodexCandidateInventory, abandonedConflictSource, deferredCodexSource, deferredCodexSourceActiveElsewhere } = observation;
1095
+ const deferredCodexAuthority = codexSendAuthorityContext
1096
+ ? terminalListRuntime().observeDeferredCodexAuthority({
1097
+ mode: "list",
1098
+ storeDir,
1099
+ context: codexSendAuthorityContext,
1100
+ sourceSession: deferredCodexSource,
1101
+ candidateInventory: deferredCodexCandidateInventory,
1102
+ abandonment: abandonedConflictSource
1103
+ ? "missing_rollout"
1104
+ : "never",
1105
+ requireUnclaimedCandidate: true
1106
+ })
1107
+ : undefined;
1108
+ const deferredCodexSourceRolloutAuthority = deferredCodexAuthority?.sourceRolloutAuthority ?? "present";
1109
+ const deferredCodexDispatchSnapshot = deferredCodexAuthority?.dispatchSnapshot;
1110
+ const deferredCodexForegroundEligible = Boolean(mutationsAllowed &&
1111
+ !terminalHasNonterminalDeferredTransfer &&
1112
+ deferredCodexSource &&
1113
+ terminalControl &&
1114
+ hasCanonicalTerminalEndpoint(terminalControl) &&
1115
+ discoveredOwnership.state === "none" &&
1116
+ unresolvedSessionClaims.length === 0 &&
1117
+ ((matchingSessions.length === 1 &&
1118
+ conflictingBoundSessionClaims.length === 0) ||
1119
+ (deferredCodexSourceRolloutAuthority ===
1120
+ "explicitly_abandoned_predecessor" &&
1121
+ matchingSessions.length === 0 &&
1122
+ conflictingBoundSessionClaims.length === 1)) &&
1123
+ codexProcessUuid &&
1124
+ codexProcessBirth &&
1125
+ codexWorkspace &&
1126
+ deferredCodexAuthority?.exactSource &&
1127
+ terminalBlockingTurns.length === 0 &&
1128
+ terminal.orphaned_terminal_dispatch === undefined &&
1129
+ deferredCodexDispatchSnapshot &&
1130
+ ["idle", "unknown"].includes(String(terminal.activity_state)) &&
1131
+ automatedInputComposerReady === true &&
1132
+ !(isRecord(terminal.approval_state) &&
1133
+ terminal.approval_state.blocked === true) &&
1134
+ !deferredCodexSourceActiveElsewhere &&
1135
+ terminalControl.capabilities.includes("send_keys") &&
1136
+ terminalControl.capabilities.includes("screen_status"));
1137
+ const deferredCodexForegroundToken = deferredCodexForegroundEligible &&
1138
+ deferredCodexSource &&
1139
+ terminalControl &&
1140
+ codexProcessUuid &&
1141
+ codexProcessBirth &&
1142
+ codexWorkspace &&
1143
+ deferredCodexDispatchSnapshot
1144
+ ? deferredCodexForegroundBindingToken({
1145
+ terminalId: String(terminal.id),
1146
+ terminalControl,
1147
+ pid: Number(terminal.pid),
1148
+ workspace: codexWorkspace,
1149
+ processUuid: codexProcessUuid,
1150
+ processBirth: codexProcessBirth,
1151
+ sourceSession: deferredCodexSource,
1152
+ dispatchSnapshot: deferredCodexDispatchSnapshot,
1153
+ sourceTurnHistory: deferredCodexAuthority?.sourceTurnHistory,
1154
+ sourceRolloutAuthority: deferredCodexSourceRolloutAuthority,
1155
+ sourceAbandonmentFingerprint: deferredCodexAuthority?.sourceAbandonmentFingerprint,
1156
+ ...(deferredCodexCandidateInventory
1157
+ ? { candidateInventory: deferredCodexCandidateInventory }
1158
+ : {})
1159
+ })
1160
+ : undefined;
1161
+ return {
1162
+ ...observation,
1163
+ deferredCodexSourceRolloutAuthority,
1164
+ deferredCodexForegroundToken
1165
+ };
1166
+ }
1167
+ function observeTerminalHandoffAuthority(terminal, context, observation) {
1168
+ const { storeDir, mutationsAllowed, conversationHasNonterminalDeferredTransfer } = context;
1169
+ const { automatedInputComposerReady, terminalControl, matchingSessions, unresolvedSessionClaims, discoveredOwnership, rawActions, soleBindingConflict, conflictingSessionRevision, conflictingSessionTurns, expectedTerminalToken, externalHandoffNativeThreadId, externalHandoffTarget, externalHandoffSnapshotToken, blockingHandoffTurns, terminalBlockingTurns, handoffSourceBlockingTurns } = observation;
1170
+ const reconcileBindingAction = mutationsAllowed &&
1171
+ discoveredOwnership.state === "none" &&
1172
+ unresolvedSessionClaims.length === 0 &&
1173
+ matchingSessions.length === 0 &&
1174
+ soleBindingConflict &&
1175
+ soleBindingConflict.kind !== "unverifiable" &&
1176
+ Number.isSafeInteger(conflictingSessionRevision) &&
1177
+ conflictingSessionRevision > 0 &&
1178
+ expectedTerminalToken &&
1179
+ terminal.activity_state === "idle" &&
1180
+ !(isRecord(terminal.approval_state) &&
1181
+ terminal.approval_state.blocked === true) &&
1182
+ !conflictingSessionTurns.some((turn) => isSessionSendBlockingStatus(turn.status)) &&
1183
+ terminalBlockingTurns.length === 0 &&
1184
+ !managedSessionHasUnresolvedNativeTransition(storeDir, soleBindingConflict.session)
1185
+ ? projectReconcileBindingAction({
1186
+ terminalId: stringValue(terminal.id),
1187
+ conflictingSession: soleBindingConflict.session,
1188
+ conflictingSessionRevision,
1189
+ expectedTerminalToken
1190
+ })
1191
+ : undefined;
1192
+ const externalHandoffAdoptable = Boolean(mutationsAllowed &&
1193
+ discoveredOwnership.state === "none" &&
1194
+ unresolvedSessionClaims.length === 0 &&
1195
+ soleBindingConflict?.kind === "live_external_thread_change" &&
1196
+ terminal.activity_state === "idle" &&
1197
+ automatedInputComposerReady === true &&
1198
+ !(isRecord(terminal.approval_state) &&
1199
+ terminal.approval_state.blocked === true) &&
1200
+ !conflictingSessionTurns.some((turn) => isSessionSendBlockingStatus(turn.status)) &&
1201
+ terminalBlockingTurns.length === 0 &&
1202
+ !managedSessionHasUnresolvedNativeTransition(storeDir, soleBindingConflict.session) &&
1203
+ externalHandoffTarget?.status === "eligible" &&
1204
+ isRecord(rawActions.send) &&
1205
+ Boolean(externalHandoffSnapshotToken));
1206
+ const handoffDecisionTurn = mutationsAllowed &&
1207
+ soleBindingConflict?.kind === "live_external_thread_change" &&
1208
+ externalHandoffTarget?.status === "eligible" &&
1209
+ terminal.activity_state === "idle" &&
1210
+ !(isRecord(terminal.approval_state) &&
1211
+ terminal.approval_state.blocked === true) &&
1212
+ !managedSessionHasUnresolvedNativeTransition(storeDir, soleBindingConflict.session) &&
1213
+ blockingHandoffTurns.length === 1 &&
1214
+ terminalBlockingTurns.every((turn) => turn.conversation_id === blockingHandoffTurns[0].conversation_id)
1215
+ ? blockingHandoffTurns[0]
1216
+ : undefined;
1217
+ const handoffDecisionToken = handoffDecisionTurn &&
1218
+ externalHandoffSnapshotToken &&
1219
+ terminalControl
1220
+ ? terminalListRuntime().activeTurnHandoffDecisionToken({
1221
+ handoffToken: externalHandoffSnapshotToken,
1222
+ turn: handoffDecisionTurn,
1223
+ ledger: terminalListRuntime().loadTerminalBridgeDispatchLedger(terminalControl)
1224
+ })
1225
+ : undefined;
1226
+ const handoffDecision = handoffDecisionTurn &&
1227
+ handoffDecisionToken &&
1228
+ externalHandoffNativeThreadId
1229
+ ? projectHandoffDecision({
1230
+ sourceSessionId: soleBindingConflict?.session.session_id,
1231
+ sourceTurnId: turnIdForConversation(handoffDecisionTurn),
1232
+ liveNativeThreadId: externalHandoffNativeThreadId,
1233
+ handoffDecisionToken,
1234
+ actionTurnId: turnIdForConversation(handoffDecisionTurn)
1235
+ })
1236
+ : undefined;
1237
+ const blockingHandoffTurnIds = new Set(handoffSourceBlockingTurns.map((turn) => turn.conversation_id));
1238
+ // An active source Turn may be superseded only through the snapshot-bound
1239
+ // handoff decision above. Never expose the generic Store-only close action
1240
+ // for that Turn, because doing so would bypass expected_handoff_token after
1241
+ // the live native thread or Turn generation changed. `blocking_turns` is
1242
+ // reserved for other same-incarnation blockers such as legacy collateral
1243
+ // stalls; clear those first, refresh, and only then decide the handoff.
1244
+ const terminalRecoveryBlockingTurns = terminalBlockingTurns.filter((turn) => !blockingHandoffTurnIds.has(turn.conversation_id) &&
1245
+ !conversationHasNonterminalDeferredTransfer(turn));
1246
+ return {
1247
+ ...observation,
1248
+ reconcileBindingAction,
1249
+ externalHandoffAdoptable,
1250
+ handoffDecision,
1251
+ blockingHandoffTurnIds,
1252
+ terminalRecoveryBlockingTurns
1253
+ };
1254
+ }
1255
+ function observeTerminalScopedApprovalAuthority(terminal, context, observation) {
1256
+ const { storeDir, mutationsAllowed } = context;
1257
+ const { terminalControl, terminalHasNonterminalDeferredTransfer, conflictingBoundSessionClaims, unresolvedSessionClaims, sessionAuthorityConflict, authoritativeSession, discoveredOwnership, ownership, rawActions } = observation;
1258
+ let terminalScopedCodexApprovalAction;
1259
+ const terminalScopedCodexApprovalPrompt = terminalScopedCodexApprovalPromptSnapshot(terminal.approval_state);
1260
+ if (mutationsAllowed &&
1261
+ terminal.agent === "codex" &&
1262
+ terminalControl &&
1263
+ hasCanonicalTerminalEndpoint(terminalControl) &&
1264
+ authoritativeSession &&
1265
+ !sessionAuthorityConflict &&
1266
+ !terminalHasNonterminalDeferredTransfer &&
1267
+ unresolvedSessionClaims.length === 0 &&
1268
+ conflictingBoundSessionClaims.length === 0 &&
1269
+ terminalScopedCodexApprovalPrompt &&
1270
+ isRecord(rawActions.approve)) {
1271
+ try {
1272
+ const ledger = terminalListRuntime().loadTerminalBridgeDispatchLedger(terminalControl);
1273
+ const boundary = ownership.state === "conflict" &&
1274
+ discoveredOwnership.state === "current" &&
1275
+ ledger
1276
+ ? terminalScopedCodexApprovalBoundary({
1277
+ storeDir,
1278
+ terminal,
1279
+ owner: discoveredOwnership.conversation,
1280
+ session: authoritativeSession,
1281
+ ledger,
1282
+ approval: terminalScopedCodexApprovalPrompt
1283
+ })
1284
+ : ownership.state === "none" &&
1285
+ discoveredOwnership.state === "none"
1286
+ ? terminalScopedCodexApprovalBoundary({
1287
+ storeDir,
1288
+ terminal,
1289
+ session: authoritativeSession,
1290
+ ledger,
1291
+ approval: terminalScopedCodexApprovalPrompt
1292
+ })
1293
+ : undefined;
1294
+ if (!boundary) {
1295
+ throw new Error("terminal-scoped Codex approval has no eligible managed authority");
1296
+ }
1297
+ terminalScopedCodexApprovalAction = {
1298
+ ...rawActions.approve,
1299
+ arguments: {
1300
+ conversation_id: String(terminal.id),
1301
+ expected_terminal_token: boundary.token
1302
+ },
1303
+ scope: "terminal_current_prompt",
1304
+ authority: boundary.authority.kind,
1305
+ managed_state_unchanged: true,
1306
+ automatic_approval_eligible: false,
1307
+ durable_dispatch_receipt: false,
1308
+ uncertain_outcome_recovery: "refresh status and inspect the live prompt; do not retry blindly"
1309
+ };
1310
+ }
1311
+ catch (error) {
1312
+ runtimeLog("info", "terminal_scoped_codex_approval_not_advertised", {
1313
+ terminal_id: String(terminal.id),
1314
+ terminal_target: terminalControl.target,
1315
+ reason: error instanceof Error ? error.message : String(error)
1316
+ });
1317
+ }
1318
+ }
1319
+ const rolloutBackedCodexSession = Boolean(authoritativeSession?.agent === "codex" &&
1320
+ isCompleteNativeRollout(authoritativeSession.binding?.native_process.rollout));
1321
+ return {
1322
+ ...observation,
1323
+ terminalScopedCodexApprovalAction,
1324
+ rolloutBackedCodexSession
1325
+ };
1326
+ }
1327
+ function observeTerminalListActionAuthority(terminal, context, binding) {
1328
+ return observeTerminalScopedApprovalAuthority(terminal, context, observeTerminalHandoffAuthority(terminal, context, observeDeferredTerminalAuthority(terminal, context, observeDeferredSourceAuthority(terminal, context, observeVerifiedEmptyTerminalAuthority(terminal, context, binding)))));
1329
+ }
1330
+ function renderTerminalFirstListEntry(terminal, context, observation) {
1331
+ const { sessionAuthorityRequired, includeAll, mutationsAllowed, conversationHasNonterminalDeferredTransfer } = context;
1332
+ const { automatedInputComposerReady, publicTerminal, allRelated, displayedRelated, relatedSessions, authoritativeSession, ownership, rawSendAction, sessionAwareRawActions, externalHandoffDetected, externalHandoffSnapshotToken, handoffSourceBlockingTurns, verifiedEmptyRawSendAction, verifiedEmptyCodexSnapshotToken, deferredCodexSourceRolloutAuthority, deferredCodexForegroundToken, reconcileBindingAction, externalHandoffAdoptable, handoffDecision, blockingHandoffTurnIds, terminalRecoveryBlockingTurns, terminalScopedCodexApprovalAction, rolloutBackedCodexSession } = observation;
1333
+ const association = decideManagedTerminalAssociation({
1334
+ allRelated,
1335
+ displayedRelated,
1336
+ authoritativeSession,
1337
+ sessionAuthorityRequired,
1338
+ currentOwner: ownership.state === "current"
1339
+ ? ownership.conversation
1340
+ : undefined
1341
+ });
1342
+ const { managedSessionId, sessionIds, sessionAllRelated, sessionDisplayedRelated } = association;
1343
+ // History remains useful when a pane has restarted and therefore no
1344
+ // first-class Session is authoritative for the new process incarnation.
1345
+ // Keep that display-only association separate from the send target: under
1346
+ // protocol 3, only authoritativeSession may populate managedSessionId.
1347
+ const sessionBindingMatchesLiveTerminal = authoritativeSession
1348
+ ? true
1349
+ : Boolean(!sessionAuthorityRequired &&
1350
+ sessionAllRelated.some((turn) => managedTurnMatchesLiveTerminal(turn, terminal)));
1351
+ const currentTurnValue = ownership.state === "current"
1352
+ ? currentManagedTurnForTerminal(ownership.conversation, terminal, sessionAwareRawActions)
1353
+ : undefined;
1354
+ const currentTurnProjection = currentTurnValue && (!mutationsAllowed ||
1355
+ (ownership.state === "current" &&
1356
+ conversationHasNonterminalDeferredTransfer(ownership.conversation)))
1357
+ ? readOnlyManagedTurn(currentTurnValue)
1358
+ : currentTurnValue;
1359
+ const currentTurn = currentTurnProjection
1360
+ ? withoutGenericHandoffSourceClose(currentTurnProjection, blockingHandoffTurnIds)
1361
+ : undefined;
1362
+ const nonOwnerRawActions = nonOwnerTerminalActions(sessionAwareRawActions, {
1363
+ hasAuthoritativeSession: Boolean(authoritativeSession),
1364
+ rolloutBackedCodexSession
1365
+ });
1366
+ const { recentConversation, historyConversations } = selectManagedTerminalHistory({
1367
+ displayedRelated: sessionDisplayedRelated,
1368
+ currentConversationId: stringValue(currentTurn?.conversation_id),
1369
+ hasCurrentTurn: Boolean(currentTurn),
1370
+ includeAll
1371
+ });
1372
+ const recentTurnValue = recentConversation
1373
+ ? historicalManagedTurnForTerminal(recentConversation)
1374
+ : undefined;
1375
+ const recentTurnProjection = recentTurnValue && (!mutationsAllowed ||
1376
+ Boolean(recentConversation &&
1377
+ conversationHasNonterminalDeferredTransfer(recentConversation)))
1378
+ ? readOnlyManagedTurn(recentTurnValue)
1379
+ : recentTurnValue;
1380
+ const recentTurn = recentTurnProjection
1381
+ ? withoutGenericHandoffSourceClose(recentTurnProjection, blockingHandoffTurnIds)
1382
+ : undefined;
1383
+ const history = historyConversations.map((conversation) => {
1384
+ const turn = historicalManagedTurnForTerminal(conversation);
1385
+ return withoutGenericHandoffSourceClose(mutationsAllowed &&
1386
+ !conversationHasNonterminalDeferredTransfer(conversation)
1387
+ ? turn
1388
+ : readOnlyManagedTurn(turn), blockingHandoffTurnIds);
1389
+ });
1390
+ const visibleTurnIds = new Set([currentTurn, recentTurn, ...history]
1391
+ .map((turn) => stringValue(turn?.conversation_id))
1392
+ .filter((id) => id !== undefined));
1393
+ const managedSessionShortReference = managedSessionId
1394
+ ? sessionShortRef(managedSessionId)
1395
+ : null;
1396
+ const management = projectTerminalManagement({
1397
+ managedSessionId,
1398
+ managedSessionShortRef: managedSessionShortReference,
1399
+ currentTurn,
1400
+ recentTurn,
1401
+ sessionAllRelatedCount: sessionAllRelated.length,
1402
+ hiddenTurnCount: sessionAllRelated.filter((conversation) => !visibleTurnIds.has(conversation.conversation_id)).length,
1403
+ sessionCount: new Set([
1404
+ ...sessionIds,
1405
+ ...relatedSessions.map((session) => session.session_id)
1406
+ ]).size,
1407
+ authoritativeSession,
1408
+ history: includeAll ? history : undefined
1409
+ });
1410
+ const sendAuthority = decideTerminalSendAuthority({
1411
+ ownership: ownership.state,
1412
+ verifiedEmptyToken: verifiedEmptyCodexSnapshotToken,
1413
+ externalToken: externalHandoffAdoptable
1414
+ ? externalHandoffSnapshotToken
1415
+ : undefined,
1416
+ deferredToken: ownership.state !== "conflict" ||
1417
+ deferredCodexSourceRolloutAuthority ===
1418
+ "explicitly_abandoned_predecessor"
1419
+ ? deferredCodexForegroundToken
1420
+ : undefined,
1421
+ managedSendSessionId: managedSessionId &&
1422
+ !rolloutBackedCodexSession &&
1423
+ sessionBindingMatchesLiveTerminal &&
1424
+ isRecord(sessionAwareRawActions.send)
1425
+ ? managedSessionId
1426
+ : undefined
1427
+ });
1428
+ const tokenSendAction = sendAuthority.mode === "external_handoff"
1429
+ ? rawSendAction
1430
+ : sendAuthority.mode === "verified_empty" ||
1431
+ sendAuthority.mode === "deferred"
1432
+ ? verifiedEmptyRawSendAction
1433
+ : undefined;
1434
+ const authoritativeSendAction = sendAuthority.mode === "managed" && isRecord(sessionAwareRawActions.send)
1435
+ ? sendActionForManagedSession(sessionAwareRawActions.send, sendAuthority.sessionId)
1436
+ : tokenSendAction &&
1437
+ "token" in sendAuthority &&
1438
+ sendAuthority.token
1439
+ ? {
1440
+ ...tokenSendAction,
1441
+ arguments: {
1442
+ ...(isRecord(tokenSendAction.arguments)
1443
+ ? tokenSendAction.arguments
1444
+ : {}),
1445
+ expected_terminal_token: sendAuthority.token
1446
+ }
1447
+ }
1448
+ : undefined;
1449
+ const availableActions = selectTerminalAvailableActions({
1450
+ ownership: ownership.state,
1451
+ currentActions: ownership.state === "current"
1452
+ ? currentTerminalActions(currentTurn)
1453
+ : {},
1454
+ sessionAwareRawActions: sessionAwareRawActions,
1455
+ nonOwnerRawActions,
1456
+ authoritativeSendAction,
1457
+ reconcileBindingAction,
1458
+ terminalScopedApprovalAction: terminalScopedCodexApprovalAction,
1459
+ isAction: isRecord
1460
+ });
1461
+ // An explicit undefined rollout prevents a lingering resolver rollout from
1462
+ // being presented as the authoritative status-card thread.
1463
+ const authoritativeIdentity = authoritativeTerminalIdentity(authoritativeSession);
1464
+ const publicManagementConflict = ownership.state === "conflict"
1465
+ ? projectPublicManagementConflict({
1466
+ conflict: ownership.conflict,
1467
+ verifiedEmptyToken: verifiedEmptyCodexSnapshotToken,
1468
+ deferredToken: deferredCodexForegroundToken,
1469
+ explicitlyAbandonedPredecessor: deferredCodexSourceRolloutAuthority ===
1470
+ "explicitly_abandoned_predecessor"
1471
+ })
1472
+ : undefined;
1473
+ const handoffPresentation = projectHandoffPresentation({
1474
+ externalHandoffDetected,
1475
+ externalHandoffAdoptable,
1476
+ recoveryBlockingTurnCount: terminalRecoveryBlockingTurns.length,
1477
+ hasHandoffDecision: Boolean(handoffDecision),
1478
+ sourceBlockingTurnCount: handoffSourceBlockingTurns.length,
1479
+ automatedInputComposerReady: automatedInputComposerReady === true,
1480
+ verifiedEmptyToken: verifiedEmptyCodexSnapshotToken
1481
+ });
1482
+ return {
1483
+ ...publicTerminal,
1484
+ ...authoritativeIdentity,
1485
+ management_state: ownership.state === "conflict"
1486
+ ? "conflict"
1487
+ : ownership.state === "current" || Boolean(authoritativeSession)
1488
+ ? "managed"
1489
+ : "unmanaged",
1490
+ ...(ownership.state === "conflict"
1491
+ ? { management_conflict: publicManagementConflict }
1492
+ : {}),
1493
+ ...handoffPresentation,
1494
+ ...(handoffDecision ? { handoff_decision: handoffDecision } : {}),
1495
+ ...(terminalRecoveryBlockingTurns.length > 0
1496
+ ? {
1497
+ blocking_turns: terminalRecoveryBlockingTurns.map((turn) => projectBlockingTurn({
1498
+ sessionId: sessionIdForConversation(turn),
1499
+ turnId: turnIdForConversation(turn),
1500
+ status: turn.status,
1501
+ recoveryTurnId: turnIdForConversation(turn)
1502
+ }))
1503
+ }
1504
+ : {}),
1505
+ managed: management,
1506
+ available_actions: availableActions
1507
+ };
1508
+ }
1509
+ function terminalFirstListProjection({ storeDir, terminals, managedSessions, sessionAuthorityRequired, allConversations, displayedConversations, includeAll, managedOnly, statusFilter, mutationsAllowed }) {
1510
+ const nonterminalDeferredTransfers = listDeferredForegroundTransfers(storeDir).filter((transfer) => !isFinalDeferredForegroundTransferStatus(transfer.status));
1511
+ const nonterminalDeferredTransferIds = new Set(nonterminalDeferredTransfers.map((transfer) => transfer.transfer_id));
1512
+ const nonterminalDeferredSourceTurnIds = new Set(nonterminalDeferredTransfers.flatMap((transfer) => transfer.version === 2 &&
1513
+ transfer.source_kind === "candidate_rollout_quiescent"
1514
+ ? (transfer.source_turn_history ?? []).map((turn) => turn.turn_id)
1515
+ : []));
1516
+ const conversationHasNonterminalDeferredTransfer = (conversation) => {
1517
+ if (nonterminalDeferredSourceTurnIds.has(turnIdForConversation(conversation))) {
1518
+ return true;
1519
+ }
1520
+ const takeover = isRecord(conversation.native_session_takeover)
1521
+ ? conversation.native_session_takeover
1522
+ : undefined;
1523
+ const transferId = stringValue(takeover?.deferred_foreground_transfer_id);
1524
+ return Boolean(transferId && nonterminalDeferredTransferIds.has(transferId));
1525
+ };
1526
+ const discoveredTerminalControls = terminals.flatMap((terminal) => {
1527
+ const control = isRecord(terminal.terminal_control)
1528
+ ? terminal.terminal_control
1529
+ : undefined;
1530
+ return control ? [control] : [];
1531
+ });
1532
+ const projectionContext = {
1533
+ storeDir,
1534
+ terminals,
1535
+ managedSessions,
1536
+ sessionAuthorityRequired,
1537
+ allConversations,
1538
+ displayedConversations,
1539
+ includeAll,
1540
+ mutationsAllowed,
1541
+ nonterminalDeferredTransfers,
1542
+ conversationHasNonterminalDeferredTransfer
1543
+ };
1544
+ const projectedTerminals = terminals.map((terminal) => renderTerminalFirstListEntry(terminal, projectionContext, observeTerminalListActionAuthority(terminal, projectionContext, observeTerminalListBindingAuthority(terminal, projectionContext))));
1545
+ const unavailableManagedTurns = displayedConversations
1546
+ .filter((conversation) => {
1547
+ const managedControl = terminalControlForManagedConversation(conversation);
1548
+ if (discoveredTerminalControls.some((control) => terminalControlsShareIncarnation(managedControl, control))) {
1549
+ return false;
1550
+ }
1551
+ return (includeAll ||
1552
+ managedOnly ||
1553
+ statusFilter !== undefined ||
1554
+ managedTurnNeedsAttention(conversation));
1555
+ })
1556
+ .sort(compareManagedConversationRecency)
1557
+ .map((conversation) => {
1558
+ const managedTurn = managedTurnListEntry(terminalListRuntime().summarizeConversation(conversation), {
1559
+ terminalBridge: terminalListRuntime().terminalBridgeEnabled(conversation),
1560
+ approvalState: managedListApprovalState(conversation),
1561
+ conversation
1562
+ });
1563
+ return {
1564
+ ...managedTurn,
1565
+ available_actions: mutationsAllowed &&
1566
+ !conversationHasNonterminalDeferredTransfer(conversation)
1567
+ ? safeUnavailableManagedTurnActions(isRecord(managedTurn.available_actions)
1568
+ ? managedTurn.available_actions
1569
+ : {})
1570
+ : readOnlyListActions(isRecord(managedTurn.available_actions)
1571
+ ? managedTurn.available_actions
1572
+ : {}),
1573
+ terminal_availability: {
1574
+ available: false,
1575
+ reason: managedOnly
1576
+ ? "terminal discovery was disabled by --managed-only"
1577
+ : "the referenced terminal pane is not currently available"
1578
+ }
1579
+ };
1580
+ });
1581
+ return {
1582
+ terminals: projectedTerminals,
1583
+ unavailableManagedTurns
1584
+ };
1585
+ }
1586
+ function managedSessionMatchesLiveTerminalEntry(session, terminal, storeDir) {
1587
+ const binding = session.binding;
1588
+ const liveControl = isRecord(terminal.terminal_control)
1589
+ ? terminal.terminal_control
1590
+ : undefined;
1591
+ if (session.status !== "bound" ||
1592
+ !binding ||
1593
+ session.agent !== terminal.agent ||
1594
+ binding.native_process.pid !== Number(terminal.pid)) {
1595
+ return false;
1596
+ }
1597
+ const terminalAliasMatches = terminalControlAliasMatches(binding.terminal_id, binding.terminal_control, terminal.id, liveControl);
1598
+ if (!terminalAliasMatches) {
1599
+ return false;
1600
+ }
1601
+ const workspaceMatches = terminalListRuntime().matchesConfiguredWorkspace(session.workspace, terminal.workspace ?? terminal.cwd);
1602
+ if (!workspaceMatches) {
1603
+ return false;
1604
+ }
1605
+ const observation = terminalObservationFromListEntry(terminal, session.agent);
1606
+ const evidence = {
1607
+ terminalAliasMatches,
1608
+ workspaceMatches
1609
+ };
1610
+ let decision = decideTerminalBindingMatch(session, observation, evidence);
1611
+ if (decision.state === "not_exact" &&
1612
+ decision.reason === "native_identity_mismatch" &&
1613
+ session.agent === "codex" &&
1614
+ observation.nativeIdentity.status === "resolved") {
1615
+ decision = decideTerminalBindingMatch(session, observation, {
1616
+ ...evidence,
1617
+ codexLingeringBeforeMatches: terminalListRuntime().codexLingeringBeforeIdentityMatchesSession({
1618
+ storeDir,
1619
+ session,
1620
+ identity: observation.nativeIdentity.identity
1621
+ })
1622
+ });
1623
+ }
1624
+ return decision.state === "exact";
1625
+ }
1626
+ function managedSessionClaimsLiveTerminalEntry(session, terminal) {
1627
+ const binding = session.binding;
1628
+ const liveControl = isRecord(terminal.terminal_control)
1629
+ ? terminal.terminal_control
1630
+ : undefined;
1631
+ return Boolean(binding &&
1632
+ session.agent === terminal.agent &&
1633
+ binding.native_process.pid === Number(terminal.pid) &&
1634
+ terminalControlsShareIncarnation(binding.terminal_control, liveControl));
1635
+ }
1636
+ function listedTerminalProcessIncarnation(terminal) {
1637
+ const processUuid = stringValue(terminal.native_agent_process_uuid);
1638
+ const processBirth = stringValue(terminal.native_agent_process_birth);
1639
+ if (terminal.agent !== "codex" ||
1640
+ (processUuid && processBirth)) {
1641
+ return { processUuid, processBirth };
1642
+ }
1643
+ const pid = Number(terminal.pid);
1644
+ if (!Number.isSafeInteger(pid) || pid <= 1) {
1645
+ return { processUuid, processBirth };
1646
+ }
1647
+ try {
1648
+ const incarnation = terminalListRuntime().codexProcessIncarnationForPid(pid);
1649
+ return {
1650
+ processUuid: processUuid ?? incarnation.processUuid,
1651
+ processBirth: processBirth ?? incarnation.processBirth
1652
+ };
1653
+ }
1654
+ catch {
1655
+ return { processUuid, processBirth };
1656
+ }
1657
+ }
1658
+ function managedBindingConflictKindForLiveTerminalEntry({ storeDir, session, terminal }) {
1659
+ const binding = session.binding;
1660
+ if (session.status !== "bound" ||
1661
+ !binding ||
1662
+ !managedSessionClaimsLiveTerminalEntry(session, terminal) ||
1663
+ managedSessionMatchesLiveTerminalEntry(session, terminal, storeDir)) {
1664
+ return undefined;
1665
+ }
1666
+ const livePid = Number(terminal.pid);
1667
+ const incarnation = listedTerminalProcessIncarnation(terminal);
1668
+ const relationship = processIncarnationRelationship({
1669
+ binding,
1670
+ livePid,
1671
+ liveProcessUuid: incarnation.processUuid,
1672
+ liveProcessBirth: incarnation.processBirth
1673
+ });
1674
+ if (relationship === "different") {
1675
+ return "stale_process_incarnation";
1676
+ }
1677
+ if (!terminalControlAliasMatches(binding.terminal_id, binding.terminal_control, terminal.id, isRecord(terminal.terminal_control)
1678
+ ? terminal.terminal_control
1679
+ : undefined) ||
1680
+ !terminalListRuntime().matchesConfiguredWorkspace(session.workspace, terminal.workspace ?? terminal.cwd)) {
1681
+ return "unverifiable";
1682
+ }
1683
+ return decideManagedBindingConflict({
1684
+ session,
1685
+ claimsTerminal: true,
1686
+ exactBinding: false,
1687
+ ownerConclusivelyInactive: false,
1688
+ processRelationship: relationship,
1689
+ liveNativeThreadId: stringValue(terminal.native_agent_session_id),
1690
+ statusCardNativeThreadId: stringValue(terminal.native_agent_status_card_session_id),
1691
+ managedTurnCount: provisionalManagedBindingTurnCount(storeDir, session)
1692
+ });
1693
+ }
1694
+ function provisionalManagedBindingTurnCount(storeDir, session) {
1695
+ const binding = session.binding;
1696
+ return binding && session.lineage.created_by === "attach" &&
1697
+ !session.last_transition_id &&
1698
+ !binding.native_thread_id &&
1699
+ !binding.native_process.rollout
1700
+ ? terminalListRuntime().managedTurnsForSession(storeDir, session.session_id).length
1701
+ : undefined;
1702
+ }
1703
+ function managedSessionHasUnresolvedNativeTransition(storeDir, session) {
1704
+ const root = nativeThreadTransitionsDir(storeDir);
1705
+ if (!fs.existsSync(root)) {
1706
+ return false;
1707
+ }
1708
+ let entries;
1709
+ try {
1710
+ entries = fs.readdirSync(root, { withFileTypes: true });
1711
+ }
1712
+ catch {
1713
+ return true;
1714
+ }
1715
+ for (const entry of entries) {
1716
+ if (!entry.isDirectory()) {
1717
+ continue;
1718
+ }
1719
+ let transition;
1720
+ try {
1721
+ transition = loadNativeThreadTransition(storeDir, entry.name);
1722
+ }
1723
+ catch {
1724
+ return true;
1725
+ }
1726
+ if (transition.source_session_id !== session.session_id &&
1727
+ transition.target_session_id !== session.session_id) {
1728
+ continue;
1729
+ }
1730
+ if (!["committed", "aborted"].includes(transition.status)) {
1731
+ return true;
1732
+ }
1733
+ }
1734
+ return false;
1735
+ }
1736
+ function managedSessionHasAnyNativeTransition(storeDir, session) {
1737
+ const root = nativeThreadTransitionsDir(storeDir);
1738
+ if (!fs.existsSync(root)) {
1739
+ return false;
1740
+ }
1741
+ let entries;
1742
+ try {
1743
+ entries = fs.readdirSync(root, { withFileTypes: true });
1744
+ }
1745
+ catch {
1746
+ return true;
1747
+ }
1748
+ for (const entry of entries) {
1749
+ if (!entry.isDirectory()) {
1750
+ continue;
1751
+ }
1752
+ let transition;
1753
+ try {
1754
+ transition = loadNativeThreadTransition(storeDir, entry.name);
1755
+ }
1756
+ catch {
1757
+ return true;
1758
+ }
1759
+ if (transition.source_session_id === session.session_id ||
1760
+ transition.target_session_id === session.session_id) {
1761
+ return true;
1762
+ }
1763
+ }
1764
+ return false;
1765
+ }
1766
+ function terminalControlForManagedConversation(conversation) {
1767
+ return terminalListRuntime().terminalControlFromTakeover(isRecord(conversation.native_session_takeover)
1768
+ ? conversation.native_session_takeover
1769
+ : undefined);
1770
+ }
1771
+ function terminalIncarnationBlockingTurns(storeDir, terminalControl, conversations = listConversations(storeDir)) {
1772
+ return conversations
1773
+ .filter(terminalListRuntime().isDiscoverableTmuxConversation)
1774
+ .filter((turn) => terminalControlsShareIncarnation(terminalControlForManagedConversation(turn), terminalControl) &&
1775
+ isSessionSendBlockingStatus(turn.status))
1776
+ .sort(compareManagedConversationRecency);
1777
+ }
1778
+ function managedTurnNeedsAttention(conversation) {
1779
+ return terminalManagedTurnNeedsAttention({
1780
+ status: conversation.status,
1781
+ get callbackDeliveryStatus() {
1782
+ const delivery = isRecord(conversation.callback_delivery)
1783
+ ? conversation.callback_delivery
1784
+ : undefined;
1785
+ return String(delivery?.status ?? "");
1786
+ }
1787
+ });
1788
+ }
1789
+ function assertTerminalIncarnationCanStartTurn(storeDir, terminalControl) {
1790
+ const blocker = terminalIncarnationBlockingTurns(storeDir, terminalControl)[0];
1791
+ if (!blocker) {
1792
+ return;
1793
+ }
1794
+ throw new Error(`terminal ${terminalControl.target} still has unresolved Turn ` +
1795
+ `${turnIdForConversation(blocker)} (${blocker.status})`);
1796
+ }
1797
+ function terminalDispatchOwnership(terminalControl) {
1798
+ let ledger;
1799
+ try {
1800
+ ledger = terminalListRuntime().loadTerminalBridgeDispatchLedger(terminalControl);
1801
+ }
1802
+ catch (error) {
1803
+ const decision = dispatch.decideTerminalDispatchOwnership("unreadable");
1804
+ return {
1805
+ state: "conflict",
1806
+ conflict: {
1807
+ reason: decision.code === "ledger_unreadable"
1808
+ ? error instanceof Error ? error.message : String(error)
1809
+ : "terminal dispatch ledger is unreadable",
1810
+ recovery: "inspect the shared terminal pane before performing a side effect"
1811
+ }
1812
+ };
1813
+ }
1814
+ const ledgerAuthority = !ledger
1815
+ ? "absent"
1816
+ : ledger.status === "resolved"
1817
+ ? "resolved"
1818
+ : terminalListRuntime().terminalDispatchRecordMatchesControl(ledger, terminalControl, {
1819
+ requireProcessAnchor: false
1820
+ }) && !terminalListRuntime().terminalDispatchRecordMatchesControl(ledger, terminalControl)
1821
+ ? "stale_process_incarnation"
1822
+ : dispatch.isActiveTerminalDispatchStatus(String(ledger.status))
1823
+ ? "active"
1824
+ : "inactive_status";
1825
+ let decision = dispatch.decideTerminalDispatchOwnership(ledgerAuthority);
1826
+ if (decision.state === "none" || !ledger)
1827
+ return { state: "none" };
1828
+ const owner = terminalListRuntime().loadTerminalDispatchLedgerOwner(ledger);
1829
+ const ledgerMessageId = stringValue(ledger.message_id);
1830
+ const ownerAuthority = !owner
1831
+ ? "unavailable"
1832
+ : isTerminalDispatchOwnerReleasedStatus(owner.status)
1833
+ ? "released"
1834
+ : !terminalControlsShareIncarnation(terminalControlForManagedConversation(owner), terminalControl)
1835
+ ? "terminal_mismatch"
1836
+ : ledgerMessageId && stringValue(isRecord(owner.native_session_takeover)
1837
+ ? owner.native_session_takeover.terminal_bridge_message_id
1838
+ : undefined) !== ledgerMessageId
1839
+ ? "generation_mismatch"
1840
+ : "current";
1841
+ decision = dispatch.decideTerminalDispatchOwnership(ledgerAuthority, ownerAuthority);
1842
+ if (decision.state === "none")
1843
+ return { state: "none" };
1844
+ if (decision.state === "current" && owner) {
1845
+ return { state: "current", conversation: owner };
1846
+ }
1847
+ const reason = decision.state === "conflict" &&
1848
+ decision.code === "owner_terminal_mismatch"
1849
+ ? "dispatch owner does not reference this terminal pane incarnation"
1850
+ : decision.state === "conflict" &&
1851
+ decision.code === "owner_generation_mismatch"
1852
+ ? "dispatch generation does not match the owner state"
1853
+ : "dispatch owner state is unavailable";
1854
+ return {
1855
+ state: "conflict",
1856
+ conflict: projectTerminalDispatchConflict({
1857
+ reason,
1858
+ dispatchStatus: stringValue(ledger.status),
1859
+ ownerConversationId: stringValue(ledger.conversation_id),
1860
+ messageId: stringValue(ledger.message_id)
1861
+ })
1862
+ };
1863
+ }
1864
+ function localTerminalDispatchOwnership(ledgerOwner, localConversations, terminal) {
1865
+ const localOwner = localConversations.find((conversation) => conversation.conversation_id === ledgerOwner.conversation_id &&
1866
+ sameCanonicalStatePath(conversation.state_path, ledgerOwner.state_path));
1867
+ return decideLocalTerminalDispatchOwnership({
1868
+ ledgerOwnerId: ledgerOwner.conversation_id,
1869
+ localOwner,
1870
+ localOwnerMatchesLiveTerminal: localOwner
1871
+ ? managedTurnMatchesLiveTerminal(localOwner, terminal)
1872
+ : false
1873
+ });
1874
+ }
1875
+ function terminalScopedCodexApprovalBoundary({ storeDir, terminal, session, ledger, approval, owner }) {
1876
+ const terminalRecord = isRecord(terminal) ? terminal : {};
1877
+ const terminalControl = isRecord(terminalRecord.terminal_control)
1878
+ ? terminalRecord.terminal_control
1879
+ : undefined;
1880
+ const control = terminalControl;
1881
+ const terminalId = String(terminalRecord.id);
1882
+ const relatedBoundSessionIds = () => listManagedSessions(storeDir)
1883
+ .filter((candidate) => candidate.status === "bound" &&
1884
+ candidate.agent === "codex" &&
1885
+ candidate.binding?.native_process.pid === Number(terminalRecord.pid) &&
1886
+ terminalControlsShareIncarnation(candidate.binding?.terminal_control, terminalControl))
1887
+ .map((candidate) => candidate.session_id);
1888
+ const blockingTurnIds = () => terminalIncarnationBlockingTurns(storeDir, control)
1889
+ .map((turn) => turnIdForConversation(turn));
1890
+ const hasDeferredRecovery = () => listDeferredForegroundTransfers(storeDir).some((transfer) => !isFinalDeferredForegroundTransferStatus(transfer.status) &&
1891
+ (transfer.source_session_id === session.session_id ||
1892
+ transfer.target_session_id === session.session_id ||
1893
+ (transfer.terminal_id === terminalId &&
1894
+ terminalControlEvidenceMatches(transfer.terminal_endpoint, control))));
1895
+ const commonChecks = {
1896
+ relatedBoundSessionIds, blockingTurnIds,
1897
+ hasNativeTransition: () => managedSessionHasAnyNativeTransition(storeDir, session),
1898
+ hasDeferredRecovery,
1899
+ ledgerMatchesTerminal: () => terminalListRuntime().terminalDispatchRecordMatchesControl(ledger, control)
1900
+ };
1901
+ if (owner && ledger) {
1902
+ return decideTerminalScopedCodexApprovalAuthority({
1903
+ kind: "current_dispatch_owner", storeDir, terminal, owner, session,
1904
+ ledger, approval,
1905
+ checks: {
1906
+ ...commonChecks,
1907
+ assertDispatchOwner: () => terminalListRuntime().assertManagedTerminalDispatchOwner({
1908
+ storeDir, conversation: owner, terminalControl: control, action: "approve"
1909
+ }),
1910
+ ownerMatchesNativeIdentity: (identity) => nativeAgentIdentityMatchesTurn(owner, identity
1911
+ ? { ...identity, evidence: "terminal_scoped_approval" }
1912
+ : undefined)
1913
+ }
1914
+ });
1915
+ }
1916
+ return decideTerminalScopedCodexApprovalAuthority({
1917
+ kind: "managed_session_no_dispatch_owner", storeDir, terminal, session,
1918
+ ledger, approval,
1919
+ checks: {
1920
+ ...commonChecks,
1921
+ dispatchOwnershipIsNone: () => terminalDispatchOwnership(control).state === "none",
1922
+ hasOrphanedDispatch: () => Boolean(terminalListRuntime().orphanedTerminalDispatchForRecovery(control))
1923
+ }
1924
+ });
1925
+ }
1926
+ async function resolveTerminalScopedCodexApproval({ options, terminal, approvalSnapshot }) {
1927
+ if (terminal.agent !== "codex") {
1928
+ return { state: "blocked", reason: "raw Claude approval remains unsupported" };
1929
+ }
1930
+ const storeDir = path.resolve(terminalListRuntime().storeDirFromOptions(options));
1931
+ let ledger;
1932
+ try {
1933
+ ledger = terminalListRuntime().loadTerminalBridgeDispatchLedger(terminal.terminalControl);
1934
+ }
1935
+ catch (error) {
1936
+ return {
1937
+ state: "blocked",
1938
+ reason: error instanceof Error ? error.message : String(error)
1939
+ };
1940
+ }
1941
+ const relatedSessions = listManagedSessions(storeDir).filter((session) => session.binding?.native_process.pid === terminal.pid &&
1942
+ terminalControlsShareIncarnation(session.binding?.terminal_control, terminal.terminalControl));
1943
+ const relatedTurns = listConversations(storeDir).filter((turn) => terminalControlsShareIncarnation(terminalControlForManagedConversation(turn), terminal.terminalControl));
1944
+ if (ledger?.status === "uncertain") {
1945
+ return {
1946
+ state: "blocked",
1947
+ reason: "terminal dispatch is uncertain; inspect the pane and do not retry approval"
1948
+ };
1949
+ }
1950
+ const ownership = terminalDispatchOwnership(terminal.terminalControl);
1951
+ if (ownership.state === "conflict") {
1952
+ return {
1953
+ state: "blocked",
1954
+ reason: stringValue(ownership.conflict.reason) ??
1955
+ "terminal dispatch ownership is conflicted"
1956
+ };
1957
+ }
1958
+ if (ownership.state === "none" &&
1959
+ relatedSessions.length === 0 &&
1960
+ relatedTurns.length === 0) {
1961
+ return { state: "unmanaged" };
1962
+ }
1963
+ const owner = ownership.state === "current"
1964
+ ? ownership.conversation
1965
+ : undefined;
1966
+ if (owner) {
1967
+ const ownerStoreDir = terminalListRuntime().managedSessionStoreDirForConversation(owner);
1968
+ if (!ownerStoreDir || path.resolve(ownerStoreDir) !== storeDir) {
1969
+ return {
1970
+ state: "blocked",
1971
+ reason: "terminal dispatch owner belongs to another AKK Store"
1972
+ };
1973
+ }
1974
+ }
1975
+ const ownerSession = owner
1976
+ ? tryLoadManagedSession(storeDir, sessionIdForConversation(owner))
1977
+ : undefined;
1978
+ if (owner && !ownerSession) {
1979
+ return {
1980
+ state: "blocked",
1981
+ reason: "terminal dispatch owner has no current managed Session"
1982
+ };
1983
+ }
1984
+ let identityContext;
1985
+ try {
1986
+ identityContext = terminalListRuntime().codexManagedIdentityResolutionContext({
1987
+ storeDir,
1988
+ terminal
1989
+ });
1990
+ }
1991
+ catch {
1992
+ // The terminal-scoped path deliberately tolerates an unavailable Codex
1993
+ // rollout resolver. Store, pane, process, owner, and prompt fences below
1994
+ // remain mandatory.
1995
+ }
1996
+ const observation = await terminalListRuntime().observeCurrentNativeAgentSessionIdentity({
1997
+ options,
1998
+ agent: "codex",
1999
+ pid: terminal.pid,
2000
+ cwd: terminal.terminalControl.currentPath,
2001
+ preferredSessionId: identityContext?.preferredSessionId,
2002
+ allowedCompanionIdentity: identityContext?.companions.primary,
2003
+ allowedAdditionalIdentities: identityContext?.companions.additional
2004
+ });
2005
+ let processIncarnation;
2006
+ try {
2007
+ processIncarnation = terminalListRuntime().codexProcessIncarnationForPid(terminal.pid);
2008
+ }
2009
+ catch (error) {
2010
+ return {
2011
+ state: "blocked",
2012
+ reason: error instanceof Error ? error.message : String(error)
2013
+ };
2014
+ }
2015
+ const identity = observation.status === "resolved"
2016
+ ? observation.identity
2017
+ : undefined;
2018
+ let approval = approvalSnapshot;
2019
+ if (!approval) {
2020
+ try {
2021
+ const terminalStatus = await terminalListRuntime().terminalStatusForControl("codex", terminal.terminalControl, options, {
2022
+ pid: terminal.pid,
2023
+ cwd: terminal.terminalControl.currentPath,
2024
+ conversationId: terminal.conversationId,
2025
+ terminalTarget: terminal.terminalControl.target
2026
+ });
2027
+ approval = terminalScopedCodexApprovalPromptSnapshot(terminalStatus.approval_state);
2028
+ }
2029
+ catch (error) {
2030
+ return {
2031
+ state: "blocked",
2032
+ reason: error instanceof Error ? error.message : String(error)
2033
+ };
2034
+ }
2035
+ }
2036
+ if (!approval) {
2037
+ return {
2038
+ state: "blocked",
2039
+ reason: "terminal-scoped Codex approval requires one fresh exact approval prompt"
2040
+ };
2041
+ }
2042
+ const terminalSnapshot = {
2043
+ id: terminal.conversationId,
2044
+ source: "terminal",
2045
+ agent: "codex",
2046
+ pid: terminal.pid,
2047
+ cwd: terminal.terminalControl.currentPath,
2048
+ workspace: terminal.terminalControl.currentPath,
2049
+ terminal_control: terminal.terminalControl,
2050
+ native_agent_session_id: identity?.sessionId,
2051
+ native_agent_process_uuid: identity?.processUuid ?? processIncarnation.processUuid,
2052
+ native_agent_process_birth: identity?.processBirth ?? processIncarnation.processBirth,
2053
+ native_agent_rollout: identity?.rollout,
2054
+ native_agent_identity_observation: { status: observation.status },
2055
+ approval_state: {
2056
+ approvable: true,
2057
+ fingerprint: approval.fingerprint,
2058
+ keys: approval.keys,
2059
+ decision_mode: "keys",
2060
+ request_id: approval.requestId
2061
+ }
2062
+ };
2063
+ try {
2064
+ if (owner && ownerSession && ledger) {
2065
+ return {
2066
+ state: "eligible",
2067
+ boundary: terminalScopedCodexApprovalBoundary({
2068
+ storeDir,
2069
+ terminal: terminalSnapshot,
2070
+ owner,
2071
+ session: ownerSession,
2072
+ ledger,
2073
+ approval
2074
+ })
2075
+ };
2076
+ }
2077
+ if (ownership.state !== "none") {
2078
+ throw new Error("managed terminal approval has no current exact dispatch owner");
2079
+ }
2080
+ if (ledger && ledger.status !== "resolved") {
2081
+ throw new Error(`terminal-scoped Codex approval cannot use ${String(ledger.status)} dispatch ownership`);
2082
+ }
2083
+ const matchingSessions = relatedSessions.filter((session) => managedSessionMatchesLiveTerminalEntry(session, terminalSnapshot, storeDir));
2084
+ if (matchingSessions.length !== 1) {
2085
+ throw new Error("terminal-scoped Codex approval has no single exact managed Session");
2086
+ }
2087
+ return {
2088
+ state: "eligible",
2089
+ boundary: terminalScopedCodexApprovalBoundary({
2090
+ storeDir,
2091
+ terminal: terminalSnapshot,
2092
+ session: matchingSessions[0],
2093
+ ledger,
2094
+ approval
2095
+ })
2096
+ };
2097
+ }
2098
+ catch (error) {
2099
+ return {
2100
+ state: "blocked",
2101
+ reason: error instanceof Error ? error.message : String(error)
2102
+ };
2103
+ }
2104
+ }
2105
+ function historicalManagedTurnForTerminal(conversation) {
2106
+ return renderHistoricalManagedTurn(managedTurnListEntry(terminalListRuntime().summarizeConversation(conversation), {
2107
+ terminalBridge: terminalListRuntime().terminalBridgeEnabled(conversation),
2108
+ approvalState: managedListApprovalState(conversation),
2109
+ conversation
2110
+ }));
2111
+ }
2112
+ function managedTurnMatchesLiveTerminal(conversation, terminal) {
2113
+ const takeover = isRecord(conversation.native_session_takeover)
2114
+ ? conversation.native_session_takeover
2115
+ : undefined;
2116
+ const liveControl = isRecord(terminal.terminal_control)
2117
+ ? terminal.terminal_control
2118
+ : undefined;
2119
+ const storedControl = terminalListRuntime().terminalControlFromTakeover(takeover);
2120
+ const livePid = Number(terminal.pid);
2121
+ const storedPid = Number(takeover?.terminal_agent_pid);
2122
+ if (executorForConversation(conversation).kind !== terminal.agent ||
2123
+ !Number.isSafeInteger(livePid) ||
2124
+ livePid <= 1 ||
2125
+ storedPid !== livePid ||
2126
+ !terminalControlAliasMatches(stringValue(takeover?.native_session_id), storedControl, stringValue(terminal.id), liveControl) ||
2127
+ !terminalControlsShareIncarnation(storedControl, liveControl)) {
2128
+ return false;
2129
+ }
2130
+ const liveSessionId = stringValue(terminal.native_agent_session_id);
2131
+ const liveProcessUuid = stringValue(terminal.native_agent_process_uuid);
2132
+ const liveProcessBirth = stringValue(terminal.native_agent_process_birth);
2133
+ const liveRollout = isRecord(terminal.native_agent_rollout)
2134
+ ? terminal.native_agent_rollout
2135
+ : undefined;
2136
+ const liveNativeIdentity = liveSessionId
2137
+ ? {
2138
+ sessionId: liveSessionId,
2139
+ ...(liveProcessUuid ? { processUuid: liveProcessUuid } : {}),
2140
+ ...(liveProcessBirth ? { processBirth: liveProcessBirth } : {}),
2141
+ ...(liveRollout
2142
+ ? {
2143
+ rollout: {
2144
+ fd: String(liveRollout.fd ?? ""),
2145
+ device: String(liveRollout.device ?? ""),
2146
+ inode: String(liveRollout.inode ?? ""),
2147
+ path: String(liveRollout.path ?? "")
2148
+ }
2149
+ }
2150
+ : {}),
2151
+ evidence: "live_terminal"
2152
+ }
2153
+ : undefined;
2154
+ if (!nativeAgentIdentityMatchesTurn(conversation, liveNativeIdentity)) {
2155
+ return false;
2156
+ }
2157
+ const liveWorkspace = terminal.workspace ?? terminal.cwd;
2158
+ if (!terminalListRuntime().matchesConfiguredWorkspace(conversation.workspace, liveWorkspace)) {
2159
+ return false;
2160
+ }
2161
+ const livePanePath = liveControl?.currentPath;
2162
+ if (livePanePath !== undefined &&
2163
+ !terminalListRuntime().matchesConfiguredWorkspace(conversation.workspace, livePanePath)) {
2164
+ return false;
2165
+ }
2166
+ return true;
2167
+ }
2168
+ function currentManagedTurnForTerminal(conversation, terminal, rawTerminalActions) {
2169
+ const managedTurn = managedTurnListEntry(terminalListRuntime().summarizeConversation(conversation), {
2170
+ terminalBridge: terminalListRuntime().terminalBridgeEnabled(conversation),
2171
+ approvalState: managedListApprovalState(conversation),
2172
+ conversation
2173
+ });
2174
+ const rawApproval = isRecord(rawTerminalActions.approve)
2175
+ ? rawTerminalActions.approve
2176
+ : undefined;
2177
+ if (!rawApproval || executorForConversation(conversation).kind !== "codex") {
2178
+ return managedTurn;
2179
+ }
2180
+ return renderCurrentManagedTurn(managedTurn, {
2181
+ isCodex: true,
2182
+ ownerId: conversation.conversation_id,
2183
+ rawApproval,
2184
+ terminalApprovalState: () => isRecord(terminal.approval_state)
2185
+ ? terminal.approval_state
2186
+ : undefined
2187
+ });
2188
+ }
2189
+ async function listStateForTerminal(agent, terminalControl, options, bridge = terminalListRuntime().createTerminalAgentBridge(options), runtime) {
2190
+ if (options.noApprovalScan) {
2191
+ return {
2192
+ approval_state: {
2193
+ scanned: false,
2194
+ blocked: false,
2195
+ approvable: false,
2196
+ reason: "approval scan disabled"
2197
+ },
2198
+ activity_state: "unknown",
2199
+ activity_reason: "terminal screen scan disabled"
2200
+ };
2201
+ }
2202
+ try {
2203
+ const status = await bridge.status(agent, terminalControl, {
2204
+ scrollbackLines: Number(options.scrollbackLines ?? 120),
2205
+ runtime
2206
+ });
2207
+ return {
2208
+ approval_state: {
2209
+ ...status.approval_state,
2210
+ screen_excerpt: status.approval_state.blocked ? status.screen.excerpt?.slice(-1000) : undefined
2211
+ },
2212
+ activity_state: status.activity_state,
2213
+ activity_reason: status.activity_reason,
2214
+ capability_limitation: status.capability_limitation,
2215
+ _terminal_status_snapshot: status,
2216
+ // Internal projection evidence; terminalControlledListEntry selects all
2217
+ // public fields explicitly and never exposes the pane excerpt itself.
2218
+ screen_excerpt: status.screen.excerpt
2219
+ };
2220
+ }
2221
+ catch (error) {
2222
+ return {
2223
+ approval_state: {
2224
+ scanned: false,
2225
+ blocked: false,
2226
+ approvable: false,
2227
+ error: error instanceof Error ? error.message : String(error)
2228
+ },
2229
+ activity_state: "unknown",
2230
+ activity_reason: error instanceof Error ? error.message : String(error)
2231
+ };
2232
+ }
2233
+ }
2234
+ function managedListApprovalState(conversation) {
2235
+ if (!terminalListRuntime().terminalBridgeEnabled(conversation) ||
2236
+ !["waiting_for_agent", "waiting_for_openclaw"].includes(String(conversation.status))) {
2237
+ return undefined;
2238
+ }
2239
+ const nativeTakeover = isRecord(conversation.native_session_takeover)
2240
+ ? conversation.native_session_takeover
2241
+ : undefined;
2242
+ if (!terminalListRuntime().terminalControlFromTakeover(nativeTakeover) ||
2243
+ !stringValue(nativeTakeover?.terminal_bridge_message_id)) {
2244
+ return undefined;
2245
+ }
2246
+ const approval = isRecord(nativeTakeover?.terminal_bridge_approval)
2247
+ ? nativeTakeover.terminal_bridge_approval
2248
+ : undefined;
2249
+ const approvalState = isRecord(approval?.approval_state)
2250
+ ? approval.approval_state
2251
+ : undefined;
2252
+ const fingerprint = stringValue(approval?.fingerprint);
2253
+ const notifiedAt = stringValue(approval?.notified_at);
2254
+ const notifiedAtMs = validTimestampMs(notifiedAt);
2255
+ if (!approvalState ||
2256
+ !fingerprint ||
2257
+ notifiedAtMs === undefined ||
2258
+ cliNowMs() - notifiedAtMs > terminalListRuntime().approvalTtlMs) {
2259
+ return undefined;
2260
+ }
2261
+ return {
2262
+ ...approvalState,
2263
+ fingerprint,
2264
+ notified_at: notifiedAt
2265
+ };
2266
+ }
2267
+ async function resolveConversationSelectorOption(commandName, options) {
2268
+ const sendOperation = commandName === "send";
2269
+ if (stringValue(options.expectedTerminalToken)) {
2270
+ // Selector resolution may replace an alias (or an omitted selector) with a
2271
+ // discovered full terminal id. Preserve the caller's actual authority so
2272
+ // the handoff token fence cannot mistake that convenience resolution for
2273
+ // an exact selector supplied by the caller.
2274
+ terminalListRuntime().rememberOriginalExpectedTerminalSelector(options, stringValue(sendOperation
2275
+ ? options.session ?? options.conversation ?? options.conversationId
2276
+ : options.turn ?? options.conversation ?? options.conversationId)?.trim());
2277
+ }
2278
+ if (!terminalListRuntime().selectorCommands.has(String(commandName ?? "")) ||
2279
+ options.state) {
2280
+ return;
2281
+ }
2282
+ const supplied = stringValue(sendOperation
2283
+ ? options.session ?? options.conversation ?? options.conversationId
2284
+ : options.turn ?? options.conversation ?? options.conversationId)?.trim();
2285
+ if (supplied && !isSessionSelectorSyntax(supplied)) {
2286
+ // Full authoritative IDs keep their existing command-specific validation
2287
+ // path. This avoids a discovery scan before option validation and preserves
2288
+ // precise downstream errors for closed or currently non-actionable state.
2289
+ if (sendOperation) {
2290
+ options.session = supplied;
2291
+ }
2292
+ else {
2293
+ options.turn = supplied;
2294
+ }
2295
+ return;
2296
+ }
2297
+ const candidates = await sessionSelectorCandidates(commandName, options);
2298
+ const resolution = resolveSessionSelector(supplied, candidates, {
2299
+ operation: commandName
2300
+ });
2301
+ if (sendOperation) {
2302
+ options.session = resolution.id;
2303
+ }
2304
+ else {
2305
+ options.turn = resolution.id;
2306
+ }
2307
+ options.conversation = resolution.id;
2308
+ delete options.conversationId;
2309
+ }
2310
+ function isSessionSelectorSyntax(value) {
2311
+ return (/^(?:only|latest|codex|claude|(?:codex|claude):latest)$/iu.test(value) ||
2312
+ /^@[0-9a-f]+$/iu.test(value));
2313
+ }
2314
+ async function sessionSelectorCandidates(commandName, options) {
2315
+ const storeDir = terminalListRuntime().storeDirFromOptions(options);
2316
+ const mutationsAllowed = inspectStoreCompatibility(storeDir).writable === true;
2317
+ const selectorStore = inspectStoreCompatibility(storeDir);
2318
+ const storedConversations = listConversations(storeDir);
2319
+ const workspaceConversations = storedConversations
2320
+ .filter((conversation) => terminalListRuntime().matchesConfiguredWorkspace(options.workspace, conversation.workspace));
2321
+ const discoverableWorkspaceConversations = workspaceConversations
2322
+ .filter(terminalListRuntime().isDiscoverableTmuxConversation);
2323
+ const managed = discoverableWorkspaceConversations.map((conversation) => managedTurnListEntry(terminalListRuntime().summarizeConversation(conversation), {
2324
+ terminalBridge: terminalListRuntime().terminalBridgeEnabled(conversation),
2325
+ approvalState: managedListApprovalState(conversation),
2326
+ conversation
2327
+ }));
2328
+ const terminalScan = await buildTerminalListGroup({
2329
+ options: {
2330
+ ...options,
2331
+ noApprovalScan: ["send", "approve", "cancel"].includes(commandName)
2332
+ ? options.noApprovalScan
2333
+ : true
2334
+ },
2335
+ agentFilter: undefined,
2336
+ statusFilter: undefined
2337
+ });
2338
+ const terminalProjection = terminalFirstListProjection({
2339
+ storeDir,
2340
+ terminals: terminalScan.terminalControlled.filter((entry) => terminalListRuntime().matchesConfiguredWorkspace(options.workspace, entry.workspace ?? entry.cwd)),
2341
+ managedSessions: selectorStore.readable
2342
+ ? listManagedSessions(storeDir).filter((session) => terminalListRuntime().matchesConfiguredWorkspace(options.workspace, session.workspace))
2343
+ : [],
2344
+ sessionAuthorityRequired: Number(selectorStore.writer_protocol) >= STORE_SESSION_AUTHORITY_PROTOCOL,
2345
+ allConversations: discoverableWorkspaceConversations,
2346
+ displayedConversations: discoverableWorkspaceConversations,
2347
+ includeAll: false,
2348
+ managedOnly: options.managedOnly === true,
2349
+ statusFilter: undefined,
2350
+ mutationsAllowed
2351
+ });
2352
+ const observedAtMs = cliNowMs();
2353
+ if (commandName === "send") {
2354
+ const sessionEntries = terminalProjection.terminals.flatMap((entry) => {
2355
+ const managedState = isRecord(entry.managed) ? entry.managed : undefined;
2356
+ const recentTurn = isRecord(managedState?.recent_turn)
2357
+ ? managedState.recent_turn
2358
+ : undefined;
2359
+ const currentTurn = isRecord(managedState?.current_turn)
2360
+ ? managedState.current_turn
2361
+ : undefined;
2362
+ const sessionId = stringValue(managedState?.session_id);
2363
+ const actions = isRecord(entry.available_actions)
2364
+ ? entry.available_actions
2365
+ : undefined;
2366
+ const sendAction = isRecord(actions?.send)
2367
+ ? actions.send
2368
+ : undefined;
2369
+ const sendArguments = isRecord(sendAction?.arguments)
2370
+ ? sendAction.arguments
2371
+ : undefined;
2372
+ if (!sessionId ||
2373
+ !sendAction ||
2374
+ stringValue(sendArguments?.session_id) !== sessionId) {
2375
+ return [];
2376
+ }
2377
+ const commonEntry = {
2378
+ agent: entry.agent,
2379
+ status: "idle",
2380
+ workspace: entry.workspace ?? entry.cwd,
2381
+ updated_at: recentTurn?.updated_at ??
2382
+ currentTurn?.updated_at,
2383
+ available_actions: {
2384
+ send: sendAction
2385
+ }
2386
+ };
2387
+ return [
2388
+ {
2389
+ ...commonEntry,
2390
+ id: sessionId,
2391
+ short_ref: sessionShortRef(sessionId),
2392
+ source: "managed_session"
2393
+ },
2394
+ {
2395
+ ...commonEntry,
2396
+ id: String(entry.id),
2397
+ short_ref: stringValue(entry.short_ref) ??
2398
+ sessionShortRef(String(entry.id)),
2399
+ source: "managed_session_terminal_alias"
2400
+ }
2401
+ ];
2402
+ });
2403
+ const rawTerminalEntries = terminalProjection.terminals.filter((entry) => {
2404
+ const managedState = isRecord(entry.managed) ? entry.managed : undefined;
2405
+ const actions = isRecord(entry.available_actions)
2406
+ ? entry.available_actions
2407
+ : undefined;
2408
+ const sendAction = isRecord(actions?.send) ? actions.send : undefined;
2409
+ const sendArguments = isRecord(sendAction?.arguments)
2410
+ ? sendAction.arguments
2411
+ : undefined;
2412
+ return (!stringValue(managedState?.session_id) ||
2413
+ !stringValue(sendArguments?.session_id));
2414
+ });
2415
+ return [
2416
+ ...managed.map((entry) => projectSelectorCandidate(entry, commandName, observedAtMs, {
2417
+ defaultActionable: false,
2418
+ mutationsAllowed
2419
+ })),
2420
+ ...sessionEntries.map((entry) => projectSelectorCandidate(entry, commandName, observedAtMs, {
2421
+ // A managed terminal's full id/@short-ref is an explicit alias for
2422
+ // its Session send target. It must not duplicate the Session in
2423
+ // omitted, only, latest, or agent-name selection.
2424
+ defaultActionable: entry.source !== "managed_session_terminal_alias",
2425
+ mutationsAllowed
2426
+ })),
2427
+ ...rawTerminalEntries.map((entry) => projectSelectorCandidate(entry, commandName, observedAtMs, {
2428
+ defaultActionable: true,
2429
+ mutationsAllowed
2430
+ }))
2431
+ ];
2432
+ }
2433
+ return [
2434
+ ...managed.map((entry) => projectSelectorCandidate(entry, commandName, observedAtMs, {
2435
+ defaultActionable: options.managedOnly === true,
2436
+ mutationsAllowed
2437
+ })),
2438
+ ...terminalProjection.terminals.map((entry) => projectSelectorCandidate(entry, commandName, observedAtMs, {
2439
+ defaultActionable: true,
2440
+ mutationsAllowed
2441
+ }))
2442
+ ];
2443
+ }
2444
+ //# sourceMappingURL=terminal-list-cli-adapter.js.map