@scotthuang/agent-knock-knock 0.12.11 → 0.12.12

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