@openrig/cli 0.6.11 → 0.6.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1322) hide show
  1. package/README.md +23 -13
  2. package/daemon/assets/claude-statusline-context.cjs +2 -0
  3. package/daemon/assets/guidance/openrig-start.md +2 -1
  4. package/daemon/assets/plugins/openrig-core/.claude-plugin/plugin.json +1 -1
  5. package/daemon/assets/plugins/openrig-core/.codex-plugin/plugin.json +1 -1
  6. package/daemon/assets/plugins/openrig-core/README.md +37 -7
  7. package/daemon/assets/plugins/openrig-core/hooks/claude.json +7 -7
  8. package/daemon/assets/plugins/openrig-core/hooks/codex.json +2 -2
  9. package/daemon/assets/plugins/openrig-core/hooks/scripts/activity-relay.cjs +93 -0
  10. package/daemon/assets/plugins/openrig-core/hooks/scripts/compaction-restore-bridge.cjs +23 -0
  11. package/daemon/assets/plugins/openrig-core/hooks/scripts/refocus-health.cjs +4 -2
  12. package/daemon/assets/plugins/openrig-core/hooks/scripts/refocus.cjs +76 -20
  13. package/daemon/assets/plugins/openrig-core/skills/claude-compaction-restore/scripts/precompact-hook.mjs +24 -0
  14. package/daemon/assets/plugins/openrig-core/skills/delegating-work/SKILL.md +25 -5
  15. package/daemon/assets/plugins/openrig-core/skills/forming-an-openrig-mental-model/SKILL.md +4 -2
  16. package/daemon/assets/plugins/openrig-core/skills/mission-slice-sop/SKILL.md +15 -1
  17. package/daemon/assets/plugins/openrig-core/skills/openrig-skills/SKILL.md +1 -1
  18. package/daemon/assets/plugins/openrig-core/skills/openrig-user/SKILL.md +172 -11
  19. package/daemon/assets/plugins/openrig-core/skills/orienting-to-an-inherited-seat/SKILL.md +7 -4
  20. package/daemon/assets/plugins/openrig-core/skills/queue-handoff/SKILL.md +8 -0
  21. package/daemon/assets/plugins/openrig-core/skills/refocusing/scripts/trace-to-root.py +61 -16
  22. package/daemon/assets/plugins/openrig-core/skills/retiring-and-inheriting-a-seat/SKILL.md +5 -2
  23. package/daemon/assets/plugins/openrig-core/skills/session-compaction-and-restore/SKILL.md +19 -0
  24. package/daemon/assets/skills/rigs/SKILL.md +63 -13
  25. package/daemon/context-packs/help/manifest.yaml +1 -1
  26. package/daemon/context-packs/onboarding-width/manifest.yaml +1 -1
  27. package/daemon/context-packs/onboarding-width/public-what-you-can-do.md +160 -43
  28. package/daemon/context-packs/reference/getting-started.md +81 -18
  29. package/daemon/context-packs/reference/manifest.yaml +1 -1
  30. package/daemon/context-packs/reference/rig-spec.md +67 -6
  31. package/daemon/context-packs/reference/whats-new.md +99 -151
  32. package/daemon/context-packs/skills/agent-operated-software/manifest.yaml +1 -1
  33. package/daemon/context-packs/skills/agent-operated-workflows/manifest.yaml +1 -1
  34. package/daemon/context-packs/skills/applying-a-permission-policy/manifest.yaml +1 -1
  35. package/daemon/context-packs/skills/claude-compaction-restore/manifest.yaml +1 -1
  36. package/daemon/context-packs/skills/claude-compaction-restore/scripts/precompact-hook.mjs +24 -0
  37. package/daemon/context-packs/skills/core/agent-starters/manifest.yaml +1 -1
  38. package/daemon/context-packs/skills/core/agent-startup-and-context-ingestion/manifest.yaml +1 -1
  39. package/daemon/context-packs/skills/core/cross-host-rig-commands/manifest.yaml +1 -1
  40. package/daemon/context-packs/skills/core/human-in-the-loop/manifest.yaml +1 -1
  41. package/daemon/context-packs/skills/core/messaging-the-human/manifest.yaml +1 -1
  42. package/daemon/context-packs/skills/core/openrig-architect/manifest.yaml +1 -1
  43. package/daemon/context-packs/skills/core/openrig-cmux/manifest.yaml +1 -1
  44. package/daemon/context-packs/skills/core/openrig-herdr/manifest.yaml +1 -1
  45. package/daemon/context-packs/skills/core/openrig-skills/SKILL.md +1 -1
  46. package/daemon/context-packs/skills/core/openrig-skills/manifest.yaml +1 -1
  47. package/daemon/context-packs/skills/core/openrig-software-factory/SKILL.md +0 -1
  48. package/daemon/context-packs/skills/core/openrig-software-factory/manifest.yaml +1 -1
  49. package/daemon/context-packs/skills/core/openrig-software-factory/references/worked-example.md +7 -4
  50. package/daemon/context-packs/skills/core/openrig-upgrade/manifest.yaml +1 -1
  51. package/daemon/context-packs/skills/core/rig-bundles-and-shareable-artifacts/manifest.yaml +1 -1
  52. package/daemon/context-packs/skills/core/rig-lifecycle/manifest.yaml +1 -1
  53. package/daemon/context-packs/skills/core/session-source-fork/manifest.yaml +1 -1
  54. package/daemon/context-packs/skills/core/specification-system/manifest.yaml +1 -1
  55. package/daemon/context-packs/skills/core/topology-mutation-and-seat-management/SKILL.md +1 -1
  56. package/daemon/context-packs/skills/core/topology-mutation-and-seat-management/manifest.yaml +1 -1
  57. package/daemon/context-packs/skills/core/watchdog/manifest.yaml +1 -1
  58. package/daemon/context-packs/skills/delegating-work/SKILL.md +25 -5
  59. package/daemon/context-packs/skills/delegating-work/manifest.yaml +3 -3
  60. package/daemon/context-packs/skills/forming-an-openrig-mental-model/SKILL.md +4 -2
  61. package/daemon/context-packs/skills/forming-an-openrig-mental-model/manifest.yaml +1 -1
  62. package/daemon/context-packs/skills/loading-addressable-markdown/manifest.yaml +1 -1
  63. package/daemon/context-packs/skills/mission-slice-sop/SKILL.md +15 -1
  64. package/daemon/context-packs/skills/mission-slice-sop/manifest.yaml +1 -1
  65. package/daemon/context-packs/skills/openrig-operating-model/manifest.yaml +1 -1
  66. package/daemon/context-packs/skills/openrig-user/SKILL.md +172 -11
  67. package/daemon/context-packs/skills/openrig-user/manifest.yaml +1 -1
  68. package/daemon/context-packs/skills/orienting-to-an-inherited-seat/SKILL.md +7 -4
  69. package/daemon/context-packs/skills/orienting-to-an-inherited-seat/manifest.yaml +1 -1
  70. package/daemon/context-packs/skills/pm/backlog-capture/manifest.yaml +1 -1
  71. package/daemon/context-packs/skills/pm/context-builder/manifest.yaml +1 -1
  72. package/daemon/context-packs/skills/pm/exec-summary/manifest.yaml +1 -1
  73. package/daemon/context-packs/skills/pm/office-hours/manifest.yaml +1 -1
  74. package/daemon/context-packs/skills/pm/plan-review/manifest.yaml +1 -1
  75. package/daemon/context-packs/skills/pm/requirements-writer/manifest.yaml +1 -1
  76. package/daemon/context-packs/skills/pm/ui-mockup/manifest.yaml +1 -1
  77. package/daemon/context-packs/skills/pods/development-team/manifest.yaml +1 -1
  78. package/daemon/context-packs/skills/pods/orchestration-team/SKILL.md +1 -1
  79. package/daemon/context-packs/skills/pods/orchestration-team/manifest.yaml +1 -1
  80. package/daemon/context-packs/skills/pods/oversight-team/manifest.yaml +1 -1
  81. package/daemon/context-packs/skills/pods/review-team/manifest.yaml +1 -1
  82. package/daemon/context-packs/skills/process/agent-browser/manifest.yaml +1 -1
  83. package/daemon/context-packs/skills/process/context-engineering/manifest.yaml +1 -1
  84. package/daemon/context-packs/skills/process/dogfood/manifest.yaml +1 -1
  85. package/daemon/context-packs/skills/process/frontend-design/manifest.yaml +1 -1
  86. package/daemon/context-packs/skills/process/systematic-debugging/manifest.yaml +1 -1
  87. package/daemon/context-packs/skills/process/test-driven-development/manifest.yaml +1 -1
  88. package/daemon/context-packs/skills/process/verification-before-completion/manifest.yaml +1 -1
  89. package/daemon/context-packs/skills/queue-handoff/SKILL.md +8 -0
  90. package/daemon/context-packs/skills/queue-handoff/manifest.yaml +1 -1
  91. package/daemon/context-packs/skills/refocusing/manifest.yaml +1 -1
  92. package/daemon/context-packs/skills/refocusing/scripts/trace-to-root.py +61 -16
  93. package/daemon/context-packs/skills/retiring-and-inheriting-a-seat/SKILL.md +5 -2
  94. package/daemon/context-packs/skills/retiring-and-inheriting-a-seat/manifest.yaml +1 -1
  95. package/daemon/context-packs/skills/seat-continuity-and-handover/manifest.yaml +1 -1
  96. package/daemon/context-packs/skills/session-compaction-and-restore/SKILL.md +19 -0
  97. package/daemon/context-packs/skills/session-compaction-and-restore/manifest.yaml +1 -1
  98. package/daemon/context-packs/skills/software-for-agents/manifest.yaml +1 -1
  99. package/daemon/context-packs/world-example/manifest.yaml +1 -1
  100. package/daemon/context-packs/world-public/manifest.yaml +1 -1
  101. package/daemon/dist/adapters/bounded-probe-exec.d.ts +45 -0
  102. package/daemon/dist/adapters/bounded-probe-exec.d.ts.map +1 -0
  103. package/daemon/dist/adapters/bounded-probe-exec.js +204 -0
  104. package/daemon/dist/adapters/bounded-probe-exec.js.map +1 -0
  105. package/daemon/dist/adapters/claude-code-adapter.d.ts +26 -2
  106. package/daemon/dist/adapters/claude-code-adapter.d.ts.map +1 -1
  107. package/daemon/dist/adapters/claude-code-adapter.js +275 -76
  108. package/daemon/dist/adapters/claude-code-adapter.js.map +1 -1
  109. package/daemon/dist/adapters/claude-resume.d.ts.map +1 -1
  110. package/daemon/dist/adapters/claude-resume.js +14 -3
  111. package/daemon/dist/adapters/claude-resume.js.map +1 -1
  112. package/daemon/dist/adapters/codex-resume.d.ts +5 -0
  113. package/daemon/dist/adapters/codex-resume.d.ts.map +1 -1
  114. package/daemon/dist/adapters/codex-resume.js +17 -3
  115. package/daemon/dist/adapters/codex-resume.js.map +1 -1
  116. package/daemon/dist/adapters/codex-runtime-adapter.d.ts +38 -1
  117. package/daemon/dist/adapters/codex-runtime-adapter.d.ts.map +1 -1
  118. package/daemon/dist/adapters/codex-runtime-adapter.js +184 -11
  119. package/daemon/dist/adapters/codex-runtime-adapter.js.map +1 -1
  120. package/daemon/dist/adapters/community-harness-runtime-adapter.d.ts +46 -0
  121. package/daemon/dist/adapters/community-harness-runtime-adapter.d.ts.map +1 -0
  122. package/daemon/dist/adapters/community-harness-runtime-adapter.js +102 -0
  123. package/daemon/dist/adapters/community-harness-runtime-adapter.js.map +1 -0
  124. package/daemon/dist/adapters/compose-services-adapter.d.ts.map +1 -1
  125. package/daemon/dist/adapters/compose-services-adapter.js +2 -1
  126. package/daemon/dist/adapters/compose-services-adapter.js.map +1 -1
  127. package/daemon/dist/adapters/kernel-authority.d.ts +8 -1
  128. package/daemon/dist/adapters/kernel-authority.d.ts.map +1 -1
  129. package/daemon/dist/adapters/kernel-authority.js +35 -10
  130. package/daemon/dist/adapters/kernel-authority.js.map +1 -1
  131. package/daemon/dist/adapters/omp-rpc-frame.d.ts +51 -0
  132. package/daemon/dist/adapters/omp-rpc-frame.d.ts.map +1 -0
  133. package/daemon/dist/adapters/omp-rpc-frame.js +153 -0
  134. package/daemon/dist/adapters/omp-rpc-frame.js.map +1 -0
  135. package/daemon/dist/adapters/pi-openrouter-charge.d.ts +67 -0
  136. package/daemon/dist/adapters/pi-openrouter-charge.d.ts.map +1 -0
  137. package/daemon/dist/adapters/pi-openrouter-charge.js +228 -0
  138. package/daemon/dist/adapters/pi-openrouter-charge.js.map +1 -0
  139. package/daemon/dist/adapters/pi-readiness.d.ts +81 -5
  140. package/daemon/dist/adapters/pi-readiness.d.ts.map +1 -1
  141. package/daemon/dist/adapters/pi-readiness.js +193 -27
  142. package/daemon/dist/adapters/pi-readiness.js.map +1 -1
  143. package/daemon/dist/adapters/pi-resume.d.ts +5 -1
  144. package/daemon/dist/adapters/pi-resume.d.ts.map +1 -1
  145. package/daemon/dist/adapters/pi-resume.js +6 -1
  146. package/daemon/dist/adapters/pi-resume.js.map +1 -1
  147. package/daemon/dist/adapters/pi-runner-protocol.d.ts +13 -0
  148. package/daemon/dist/adapters/pi-runner-protocol.d.ts.map +1 -1
  149. package/daemon/dist/adapters/pi-runner-protocol.js +21 -0
  150. package/daemon/dist/adapters/pi-runner-protocol.js.map +1 -1
  151. package/daemon/dist/adapters/pi-runner.d.ts +72 -4
  152. package/daemon/dist/adapters/pi-runner.d.ts.map +1 -1
  153. package/daemon/dist/adapters/pi-runner.js +405 -28
  154. package/daemon/dist/adapters/pi-runner.js.map +1 -1
  155. package/daemon/dist/adapters/pi-runtime-adapter.d.ts +16 -1
  156. package/daemon/dist/adapters/pi-runtime-adapter.d.ts.map +1 -1
  157. package/daemon/dist/adapters/pi-runtime-adapter.js +19 -0
  158. package/daemon/dist/adapters/pi-runtime-adapter.js.map +1 -1
  159. package/daemon/dist/adapters/preflight-exec.d.ts +3 -0
  160. package/daemon/dist/adapters/preflight-exec.d.ts.map +1 -1
  161. package/daemon/dist/adapters/preflight-exec.js +15 -7
  162. package/daemon/dist/adapters/preflight-exec.js.map +1 -1
  163. package/daemon/dist/adapters/replace-file.d.ts +8 -0
  164. package/daemon/dist/adapters/replace-file.d.ts.map +1 -0
  165. package/daemon/dist/adapters/replace-file.js +32 -0
  166. package/daemon/dist/adapters/replace-file.js.map +1 -0
  167. package/daemon/dist/adapters/stub-compaction.d.ts +1 -0
  168. package/daemon/dist/adapters/stub-compaction.d.ts.map +1 -1
  169. package/daemon/dist/adapters/stub-compaction.js +2 -0
  170. package/daemon/dist/adapters/stub-compaction.js.map +1 -1
  171. package/daemon/dist/adapters/stub-refocus.d.ts +6 -0
  172. package/daemon/dist/adapters/stub-refocus.d.ts.map +1 -0
  173. package/daemon/dist/adapters/stub-refocus.js +22 -0
  174. package/daemon/dist/adapters/stub-refocus.js.map +1 -0
  175. package/daemon/dist/adapters/stub-runner.d.ts +6 -0
  176. package/daemon/dist/adapters/stub-runner.d.ts.map +1 -1
  177. package/daemon/dist/adapters/stub-runner.js +18 -1
  178. package/daemon/dist/adapters/stub-runner.js.map +1 -1
  179. package/daemon/dist/adapters/stub-script.d.ts +2 -2
  180. package/daemon/dist/adapters/stub-script.d.ts.map +1 -1
  181. package/daemon/dist/adapters/stub-script.js +2 -2
  182. package/daemon/dist/adapters/stub-script.js.map +1 -1
  183. package/daemon/dist/adapters/tmux-control.d.ts +119 -0
  184. package/daemon/dist/adapters/tmux-control.d.ts.map +1 -0
  185. package/daemon/dist/adapters/tmux-control.js +547 -0
  186. package/daemon/dist/adapters/tmux-control.js.map +1 -0
  187. package/daemon/dist/adapters/tmux-exec.d.ts +18 -4
  188. package/daemon/dist/adapters/tmux-exec.d.ts.map +1 -1
  189. package/daemon/dist/adapters/tmux-exec.js +98 -17
  190. package/daemon/dist/adapters/tmux-exec.js.map +1 -1
  191. package/daemon/dist/adapters/tmux.d.ts +52 -15
  192. package/daemon/dist/adapters/tmux.d.ts.map +1 -1
  193. package/daemon/dist/adapters/tmux.js +232 -58
  194. package/daemon/dist/adapters/tmux.js.map +1 -1
  195. package/daemon/dist/build-info.js +3 -3
  196. package/daemon/dist/composer-prompts-surface.d.ts +2 -0
  197. package/daemon/dist/composer-prompts-surface.d.ts.map +1 -0
  198. package/daemon/dist/composer-prompts-surface.js +2 -0
  199. package/daemon/dist/composer-prompts-surface.js.map +1 -0
  200. package/daemon/dist/db/all-migrations.d.ts.map +1 -1
  201. package/daemon/dist/db/all-migrations.js +26 -0
  202. package/daemon/dist/db/all-migrations.js.map +1 -1
  203. package/daemon/dist/db/migrations/100_typing_guard_modes.d.ts +3 -0
  204. package/daemon/dist/db/migrations/100_typing_guard_modes.d.ts.map +1 -0
  205. package/daemon/dist/db/migrations/100_typing_guard_modes.js +10 -0
  206. package/daemon/dist/db/migrations/100_typing_guard_modes.js.map +1 -0
  207. package/daemon/dist/db/migrations/104_node_effort_choice.d.ts +3 -0
  208. package/daemon/dist/db/migrations/104_node_effort_choice.d.ts.map +1 -0
  209. package/daemon/dist/db/migrations/104_node_effort_choice.js +10 -0
  210. package/daemon/dist/db/migrations/104_node_effort_choice.js.map +1 -0
  211. package/daemon/dist/db/migrations/105_usage_samples_client_cost.d.ts +9 -0
  212. package/daemon/dist/db/migrations/105_usage_samples_client_cost.d.ts.map +1 -0
  213. package/daemon/dist/db/migrations/105_usage_samples_client_cost.js +14 -0
  214. package/daemon/dist/db/migrations/105_usage_samples_client_cost.js.map +1 -0
  215. package/daemon/dist/db/migrations/107_pi_usage_entries.d.ts +10 -0
  216. package/daemon/dist/db/migrations/107_pi_usage_entries.d.ts.map +1 -0
  217. package/daemon/dist/db/migrations/107_pi_usage_entries.js +38 -0
  218. package/daemon/dist/db/migrations/107_pi_usage_entries.js.map +1 -0
  219. package/daemon/dist/db/migrations/108_pi_usage_charges.d.ts +12 -0
  220. package/daemon/dist/db/migrations/108_pi_usage_charges.d.ts.map +1 -0
  221. package/daemon/dist/db/migrations/108_pi_usage_charges.js +39 -0
  222. package/daemon/dist/db/migrations/108_pi_usage_charges.js.map +1 -0
  223. package/daemon/dist/db/migrations/109_mode_posture_coexist.d.ts +5 -0
  224. package/daemon/dist/db/migrations/109_mode_posture_coexist.d.ts.map +1 -0
  225. package/daemon/dist/db/migrations/109_mode_posture_coexist.js +11 -0
  226. package/daemon/dist/db/migrations/109_mode_posture_coexist.js.map +1 -0
  227. package/daemon/dist/db/migrations/110_workflow_instance_scope.d.ts +10 -0
  228. package/daemon/dist/db/migrations/110_workflow_instance_scope.d.ts.map +1 -0
  229. package/daemon/dist/db/migrations/110_workflow_instance_scope.js +14 -0
  230. package/daemon/dist/db/migrations/110_workflow_instance_scope.js.map +1 -0
  231. package/daemon/dist/db/migrations/111_seat_env.d.ts +6 -0
  232. package/daemon/dist/db/migrations/111_seat_env.d.ts.map +1 -0
  233. package/daemon/dist/db/migrations/111_seat_env.js +11 -0
  234. package/daemon/dist/db/migrations/111_seat_env.js.map +1 -0
  235. package/daemon/dist/db/migrations/112_queue_transitions_ts_qitem.d.ts +3 -0
  236. package/daemon/dist/db/migrations/112_queue_transitions_ts_qitem.d.ts.map +1 -0
  237. package/daemon/dist/db/migrations/112_queue_transitions_ts_qitem.js +14 -0
  238. package/daemon/dist/db/migrations/112_queue_transitions_ts_qitem.js.map +1 -0
  239. package/daemon/dist/db/migrations/113_applied_launch_daemon_opt_out.d.ts +3 -0
  240. package/daemon/dist/db/migrations/113_applied_launch_daemon_opt_out.d.ts.map +1 -0
  241. package/daemon/dist/db/migrations/113_applied_launch_daemon_opt_out.js +9 -0
  242. package/daemon/dist/db/migrations/113_applied_launch_daemon_opt_out.js.map +1 -0
  243. package/daemon/dist/db/migrations/114_session_agent_app.d.ts +3 -0
  244. package/daemon/dist/db/migrations/114_session_agent_app.d.ts.map +1 -0
  245. package/daemon/dist/db/migrations/114_session_agent_app.js +11 -0
  246. package/daemon/dist/db/migrations/114_session_agent_app.js.map +1 -0
  247. package/daemon/dist/db/migrations/115_thread_seat_map_channel_key.d.ts +3 -0
  248. package/daemon/dist/db/migrations/115_thread_seat_map_channel_key.d.ts.map +1 -0
  249. package/daemon/dist/db/migrations/115_thread_seat_map_channel_key.js +28 -0
  250. package/daemon/dist/db/migrations/115_thread_seat_map_channel_key.js.map +1 -0
  251. package/daemon/dist/db/migrations/117_provider_account_readings.d.ts +3 -0
  252. package/daemon/dist/db/migrations/117_provider_account_readings.d.ts.map +1 -0
  253. package/daemon/dist/db/migrations/117_provider_account_readings.js +32 -0
  254. package/daemon/dist/db/migrations/117_provider_account_readings.js.map +1 -0
  255. package/daemon/dist/domain/activity-taxonomy.d.ts +13 -5
  256. package/daemon/dist/domain/activity-taxonomy.d.ts.map +1 -1
  257. package/daemon/dist/domain/activity-taxonomy.js +14 -2
  258. package/daemon/dist/domain/activity-taxonomy.js.map +1 -1
  259. package/daemon/dist/domain/agent-activity-store.d.ts +2 -0
  260. package/daemon/dist/domain/agent-activity-store.d.ts.map +1 -1
  261. package/daemon/dist/domain/agent-activity-store.js +14 -8
  262. package/daemon/dist/domain/agent-activity-store.js.map +1 -1
  263. package/daemon/dist/domain/agent-images/agent-image-library-service.js +1 -1
  264. package/daemon/dist/domain/agent-images/agent-image-library-service.js.map +1 -1
  265. package/daemon/dist/domain/agent-images/manifest-parser.js +1 -1
  266. package/daemon/dist/domain/agent-images/manifest-parser.js.map +1 -1
  267. package/daemon/dist/domain/agent-manifest.d.ts.map +1 -1
  268. package/daemon/dist/domain/agent-manifest.js +21 -3
  269. package/daemon/dist/domain/agent-manifest.js.map +1 -1
  270. package/daemon/dist/domain/applied-launch-observation-store.d.ts +2 -0
  271. package/daemon/dist/domain/applied-launch-observation-store.d.ts.map +1 -1
  272. package/daemon/dist/domain/applied-launch-observation-store.js +27 -3
  273. package/daemon/dist/domain/applied-launch-observation-store.js.map +1 -1
  274. package/daemon/dist/domain/bootstrap-orchestrator.d.ts +3 -0
  275. package/daemon/dist/domain/bootstrap-orchestrator.d.ts.map +1 -1
  276. package/daemon/dist/domain/bootstrap-orchestrator.js +49 -16
  277. package/daemon/dist/domain/bootstrap-orchestrator.js.map +1 -1
  278. package/daemon/dist/domain/built-in-runtimes.d.ts +10 -0
  279. package/daemon/dist/domain/built-in-runtimes.d.ts.map +1 -0
  280. package/daemon/dist/domain/built-in-runtimes.js +14 -0
  281. package/daemon/dist/domain/built-in-runtimes.js.map +1 -0
  282. package/daemon/dist/domain/bundle-archive.d.ts +3 -1
  283. package/daemon/dist/domain/bundle-archive.d.ts.map +1 -1
  284. package/daemon/dist/domain/bundle-archive.js +5 -3
  285. package/daemon/dist/domain/bundle-archive.js.map +1 -1
  286. package/daemon/dist/domain/bundle-behaviour.d.ts +20 -0
  287. package/daemon/dist/domain/bundle-behaviour.d.ts.map +1 -1
  288. package/daemon/dist/domain/bundle-behaviour.js +57 -1
  289. package/daemon/dist/domain/bundle-behaviour.js.map +1 -1
  290. package/daemon/dist/domain/bundle-integrity.d.ts +5 -2
  291. package/daemon/dist/domain/bundle-integrity.d.ts.map +1 -1
  292. package/daemon/dist/domain/bundle-integrity.js +39 -8
  293. package/daemon/dist/domain/bundle-integrity.js.map +1 -1
  294. package/daemon/dist/domain/bundle-source-resolver.d.ts +3 -1
  295. package/daemon/dist/domain/bundle-source-resolver.d.ts.map +1 -1
  296. package/daemon/dist/domain/bundle-source-resolver.js +7 -2
  297. package/daemon/dist/domain/bundle-source-resolver.js.map +1 -1
  298. package/daemon/dist/domain/bundle-types.d.ts +6 -0
  299. package/daemon/dist/domain/bundle-types.d.ts.map +1 -1
  300. package/daemon/dist/domain/bundle-types.js +26 -0
  301. package/daemon/dist/domain/bundle-types.js.map +1 -1
  302. package/daemon/dist/domain/chat-repository.d.ts.map +1 -1
  303. package/daemon/dist/domain/chat-repository.js +3 -2
  304. package/daemon/dist/domain/chat-repository.js.map +1 -1
  305. package/daemon/dist/domain/claim-service.d.ts +6 -0
  306. package/daemon/dist/domain/claim-service.d.ts.map +1 -1
  307. package/daemon/dist/domain/claim-service.js +20 -1
  308. package/daemon/dist/domain/claim-service.js.map +1 -1
  309. package/daemon/dist/domain/claude-activity-hooks.d.ts +6 -0
  310. package/daemon/dist/domain/claude-activity-hooks.d.ts.map +1 -1
  311. package/daemon/dist/domain/claude-activity-hooks.js +19 -1
  312. package/daemon/dist/domain/claude-activity-hooks.js.map +1 -1
  313. package/daemon/dist/domain/claude-compaction-enforcer.d.ts +63 -2
  314. package/daemon/dist/domain/claude-compaction-enforcer.d.ts.map +1 -1
  315. package/daemon/dist/domain/claude-compaction-enforcer.js +154 -16
  316. package/daemon/dist/domain/claude-compaction-enforcer.js.map +1 -1
  317. package/daemon/dist/domain/claude-core-plugin-env.d.ts +28 -0
  318. package/daemon/dist/domain/claude-core-plugin-env.d.ts.map +1 -0
  319. package/daemon/dist/domain/claude-core-plugin-env.js +90 -0
  320. package/daemon/dist/domain/claude-core-plugin-env.js.map +1 -0
  321. package/daemon/dist/domain/claude-managed-launch.d.ts +18 -6
  322. package/daemon/dist/domain/claude-managed-launch.d.ts.map +1 -1
  323. package/daemon/dist/domain/claude-managed-launch.js +104 -10
  324. package/daemon/dist/domain/claude-managed-launch.js.map +1 -1
  325. package/daemon/dist/domain/claude-mcp-menu.d.ts +5 -0
  326. package/daemon/dist/domain/claude-mcp-menu.d.ts.map +1 -0
  327. package/daemon/dist/domain/claude-mcp-menu.js +25 -0
  328. package/daemon/dist/domain/claude-mcp-menu.js.map +1 -0
  329. package/daemon/dist/domain/codex-thread-id.d.ts +22 -3
  330. package/daemon/dist/domain/codex-thread-id.d.ts.map +1 -1
  331. package/daemon/dist/domain/codex-thread-id.js +65 -16
  332. package/daemon/dist/domain/codex-thread-id.js.map +1 -1
  333. package/daemon/dist/domain/community-harness-presets.d.ts +38 -0
  334. package/daemon/dist/domain/community-harness-presets.d.ts.map +1 -0
  335. package/daemon/dist/domain/community-harness-presets.js +155 -0
  336. package/daemon/dist/domain/community-harness-presets.js.map +1 -0
  337. package/daemon/dist/domain/composer-prompts.d.ts +118 -0
  338. package/daemon/dist/domain/composer-prompts.d.ts.map +1 -0
  339. package/daemon/dist/domain/composer-prompts.js +165 -0
  340. package/daemon/dist/domain/composer-prompts.js.map +1 -0
  341. package/daemon/dist/domain/context-monitor.d.ts +3 -0
  342. package/daemon/dist/domain/context-monitor.d.ts.map +1 -1
  343. package/daemon/dist/domain/context-monitor.js +14 -2
  344. package/daemon/dist/domain/context-monitor.js.map +1 -1
  345. package/daemon/dist/domain/context-packs/context-pack-library-service.js +2 -2
  346. package/daemon/dist/domain/context-packs/context-pack-library-service.js.map +1 -1
  347. package/daemon/dist/domain/context-packs/manifest-parser.js +1 -1
  348. package/daemon/dist/domain/context-packs/manifest-parser.js.map +1 -1
  349. package/daemon/dist/domain/context-usage-store.d.ts +4 -0
  350. package/daemon/dist/domain/context-usage-store.d.ts.map +1 -1
  351. package/daemon/dist/domain/context-usage-store.js +1 -0
  352. package/daemon/dist/domain/context-usage-store.js.map +1 -1
  353. package/daemon/dist/domain/current-work.js +2 -2
  354. package/daemon/dist/domain/current-work.js.map +1 -1
  355. package/daemon/dist/domain/draft-aware-delivery.d.ts +32 -0
  356. package/daemon/dist/domain/draft-aware-delivery.d.ts.map +1 -0
  357. package/daemon/dist/domain/draft-aware-delivery.js +156 -0
  358. package/daemon/dist/domain/draft-aware-delivery.js.map +1 -0
  359. package/daemon/dist/domain/event-bus.d.ts +10 -1
  360. package/daemon/dist/domain/event-bus.d.ts.map +1 -1
  361. package/daemon/dist/domain/event-bus.js +25 -0
  362. package/daemon/dist/domain/event-bus.js.map +1 -1
  363. package/daemon/dist/domain/execution-view.d.ts.map +1 -1
  364. package/daemon/dist/domain/execution-view.js +76 -10
  365. package/daemon/dist/domain/execution-view.js.map +1 -1
  366. package/daemon/dist/domain/fresh-occupant-relation.d.ts +7 -0
  367. package/daemon/dist/domain/fresh-occupant-relation.d.ts.map +1 -1
  368. package/daemon/dist/domain/fresh-occupant-relation.js +44 -0
  369. package/daemon/dist/domain/fresh-occupant-relation.js.map +1 -1
  370. package/daemon/dist/domain/frontmatter.d.ts +11 -0
  371. package/daemon/dist/domain/frontmatter.d.ts.map +1 -0
  372. package/daemon/dist/domain/frontmatter.js +20 -0
  373. package/daemon/dist/domain/frontmatter.js.map +1 -0
  374. package/daemon/dist/domain/gateway/gateway-subsystem.d.ts +11 -0
  375. package/daemon/dist/domain/gateway/gateway-subsystem.d.ts.map +1 -1
  376. package/daemon/dist/domain/gateway/gateway-subsystem.js +7 -0
  377. package/daemon/dist/domain/gateway/gateway-subsystem.js.map +1 -1
  378. package/daemon/dist/domain/gateway/human-registry.d.ts +6 -1
  379. package/daemon/dist/domain/gateway/human-registry.d.ts.map +1 -1
  380. package/daemon/dist/domain/gateway/human-registry.js +7 -0
  381. package/daemon/dist/domain/gateway/human-registry.js.map +1 -1
  382. package/daemon/dist/domain/gateway/slack/bot-inbox.d.ts +54 -0
  383. package/daemon/dist/domain/gateway/slack/bot-inbox.d.ts.map +1 -0
  384. package/daemon/dist/domain/gateway/slack/bot-inbox.js +86 -0
  385. package/daemon/dist/domain/gateway/slack/bot-inbox.js.map +1 -0
  386. package/daemon/dist/domain/gateway/slack/capabilities.d.ts.map +1 -1
  387. package/daemon/dist/domain/gateway/slack/capabilities.js +1 -0
  388. package/daemon/dist/domain/gateway/slack/capabilities.js.map +1 -1
  389. package/daemon/dist/domain/gateway/slack/channel-map.d.ts +11 -1
  390. package/daemon/dist/domain/gateway/slack/channel-map.d.ts.map +1 -1
  391. package/daemon/dist/domain/gateway/slack/channel-map.js +31 -6
  392. package/daemon/dist/domain/gateway/slack/channel-map.js.map +1 -1
  393. package/daemon/dist/domain/gateway/slack/channel-recovery.d.ts +14 -2
  394. package/daemon/dist/domain/gateway/slack/channel-recovery.d.ts.map +1 -1
  395. package/daemon/dist/domain/gateway/slack/channel-recovery.js +128 -89
  396. package/daemon/dist/domain/gateway/slack/channel-recovery.js.map +1 -1
  397. package/daemon/dist/domain/gateway/slack/config.d.ts +14 -0
  398. package/daemon/dist/domain/gateway/slack/config.d.ts.map +1 -1
  399. package/daemon/dist/domain/gateway/slack/config.js +28 -0
  400. package/daemon/dist/domain/gateway/slack/config.js.map +1 -1
  401. package/daemon/dist/domain/gateway/slack/forwarding.d.ts +32 -0
  402. package/daemon/dist/domain/gateway/slack/forwarding.d.ts.map +1 -0
  403. package/daemon/dist/domain/gateway/slack/forwarding.js +76 -0
  404. package/daemon/dist/domain/gateway/slack/forwarding.js.map +1 -0
  405. package/daemon/dist/domain/gateway/slack/inbound.d.ts +103 -1
  406. package/daemon/dist/domain/gateway/slack/inbound.d.ts.map +1 -1
  407. package/daemon/dist/domain/gateway/slack/inbound.js +329 -9
  408. package/daemon/dist/domain/gateway/slack/inbound.js.map +1 -1
  409. package/daemon/dist/domain/gateway/slack/message.d.ts +28 -0
  410. package/daemon/dist/domain/gateway/slack/message.d.ts.map +1 -1
  411. package/daemon/dist/domain/gateway/slack/message.js +105 -4
  412. package/daemon/dist/domain/gateway/slack/message.js.map +1 -1
  413. package/daemon/dist/domain/gateway/slack/outbound-driver.d.ts +3 -0
  414. package/daemon/dist/domain/gateway/slack/outbound-driver.d.ts.map +1 -1
  415. package/daemon/dist/domain/gateway/slack/outbound-driver.js +1 -0
  416. package/daemon/dist/domain/gateway/slack/outbound-driver.js.map +1 -1
  417. package/daemon/dist/domain/gateway/slack/queue-access.d.ts +1 -1
  418. package/daemon/dist/domain/gateway/slack/queue-access.d.ts.map +1 -1
  419. package/daemon/dist/domain/gateway/slack/recovery-budget.d.ts +24 -0
  420. package/daemon/dist/domain/gateway/slack/recovery-budget.d.ts.map +1 -0
  421. package/daemon/dist/domain/gateway/slack/recovery-budget.js +18 -0
  422. package/daemon/dist/domain/gateway/slack/recovery-budget.js.map +1 -0
  423. package/daemon/dist/domain/gateway/slack/slack-api.d.ts +10 -0
  424. package/daemon/dist/domain/gateway/slack/slack-api.d.ts.map +1 -1
  425. package/daemon/dist/domain/gateway/slack/slack-api.js +13 -0
  426. package/daemon/dist/domain/gateway/slack/slack-api.js.map +1 -1
  427. package/daemon/dist/domain/gateway/slack/slack-delivery.d.ts +2 -0
  428. package/daemon/dist/domain/gateway/slack/slack-delivery.d.ts.map +1 -1
  429. package/daemon/dist/domain/gateway/slack/slack-delivery.js +12 -2
  430. package/daemon/dist/domain/gateway/slack/slack-delivery.js.map +1 -1
  431. package/daemon/dist/domain/gateway/slack/slack-recovery.d.ts +104 -0
  432. package/daemon/dist/domain/gateway/slack/slack-recovery.d.ts.map +1 -0
  433. package/daemon/dist/domain/gateway/slack/slack-recovery.js +236 -0
  434. package/daemon/dist/domain/gateway/slack/slack-recovery.js.map +1 -0
  435. package/daemon/dist/domain/gateway/slack/slack-subsystem.d.ts +3 -0
  436. package/daemon/dist/domain/gateway/slack/slack-subsystem.d.ts.map +1 -1
  437. package/daemon/dist/domain/gateway/slack/slack-subsystem.js +157 -21
  438. package/daemon/dist/domain/gateway/slack/slack-subsystem.js.map +1 -1
  439. package/daemon/dist/domain/gateway/slack/state-store.d.ts +43 -2
  440. package/daemon/dist/domain/gateway/slack/state-store.d.ts.map +1 -1
  441. package/daemon/dist/domain/gateway/slack/state-store.js +91 -2
  442. package/daemon/dist/domain/gateway/slack/state-store.js.map +1 -1
  443. package/daemon/dist/domain/gateway/slack/thread-recovery.d.ts +37 -0
  444. package/daemon/dist/domain/gateway/slack/thread-recovery.d.ts.map +1 -0
  445. package/daemon/dist/domain/gateway/slack/thread-recovery.js +173 -0
  446. package/daemon/dist/domain/gateway/slack/thread-recovery.js.map +1 -0
  447. package/daemon/dist/domain/gateway/slack/thread-routing.d.ts +3 -0
  448. package/daemon/dist/domain/gateway/slack/thread-routing.d.ts.map +1 -1
  449. package/daemon/dist/domain/gateway/slack/thread-routing.js +18 -7
  450. package/daemon/dist/domain/gateway/slack/thread-routing.js.map +1 -1
  451. package/daemon/dist/domain/gateway/slack/thread-seat-map.d.ts +25 -6
  452. package/daemon/dist/domain/gateway/slack/thread-seat-map.d.ts.map +1 -1
  453. package/daemon/dist/domain/gateway/slack/thread-seat-map.js +52 -23
  454. package/daemon/dist/domain/gateway/slack/thread-seat-map.js.map +1 -1
  455. package/daemon/dist/domain/history-query.d.ts.map +1 -1
  456. package/daemon/dist/domain/history-query.js +2 -1
  457. package/daemon/dist/domain/history-query.js.map +1 -1
  458. package/daemon/dist/domain/home-isolation.d.ts +18 -0
  459. package/daemon/dist/domain/home-isolation.d.ts.map +1 -0
  460. package/daemon/dist/domain/home-isolation.js +75 -0
  461. package/daemon/dist/domain/home-isolation.js.map +1 -0
  462. package/daemon/dist/domain/hosts/remote-daemon-http.d.ts +22 -0
  463. package/daemon/dist/domain/hosts/remote-daemon-http.d.ts.map +1 -1
  464. package/daemon/dist/domain/hosts/remote-daemon-http.js +43 -3
  465. package/daemon/dist/domain/hosts/remote-daemon-http.js.map +1 -1
  466. package/daemon/dist/domain/inbox-handler.d.ts.map +1 -1
  467. package/daemon/dist/domain/inbox-handler.js +34 -32
  468. package/daemon/dist/domain/inbox-handler.js.map +1 -1
  469. package/daemon/dist/domain/mission-control/notification-adapter-ntfy.js +12 -2
  470. package/daemon/dist/domain/mission-control/notification-adapter-ntfy.js.map +1 -1
  471. package/daemon/dist/domain/mission-control/notification-dispatcher.d.ts.map +1 -1
  472. package/daemon/dist/domain/mission-control/notification-dispatcher.js +2 -1
  473. package/daemon/dist/domain/mission-control/notification-dispatcher.js.map +1 -1
  474. package/daemon/dist/domain/model-divergence/current-generation-record.d.ts +68 -0
  475. package/daemon/dist/domain/model-divergence/current-generation-record.d.ts.map +1 -1
  476. package/daemon/dist/domain/model-divergence/current-generation-record.js +133 -1
  477. package/daemon/dist/domain/model-divergence/current-generation-record.js.map +1 -1
  478. package/daemon/dist/domain/model-divergence/current-model-reader.d.ts +74 -0
  479. package/daemon/dist/domain/model-divergence/current-model-reader.d.ts.map +1 -0
  480. package/daemon/dist/domain/model-divergence/current-model-reader.js +246 -0
  481. package/daemon/dist/domain/model-divergence/current-model-reader.js.map +1 -0
  482. package/daemon/dist/domain/model-divergence/effective-model-readers.d.ts +60 -0
  483. package/daemon/dist/domain/model-divergence/effective-model-readers.d.ts.map +1 -1
  484. package/daemon/dist/domain/model-divergence/effective-model-readers.js +182 -11
  485. package/daemon/dist/domain/model-divergence/effective-model-readers.js.map +1 -1
  486. package/daemon/dist/domain/model-divergence/model-divergence-monitor.d.ts +8 -2
  487. package/daemon/dist/domain/model-divergence/model-divergence-monitor.d.ts.map +1 -1
  488. package/daemon/dist/domain/model-divergence/model-divergence-monitor.js +10 -5
  489. package/daemon/dist/domain/model-divergence/model-divergence-monitor.js.map +1 -1
  490. package/daemon/dist/domain/native-process-lineage.d.ts +57 -1
  491. package/daemon/dist/domain/native-process-lineage.d.ts.map +1 -1
  492. package/daemon/dist/domain/native-process-lineage.js +169 -41
  493. package/daemon/dist/domain/native-process-lineage.js.map +1 -1
  494. package/daemon/dist/domain/native-resume-probe.d.ts +3 -1
  495. package/daemon/dist/domain/native-resume-probe.d.ts.map +1 -1
  496. package/daemon/dist/domain/native-resume-probe.js +77 -14
  497. package/daemon/dist/domain/native-resume-probe.js.map +1 -1
  498. package/daemon/dist/domain/node-inventory.d.ts +1 -1
  499. package/daemon/dist/domain/node-inventory.d.ts.map +1 -1
  500. package/daemon/dist/domain/node-inventory.js +110 -27
  501. package/daemon/dist/domain/node-inventory.js.map +1 -1
  502. package/daemon/dist/domain/node-launcher.d.ts +18 -0
  503. package/daemon/dist/domain/node-launcher.d.ts.map +1 -1
  504. package/daemon/dist/domain/node-launcher.js +60 -2
  505. package/daemon/dist/domain/node-launcher.js.map +1 -1
  506. package/daemon/dist/domain/openrouter-charge.d.ts +42 -0
  507. package/daemon/dist/domain/openrouter-charge.d.ts.map +1 -0
  508. package/daemon/dist/domain/openrouter-charge.js +57 -0
  509. package/daemon/dist/domain/openrouter-charge.js.map +1 -0
  510. package/daemon/dist/domain/parked-query.d.ts +5 -0
  511. package/daemon/dist/domain/parked-query.d.ts.map +1 -1
  512. package/daemon/dist/domain/parked-query.js +9 -2
  513. package/daemon/dist/domain/parked-query.js.map +1 -1
  514. package/daemon/dist/domain/periodic-snapshot-scheduler.d.ts.map +1 -1
  515. package/daemon/dist/domain/periodic-snapshot-scheduler.js +27 -3
  516. package/daemon/dist/domain/periodic-snapshot-scheduler.js.map +1 -1
  517. package/daemon/dist/domain/permission-drift.d.ts +2 -0
  518. package/daemon/dist/domain/permission-drift.d.ts.map +1 -1
  519. package/daemon/dist/domain/permission-drift.js.map +1 -1
  520. package/daemon/dist/domain/permission-policy/policy-spec.d.ts.map +1 -1
  521. package/daemon/dist/domain/permission-policy/policy-spec.js +5 -7
  522. package/daemon/dist/domain/permission-policy/policy-spec.js.map +1 -1
  523. package/daemon/dist/domain/pi-usage-entries-store.d.ts +41 -0
  524. package/daemon/dist/domain/pi-usage-entries-store.d.ts.map +1 -0
  525. package/daemon/dist/domain/pi-usage-entries-store.js +173 -0
  526. package/daemon/dist/domain/pi-usage-entries-store.js.map +1 -0
  527. package/daemon/dist/domain/pi-usage-facts.d.ts +90 -0
  528. package/daemon/dist/domain/pi-usage-facts.d.ts.map +1 -0
  529. package/daemon/dist/domain/pi-usage-facts.js +165 -0
  530. package/daemon/dist/domain/pi-usage-facts.js.map +1 -0
  531. package/daemon/dist/domain/pod-bundle-assembler.d.ts +2 -0
  532. package/daemon/dist/domain/pod-bundle-assembler.d.ts.map +1 -1
  533. package/daemon/dist/domain/pod-bundle-assembler.js +4 -0
  534. package/daemon/dist/domain/pod-bundle-assembler.js.map +1 -1
  535. package/daemon/dist/domain/policies/artifact-pool-helpers.d.ts.map +1 -1
  536. package/daemon/dist/domain/policies/artifact-pool-helpers.js +6 -4
  537. package/daemon/dist/domain/policies/artifact-pool-helpers.js.map +1 -1
  538. package/daemon/dist/domain/process-census.d.ts +1 -1
  539. package/daemon/dist/domain/process-census.d.ts.map +1 -1
  540. package/daemon/dist/domain/process-table.d.ts +26 -0
  541. package/daemon/dist/domain/process-table.d.ts.map +1 -0
  542. package/daemon/dist/domain/process-table.js +185 -0
  543. package/daemon/dist/domain/process-table.js.map +1 -0
  544. package/daemon/dist/domain/profile-resolver.d.ts +10 -0
  545. package/daemon/dist/domain/profile-resolver.d.ts.map +1 -1
  546. package/daemon/dist/domain/profile-resolver.js +15 -0
  547. package/daemon/dist/domain/profile-resolver.js.map +1 -1
  548. package/daemon/dist/domain/progress/progress-indexer.js +1 -1
  549. package/daemon/dist/domain/progress/progress-indexer.js.map +1 -1
  550. package/daemon/dist/domain/project-lifecycle-compiler.d.ts +4 -0
  551. package/daemon/dist/domain/project-lifecycle-compiler.d.ts.map +1 -1
  552. package/daemon/dist/domain/project-lifecycle-compiler.js +63 -3
  553. package/daemon/dist/domain/project-lifecycle-compiler.js.map +1 -1
  554. package/daemon/dist/domain/projection-planner.js +1 -1
  555. package/daemon/dist/domain/projection-planner.js.map +1 -1
  556. package/daemon/dist/domain/proof/judgments.d.ts.map +1 -1
  557. package/daemon/dist/domain/proof/judgments.js +31 -2
  558. package/daemon/dist/domain/proof/judgments.js.map +1 -1
  559. package/daemon/dist/domain/provider/claude-auth-status-reader.d.ts +33 -0
  560. package/daemon/dist/domain/provider/claude-auth-status-reader.d.ts.map +1 -0
  561. package/daemon/dist/domain/provider/claude-auth-status-reader.js +195 -0
  562. package/daemon/dist/domain/provider/claude-auth-status-reader.js.map +1 -0
  563. package/daemon/dist/domain/provider/claude-usage-windows.d.ts +70 -0
  564. package/daemon/dist/domain/provider/claude-usage-windows.d.ts.map +1 -0
  565. package/daemon/dist/domain/provider/claude-usage-windows.js +305 -0
  566. package/daemon/dist/domain/provider/claude-usage-windows.js.map +1 -0
  567. package/daemon/dist/domain/provider/codex-auth-reader.d.ts +14 -0
  568. package/daemon/dist/domain/provider/codex-auth-reader.d.ts.map +1 -1
  569. package/daemon/dist/domain/provider/codex-auth-reader.js +41 -12
  570. package/daemon/dist/domain/provider/codex-auth-reader.js.map +1 -1
  571. package/daemon/dist/domain/provider/codex-usage-reader.d.ts +6 -1
  572. package/daemon/dist/domain/provider/codex-usage-reader.d.ts.map +1 -1
  573. package/daemon/dist/domain/provider/codex-usage-reader.js +6 -1
  574. package/daemon/dist/domain/provider/codex-usage-reader.js.map +1 -1
  575. package/daemon/dist/domain/provider/codex-usage-windows.d.ts +12 -49
  576. package/daemon/dist/domain/provider/codex-usage-windows.d.ts.map +1 -1
  577. package/daemon/dist/domain/provider/codex-usage-windows.js +16 -173
  578. package/daemon/dist/domain/provider/codex-usage-windows.js.map +1 -1
  579. package/daemon/dist/domain/provider/provider-account-readings.d.ts +42 -0
  580. package/daemon/dist/domain/provider/provider-account-readings.d.ts.map +1 -0
  581. package/daemon/dist/domain/provider/provider-account-readings.js +132 -0
  582. package/daemon/dist/domain/provider/provider-account-readings.js.map +1 -0
  583. package/daemon/dist/domain/provider/provider-seat-accounts.d.ts +18 -0
  584. package/daemon/dist/domain/provider/provider-seat-accounts.d.ts.map +1 -0
  585. package/daemon/dist/domain/provider/provider-seat-accounts.js +159 -0
  586. package/daemon/dist/domain/provider/provider-seat-accounts.js.map +1 -0
  587. package/daemon/dist/domain/provider/provider-service-impl.d.ts +7 -2
  588. package/daemon/dist/domain/provider/provider-service-impl.d.ts.map +1 -1
  589. package/daemon/dist/domain/provider/provider-service-impl.js +20 -2
  590. package/daemon/dist/domain/provider/provider-service-impl.js.map +1 -1
  591. package/daemon/dist/domain/provider/provider-service.d.ts +4 -2
  592. package/daemon/dist/domain/provider/provider-service.d.ts.map +1 -1
  593. package/daemon/dist/domain/provider/provider-types.d.ts +101 -4
  594. package/daemon/dist/domain/provider/provider-types.d.ts.map +1 -1
  595. package/daemon/dist/domain/provider/reactive-tap.js +2 -0
  596. package/daemon/dist/domain/provider/reactive-tap.js.map +1 -1
  597. package/daemon/dist/domain/provider/subscription-moves.d.ts +77 -0
  598. package/daemon/dist/domain/provider/subscription-moves.d.ts.map +1 -0
  599. package/daemon/dist/domain/provider/subscription-moves.js +140 -0
  600. package/daemon/dist/domain/provider/subscription-moves.js.map +1 -0
  601. package/daemon/dist/domain/provider/usage-across-hosts.d.ts +162 -0
  602. package/daemon/dist/domain/provider/usage-across-hosts.d.ts.map +1 -0
  603. package/daemon/dist/domain/provider/usage-across-hosts.js +569 -0
  604. package/daemon/dist/domain/provider/usage-across-hosts.js.map +1 -0
  605. package/daemon/dist/domain/provider/usage-windows.d.ts +113 -0
  606. package/daemon/dist/domain/provider/usage-windows.d.ts.map +1 -0
  607. package/daemon/dist/domain/provider/usage-windows.js +285 -0
  608. package/daemon/dist/domain/provider/usage-windows.js.map +1 -0
  609. package/daemon/dist/domain/queue-owner.d.ts +6 -0
  610. package/daemon/dist/domain/queue-owner.d.ts.map +1 -1
  611. package/daemon/dist/domain/queue-owner.js +22 -0
  612. package/daemon/dist/domain/queue-owner.js.map +1 -1
  613. package/daemon/dist/domain/queue-repository.d.ts +112 -14
  614. package/daemon/dist/domain/queue-repository.d.ts.map +1 -1
  615. package/daemon/dist/domain/queue-repository.js +390 -87
  616. package/daemon/dist/domain/queue-repository.js.map +1 -1
  617. package/daemon/dist/domain/queue-stuck-sweep.d.ts.map +1 -1
  618. package/daemon/dist/domain/queue-stuck-sweep.js +53 -22
  619. package/daemon/dist/domain/queue-stuck-sweep.js.map +1 -1
  620. package/daemon/dist/domain/queue-transition-log.d.ts +23 -2
  621. package/daemon/dist/domain/queue-transition-log.d.ts.map +1 -1
  622. package/daemon/dist/domain/queue-transition-log.js +98 -39
  623. package/daemon/dist/domain/queue-transition-log.js.map +1 -1
  624. package/daemon/dist/domain/queue-wake-ladder.d.ts +2 -2
  625. package/daemon/dist/domain/queue-wake-ladder.d.ts.map +1 -1
  626. package/daemon/dist/domain/queue-wake-ladder.js +27 -8
  627. package/daemon/dist/domain/queue-wake-ladder.js.map +1 -1
  628. package/daemon/dist/domain/queue-wake-repository.d.ts +7 -1
  629. package/daemon/dist/domain/queue-wake-repository.d.ts.map +1 -1
  630. package/daemon/dist/domain/queue-wake-repository.js +54 -5
  631. package/daemon/dist/domain/queue-wake-repository.js.map +1 -1
  632. package/daemon/dist/domain/reply-to-choice.d.ts +2 -0
  633. package/daemon/dist/domain/reply-to-choice.d.ts.map +1 -1
  634. package/daemon/dist/domain/reply-to-choice.js +2 -0
  635. package/daemon/dist/domain/reply-to-choice.js.map +1 -1
  636. package/daemon/dist/domain/restore-check-service.d.ts +10 -0
  637. package/daemon/dist/domain/restore-check-service.d.ts.map +1 -1
  638. package/daemon/dist/domain/restore-check-service.js +76 -12
  639. package/daemon/dist/domain/restore-check-service.js.map +1 -1
  640. package/daemon/dist/domain/restore-guidance.d.ts +6 -0
  641. package/daemon/dist/domain/restore-guidance.d.ts.map +1 -1
  642. package/daemon/dist/domain/restore-guidance.js +21 -5
  643. package/daemon/dist/domain/restore-guidance.js.map +1 -1
  644. package/daemon/dist/domain/restore-orchestrator.d.ts.map +1 -1
  645. package/daemon/dist/domain/restore-orchestrator.js +79 -15
  646. package/daemon/dist/domain/restore-orchestrator.js.map +1 -1
  647. package/daemon/dist/domain/restore-plan-preview.d.ts.map +1 -1
  648. package/daemon/dist/domain/restore-plan-preview.js +23 -19
  649. package/daemon/dist/domain/restore-plan-preview.js.map +1 -1
  650. package/daemon/dist/domain/restore-policy.d.ts +26 -0
  651. package/daemon/dist/domain/restore-policy.d.ts.map +1 -0
  652. package/daemon/dist/domain/restore-policy.js +22 -0
  653. package/daemon/dist/domain/restore-policy.js.map +1 -0
  654. package/daemon/dist/domain/restore-preconditions.d.ts +2 -0
  655. package/daemon/dist/domain/restore-preconditions.d.ts.map +1 -1
  656. package/daemon/dist/domain/restore-preconditions.js +6 -1
  657. package/daemon/dist/domain/restore-preconditions.js.map +1 -1
  658. package/daemon/dist/domain/restore-runtime-compatibility.d.ts +3 -0
  659. package/daemon/dist/domain/restore-runtime-compatibility.d.ts.map +1 -0
  660. package/daemon/dist/domain/restore-runtime-compatibility.js +7 -0
  661. package/daemon/dist/domain/restore-runtime-compatibility.js.map +1 -0
  662. package/daemon/dist/domain/resume-metadata-refresher.d.ts +10 -5
  663. package/daemon/dist/domain/resume-metadata-refresher.d.ts.map +1 -1
  664. package/daemon/dist/domain/resume-metadata-refresher.js +115 -50
  665. package/daemon/dist/domain/resume-metadata-refresher.js.map +1 -1
  666. package/daemon/dist/domain/resume-token-capture.d.ts +1 -1
  667. package/daemon/dist/domain/resume-token-capture.d.ts.map +1 -1
  668. package/daemon/dist/domain/resume-token-capture.js +7 -1
  669. package/daemon/dist/domain/resume-token-capture.js.map +1 -1
  670. package/daemon/dist/domain/review/compose.d.ts.map +1 -1
  671. package/daemon/dist/domain/review/compose.js +5 -4
  672. package/daemon/dist/domain/review/compose.js.map +1 -1
  673. package/daemon/dist/domain/review/gather.d.ts +2 -1
  674. package/daemon/dist/domain/review/gather.d.ts.map +1 -1
  675. package/daemon/dist/domain/review/gather.js +25 -17
  676. package/daemon/dist/domain/review/gather.js.map +1 -1
  677. package/daemon/dist/domain/rig-lifecycle-service.d.ts +31 -8
  678. package/daemon/dist/domain/rig-lifecycle-service.d.ts.map +1 -1
  679. package/daemon/dist/domain/rig-lifecycle-service.js +253 -98
  680. package/daemon/dist/domain/rig-lifecycle-service.js.map +1 -1
  681. package/daemon/dist/domain/rig-mode/rig-mode-store.d.ts +11 -1
  682. package/daemon/dist/domain/rig-mode/rig-mode-store.d.ts.map +1 -1
  683. package/daemon/dist/domain/rig-mode/rig-mode-store.js +69 -48
  684. package/daemon/dist/domain/rig-mode/rig-mode-store.js.map +1 -1
  685. package/daemon/dist/domain/rig-repository.d.ts +21 -0
  686. package/daemon/dist/domain/rig-repository.d.ts.map +1 -1
  687. package/daemon/dist/domain/rig-repository.js +58 -1
  688. package/daemon/dist/domain/rig-repository.js.map +1 -1
  689. package/daemon/dist/domain/rig-teardown.d.ts +4 -1
  690. package/daemon/dist/domain/rig-teardown.d.ts.map +1 -1
  691. package/daemon/dist/domain/rig-teardown.js +40 -2
  692. package/daemon/dist/domain/rig-teardown.js.map +1 -1
  693. package/daemon/dist/domain/rigspec-codec.d.ts.map +1 -1
  694. package/daemon/dist/domain/rigspec-codec.js +7 -0
  695. package/daemon/dist/domain/rigspec-codec.js.map +1 -1
  696. package/daemon/dist/domain/rigspec-exporter.d.ts.map +1 -1
  697. package/daemon/dist/domain/rigspec-exporter.js +4 -0
  698. package/daemon/dist/domain/rigspec-exporter.js.map +1 -1
  699. package/daemon/dist/domain/rigspec-instantiator.d.ts +3 -0
  700. package/daemon/dist/domain/rigspec-instantiator.d.ts.map +1 -1
  701. package/daemon/dist/domain/rigspec-instantiator.js +32 -7
  702. package/daemon/dist/domain/rigspec-instantiator.js.map +1 -1
  703. package/daemon/dist/domain/rigspec-preflight.d.ts +4 -0
  704. package/daemon/dist/domain/rigspec-preflight.d.ts.map +1 -1
  705. package/daemon/dist/domain/rigspec-preflight.js +30 -7
  706. package/daemon/dist/domain/rigspec-preflight.js.map +1 -1
  707. package/daemon/dist/domain/rigspec-schema.d.ts.map +1 -1
  708. package/daemon/dist/domain/rigspec-schema.js +35 -2
  709. package/daemon/dist/domain/rigspec-schema.js.map +1 -1
  710. package/daemon/dist/domain/runtime-adapter.d.ts +24 -0
  711. package/daemon/dist/domain/runtime-adapter.d.ts.map +1 -1
  712. package/daemon/dist/domain/runtime-adapter.js.map +1 -1
  713. package/daemon/dist/domain/runtime-verifier.d.ts +15 -1
  714. package/daemon/dist/domain/runtime-verifier.d.ts.map +1 -1
  715. package/daemon/dist/domain/runtime-verifier.js +28 -4
  716. package/daemon/dist/domain/runtime-verifier.js.map +1 -1
  717. package/daemon/dist/domain/scope/logical-checkbox.js +1 -1
  718. package/daemon/dist/domain/scope/logical-checkbox.js.map +1 -1
  719. package/daemon/dist/domain/scope/scope-view-projection.d.ts.map +1 -1
  720. package/daemon/dist/domain/scope/scope-view-projection.js +23 -10
  721. package/daemon/dist/domain/scope/scope-view-projection.js.map +1 -1
  722. package/daemon/dist/domain/seat-activity-service.d.ts +9 -2
  723. package/daemon/dist/domain/seat-activity-service.d.ts.map +1 -1
  724. package/daemon/dist/domain/seat-activity-service.js +100 -15
  725. package/daemon/dist/domain/seat-activity-service.js.map +1 -1
  726. package/daemon/dist/domain/seat-attention-reconciler.d.ts +3 -0
  727. package/daemon/dist/domain/seat-attention-reconciler.d.ts.map +1 -1
  728. package/daemon/dist/domain/seat-attention-reconciler.js +129 -8
  729. package/daemon/dist/domain/seat-attention-reconciler.js.map +1 -1
  730. package/daemon/dist/domain/seat-cwd-preparation.d.ts +22 -0
  731. package/daemon/dist/domain/seat-cwd-preparation.d.ts.map +1 -0
  732. package/daemon/dist/domain/seat-cwd-preparation.js +107 -0
  733. package/daemon/dist/domain/seat-cwd-preparation.js.map +1 -0
  734. package/daemon/dist/domain/seat-delivery-guard.d.ts +36 -1
  735. package/daemon/dist/domain/seat-delivery-guard.d.ts.map +1 -1
  736. package/daemon/dist/domain/seat-delivery-guard.js +255 -22
  737. package/daemon/dist/domain/seat-delivery-guard.js.map +1 -1
  738. package/daemon/dist/domain/seat-effort.d.ts +116 -0
  739. package/daemon/dist/domain/seat-effort.d.ts.map +1 -0
  740. package/daemon/dist/domain/seat-effort.js +251 -0
  741. package/daemon/dist/domain/seat-effort.js.map +1 -0
  742. package/daemon/dist/domain/seat-env.d.ts +37 -0
  743. package/daemon/dist/domain/seat-env.d.ts.map +1 -0
  744. package/daemon/dist/domain/seat-env.js +94 -0
  745. package/daemon/dist/domain/seat-env.js.map +1 -0
  746. package/daemon/dist/domain/seat-handover-service.d.ts +5 -0
  747. package/daemon/dist/domain/seat-handover-service.d.ts.map +1 -1
  748. package/daemon/dist/domain/seat-handover-service.js +22 -4
  749. package/daemon/dist/domain/seat-handover-service.js.map +1 -1
  750. package/daemon/dist/domain/seat-identity-reconciler.d.ts.map +1 -1
  751. package/daemon/dist/domain/seat-identity-reconciler.js +19 -2
  752. package/daemon/dist/domain/seat-identity-reconciler.js.map +1 -1
  753. package/daemon/dist/domain/seat-lifecycle-service.d.ts +153 -5
  754. package/daemon/dist/domain/seat-lifecycle-service.d.ts.map +1 -1
  755. package/daemon/dist/domain/seat-lifecycle-service.js +509 -23
  756. package/daemon/dist/domain/seat-lifecycle-service.js.map +1 -1
  757. package/daemon/dist/domain/seat-restart-in-place.d.ts +27 -2
  758. package/daemon/dist/domain/seat-restart-in-place.d.ts.map +1 -1
  759. package/daemon/dist/domain/seat-restart-in-place.js +82 -7
  760. package/daemon/dist/domain/seat-restart-in-place.js.map +1 -1
  761. package/daemon/dist/domain/seat-status-service.d.ts +2 -0
  762. package/daemon/dist/domain/seat-status-service.d.ts.map +1 -1
  763. package/daemon/dist/domain/seat-status-service.js +1 -0
  764. package/daemon/dist/domain/seat-status-service.js.map +1 -1
  765. package/daemon/dist/domain/seat-structural-activity-service.d.ts +12 -2
  766. package/daemon/dist/domain/seat-structural-activity-service.d.ts.map +1 -1
  767. package/daemon/dist/domain/seat-structural-activity-service.js +51 -9
  768. package/daemon/dist/domain/seat-structural-activity-service.js.map +1 -1
  769. package/daemon/dist/domain/session-name.d.ts +8 -0
  770. package/daemon/dist/domain/session-name.d.ts.map +1 -1
  771. package/daemon/dist/domain/session-name.js +12 -0
  772. package/daemon/dist/domain/session-name.js.map +1 -1
  773. package/daemon/dist/domain/session-registry.d.ts +13 -1
  774. package/daemon/dist/domain/session-registry.d.ts.map +1 -1
  775. package/daemon/dist/domain/session-registry.js +29 -2
  776. package/daemon/dist/domain/session-registry.js.map +1 -1
  777. package/daemon/dist/domain/session-transport.d.ts +186 -4
  778. package/daemon/dist/domain/session-transport.d.ts.map +1 -1
  779. package/daemon/dist/domain/session-transport.js +1421 -143
  780. package/daemon/dist/domain/session-transport.js.map +1 -1
  781. package/daemon/dist/domain/slices/slice-detail-projector.d.ts +2 -0
  782. package/daemon/dist/domain/slices/slice-detail-projector.d.ts.map +1 -1
  783. package/daemon/dist/domain/slices/slice-detail-projector.js +16 -4
  784. package/daemon/dist/domain/slices/slice-detail-projector.js.map +1 -1
  785. package/daemon/dist/domain/slices/slice-indexer.d.ts +10 -0
  786. package/daemon/dist/domain/slices/slice-indexer.d.ts.map +1 -1
  787. package/daemon/dist/domain/slices/slice-indexer.js +84 -4
  788. package/daemon/dist/domain/slices/slice-indexer.js.map +1 -1
  789. package/daemon/dist/domain/snapshot-repository.d.ts +3 -0
  790. package/daemon/dist/domain/snapshot-repository.d.ts.map +1 -1
  791. package/daemon/dist/domain/snapshot-repository.js +19 -2
  792. package/daemon/dist/domain/snapshot-repository.js.map +1 -1
  793. package/daemon/dist/domain/spec-library-workflow-scanner.d.ts +1 -1
  794. package/daemon/dist/domain/spec-library-workflow-scanner.d.ts.map +1 -1
  795. package/daemon/dist/domain/spec-library-workflow-scanner.js +24 -21
  796. package/daemon/dist/domain/spec-library-workflow-scanner.js.map +1 -1
  797. package/daemon/dist/domain/spec-validation-advisory.js +1 -1
  798. package/daemon/dist/domain/spec-validation-advisory.js.map +1 -1
  799. package/daemon/dist/domain/startup-orchestrator.d.ts +31 -3
  800. package/daemon/dist/domain/startup-orchestrator.d.ts.map +1 -1
  801. package/daemon/dist/domain/startup-orchestrator.js +218 -60
  802. package/daemon/dist/domain/startup-orchestrator.js.map +1 -1
  803. package/daemon/dist/domain/startup-submission-evidence.d.ts +13 -3
  804. package/daemon/dist/domain/startup-submission-evidence.d.ts.map +1 -1
  805. package/daemon/dist/domain/startup-submission-evidence.js +34 -25
  806. package/daemon/dist/domain/startup-submission-evidence.js.map +1 -1
  807. package/daemon/dist/domain/startup-validation.d.ts.map +1 -1
  808. package/daemon/dist/domain/startup-validation.js +3 -0
  809. package/daemon/dist/domain/startup-validation.js.map +1 -1
  810. package/daemon/dist/domain/successor-session-launcher.d.ts +7 -1
  811. package/daemon/dist/domain/successor-session-launcher.d.ts.map +1 -1
  812. package/daemon/dist/domain/successor-session-launcher.js +6 -2
  813. package/daemon/dist/domain/successor-session-launcher.js.map +1 -1
  814. package/daemon/dist/domain/text-cut.d.ts +4 -0
  815. package/daemon/dist/domain/text-cut.d.ts.map +1 -0
  816. package/daemon/dist/domain/text-cut.js +7 -0
  817. package/daemon/dist/domain/text-cut.js.map +1 -0
  818. package/daemon/dist/domain/time-bound-advisory.d.ts +5 -0
  819. package/daemon/dist/domain/time-bound-advisory.d.ts.map +1 -0
  820. package/daemon/dist/domain/time-bound-advisory.js +25 -0
  821. package/daemon/dist/domain/time-bound-advisory.js.map +1 -0
  822. package/daemon/dist/domain/transcript-capture.d.ts +7 -0
  823. package/daemon/dist/domain/transcript-capture.d.ts.map +1 -1
  824. package/daemon/dist/domain/transcript-capture.js +22 -1
  825. package/daemon/dist/domain/transcript-capture.js.map +1 -1
  826. package/daemon/dist/domain/transcript-rotation.d.ts +12 -4
  827. package/daemon/dist/domain/transcript-rotation.d.ts.map +1 -1
  828. package/daemon/dist/domain/transcript-rotation.js +185 -23
  829. package/daemon/dist/domain/transcript-rotation.js.map +1 -1
  830. package/daemon/dist/domain/transcript-store.d.ts +1 -1
  831. package/daemon/dist/domain/transcript-store.d.ts.map +1 -1
  832. package/daemon/dist/domain/transcript-store.js +8 -4
  833. package/daemon/dist/domain/transcript-store.js.map +1 -1
  834. package/daemon/dist/domain/types.d.ts +67 -3
  835. package/daemon/dist/domain/types.d.ts.map +1 -1
  836. package/daemon/dist/domain/types.js.map +1 -1
  837. package/daemon/dist/domain/usage-samples-store.d.ts +8 -0
  838. package/daemon/dist/domain/usage-samples-store.d.ts.map +1 -1
  839. package/daemon/dist/domain/usage-samples-store.js +24 -5
  840. package/daemon/dist/domain/usage-samples-store.js.map +1 -1
  841. package/daemon/dist/domain/usage-series.d.ts +154 -2
  842. package/daemon/dist/domain/usage-series.d.ts.map +1 -1
  843. package/daemon/dist/domain/usage-series.js +208 -9
  844. package/daemon/dist/domain/usage-series.js.map +1 -1
  845. package/daemon/dist/domain/user-settings/settings-store.d.ts +1 -1
  846. package/daemon/dist/domain/user-settings/settings-store.d.ts.map +1 -1
  847. package/daemon/dist/domain/user-settings/settings-store.js +37 -1
  848. package/daemon/dist/domain/user-settings/settings-store.js.map +1 -1
  849. package/daemon/dist/domain/watchdog-delivery.d.ts +29 -0
  850. package/daemon/dist/domain/watchdog-delivery.d.ts.map +1 -0
  851. package/daemon/dist/domain/watchdog-delivery.js +113 -0
  852. package/daemon/dist/domain/watchdog-delivery.js.map +1 -0
  853. package/daemon/dist/domain/watchdog-jobs-repository.d.ts +3 -1
  854. package/daemon/dist/domain/watchdog-jobs-repository.d.ts.map +1 -1
  855. package/daemon/dist/domain/watchdog-jobs-repository.js +13 -1
  856. package/daemon/dist/domain/watchdog-jobs-repository.js.map +1 -1
  857. package/daemon/dist/domain/watchdog-policy-engine.d.ts +61 -1
  858. package/daemon/dist/domain/watchdog-policy-engine.d.ts.map +1 -1
  859. package/daemon/dist/domain/watchdog-policy-engine.js +185 -30
  860. package/daemon/dist/domain/watchdog-policy-engine.js.map +1 -1
  861. package/daemon/dist/domain/watchdog-scheduler.d.ts.map +1 -1
  862. package/daemon/dist/domain/watchdog-scheduler.js +6 -1
  863. package/daemon/dist/domain/watchdog-scheduler.js.map +1 -1
  864. package/daemon/dist/domain/workflow-exception-escalation.d.ts.map +1 -1
  865. package/daemon/dist/domain/workflow-exception-escalation.js +3 -1
  866. package/daemon/dist/domain/workflow-exception-escalation.js.map +1 -1
  867. package/daemon/dist/domain/workflow-exception-readiness.d.ts.map +1 -1
  868. package/daemon/dist/domain/workflow-exception-readiness.js +7 -3
  869. package/daemon/dist/domain/workflow-exception-readiness.js.map +1 -1
  870. package/daemon/dist/domain/workflow-instance-store.d.ts +11 -0
  871. package/daemon/dist/domain/workflow-instance-store.d.ts.map +1 -1
  872. package/daemon/dist/domain/workflow-instance-store.js +24 -6
  873. package/daemon/dist/domain/workflow-instance-store.js.map +1 -1
  874. package/daemon/dist/domain/workflow-projector.d.ts.map +1 -1
  875. package/daemon/dist/domain/workflow-projector.js +5 -3
  876. package/daemon/dist/domain/workflow-projector.js.map +1 -1
  877. package/daemon/dist/domain/workflow-rig-placeholder.d.ts +24 -0
  878. package/daemon/dist/domain/workflow-rig-placeholder.d.ts.map +1 -0
  879. package/daemon/dist/domain/workflow-rig-placeholder.js +43 -0
  880. package/daemon/dist/domain/workflow-rig-placeholder.js.map +1 -0
  881. package/daemon/dist/domain/workflow-runtime.d.ts +10 -1
  882. package/daemon/dist/domain/workflow-runtime.d.ts.map +1 -1
  883. package/daemon/dist/domain/workflow-runtime.js +26 -13
  884. package/daemon/dist/domain/workflow-runtime.js.map +1 -1
  885. package/daemon/dist/domain/workflow-scope.d.ts +50 -0
  886. package/daemon/dist/domain/workflow-scope.d.ts.map +1 -0
  887. package/daemon/dist/domain/workflow-scope.js +129 -0
  888. package/daemon/dist/domain/workflow-scope.js.map +1 -0
  889. package/daemon/dist/domain/workflow-spec-cache.d.ts +10 -0
  890. package/daemon/dist/domain/workflow-spec-cache.d.ts.map +1 -1
  891. package/daemon/dist/domain/workflow-spec-cache.js +13 -0
  892. package/daemon/dist/domain/workflow-spec-cache.js.map +1 -1
  893. package/daemon/dist/domain/workflow-types.d.ts +4 -0
  894. package/daemon/dist/domain/workflow-types.d.ts.map +1 -1
  895. package/daemon/dist/domain/workflow-types.js.map +1 -1
  896. package/daemon/dist/domain/workflow-validator.d.ts.map +1 -1
  897. package/daemon/dist/domain/workflow-validator.js +3 -1
  898. package/daemon/dist/domain/workflow-validator.js.map +1 -1
  899. package/daemon/dist/domain/workspace/project-read.d.ts +2 -0
  900. package/daemon/dist/domain/workspace/project-read.d.ts.map +1 -1
  901. package/daemon/dist/domain/workspace/project-read.js +4 -0
  902. package/daemon/dist/domain/workspace/project-read.js.map +1 -1
  903. package/daemon/dist/index.d.ts.map +1 -1
  904. package/daemon/dist/index.js +10 -0
  905. package/daemon/dist/index.js.map +1 -1
  906. package/daemon/dist/lib/pane-envelope.js +1 -1
  907. package/daemon/dist/lib/pane-envelope.js.map +1 -1
  908. package/daemon/dist/middleware/json-body-error.d.ts +4 -3
  909. package/daemon/dist/middleware/json-body-error.d.ts.map +1 -1
  910. package/daemon/dist/middleware/json-body-error.js +18 -4
  911. package/daemon/dist/middleware/json-body-error.js.map +1 -1
  912. package/daemon/dist/routes/activity.d.ts.map +1 -1
  913. package/daemon/dist/routes/activity.js +78 -0
  914. package/daemon/dist/routes/activity.js.map +1 -1
  915. package/daemon/dist/routes/attention.d.ts.map +1 -1
  916. package/daemon/dist/routes/attention.js +41 -1
  917. package/daemon/dist/routes/attention.js.map +1 -1
  918. package/daemon/dist/routes/bundles.d.ts.map +1 -1
  919. package/daemon/dist/routes/bundles.js +39 -16
  920. package/daemon/dist/routes/bundles.js.map +1 -1
  921. package/daemon/dist/routes/community-harnesses.d.ts +3 -0
  922. package/daemon/dist/routes/community-harnesses.d.ts.map +1 -0
  923. package/daemon/dist/routes/community-harnesses.js +18 -0
  924. package/daemon/dist/routes/community-harnesses.js.map +1 -0
  925. package/daemon/dist/routes/compaction.d.ts +3 -0
  926. package/daemon/dist/routes/compaction.d.ts.map +1 -1
  927. package/daemon/dist/routes/compaction.js +14 -2
  928. package/daemon/dist/routes/compaction.js.map +1 -1
  929. package/daemon/dist/routes/events.js +26 -12
  930. package/daemon/dist/routes/events.js.map +1 -1
  931. package/daemon/dist/routes/gateway.d.ts +3 -0
  932. package/daemon/dist/routes/gateway.d.ts.map +1 -1
  933. package/daemon/dist/routes/gateway.js +15 -0
  934. package/daemon/dist/routes/gateway.js.map +1 -1
  935. package/daemon/dist/routes/hosts.d.ts.map +1 -1
  936. package/daemon/dist/routes/hosts.js +2 -1
  937. package/daemon/dist/routes/hosts.js.map +1 -1
  938. package/daemon/dist/routes/provider.d.ts +11 -1
  939. package/daemon/dist/routes/provider.d.ts.map +1 -1
  940. package/daemon/dist/routes/provider.js +182 -10
  941. package/daemon/dist/routes/provider.js.map +1 -1
  942. package/daemon/dist/routes/query-limit.d.ts +1 -0
  943. package/daemon/dist/routes/query-limit.d.ts.map +1 -1
  944. package/daemon/dist/routes/query-limit.js +1 -1
  945. package/daemon/dist/routes/query-limit.js.map +1 -1
  946. package/daemon/dist/routes/queue.d.ts.map +1 -1
  947. package/daemon/dist/routes/queue.js +47 -4
  948. package/daemon/dist/routes/queue.js.map +1 -1
  949. package/daemon/dist/routes/restore-check.d.ts.map +1 -1
  950. package/daemon/dist/routes/restore-check.js +19 -1
  951. package/daemon/dist/routes/restore-check.js.map +1 -1
  952. package/daemon/dist/routes/rig-mode.d.ts.map +1 -1
  953. package/daemon/dist/routes/rig-mode.js +66 -11
  954. package/daemon/dist/routes/rig-mode.js.map +1 -1
  955. package/daemon/dist/routes/scope-audit.d.ts.map +1 -1
  956. package/daemon/dist/routes/scope-audit.js +10 -0
  957. package/daemon/dist/routes/scope-audit.js.map +1 -1
  958. package/daemon/dist/routes/seat.d.ts.map +1 -1
  959. package/daemon/dist/routes/seat.js +76 -12
  960. package/daemon/dist/routes/seat.js.map +1 -1
  961. package/daemon/dist/routes/sessions.d.ts.map +1 -1
  962. package/daemon/dist/routes/sessions.js +8 -2
  963. package/daemon/dist/routes/sessions.js.map +1 -1
  964. package/daemon/dist/routes/startup.d.ts.map +1 -1
  965. package/daemon/dist/routes/startup.js +9 -2
  966. package/daemon/dist/routes/startup.js.map +1 -1
  967. package/daemon/dist/routes/telemetry.d.ts.map +1 -1
  968. package/daemon/dist/routes/telemetry.js +13 -3
  969. package/daemon/dist/routes/telemetry.js.map +1 -1
  970. package/daemon/dist/routes/transcripts.d.ts.map +1 -1
  971. package/daemon/dist/routes/transcripts.js +14 -5
  972. package/daemon/dist/routes/transcripts.js.map +1 -1
  973. package/daemon/dist/routes/transport.d.ts.map +1 -1
  974. package/daemon/dist/routes/transport.js +26 -4
  975. package/daemon/dist/routes/transport.js.map +1 -1
  976. package/daemon/dist/routes/up.d.ts.map +1 -1
  977. package/daemon/dist/routes/up.js +4 -0
  978. package/daemon/dist/routes/up.js.map +1 -1
  979. package/daemon/dist/routes/workflow.d.ts.map +1 -1
  980. package/daemon/dist/routes/workflow.js +50 -1
  981. package/daemon/dist/routes/workflow.js.map +1 -1
  982. package/daemon/dist/server.d.ts +1 -0
  983. package/daemon/dist/server.d.ts.map +1 -1
  984. package/daemon/dist/server.js +5 -1
  985. package/daemon/dist/server.js.map +1 -1
  986. package/daemon/dist/spec-validation-surface.d.ts +1 -1
  987. package/daemon/dist/spec-validation-surface.d.ts.map +1 -1
  988. package/daemon/dist/spec-validation-surface.js +1 -1
  989. package/daemon/dist/spec-validation-surface.js.map +1 -1
  990. package/daemon/dist/startup.d.ts +3 -1
  991. package/daemon/dist/startup.d.ts.map +1 -1
  992. package/daemon/dist/startup.js +160 -148
  993. package/daemon/dist/startup.js.map +1 -1
  994. package/daemon/dist/workflow-scope-surface.d.ts +2 -0
  995. package/daemon/dist/workflow-scope-surface.d.ts.map +1 -0
  996. package/daemon/dist/workflow-scope-surface.js +2 -0
  997. package/daemon/dist/workflow-scope-surface.js.map +1 -0
  998. package/daemon/docs/reference/agent-spec.md +5 -3
  999. package/daemon/docs/reference/agent-startup-guide.md +179 -6
  1000. package/daemon/docs/reference/claude-status-line.md +69 -0
  1001. package/daemon/docs/reference/getting-started.md +81 -18
  1002. package/daemon/docs/reference/keeping-a-watch.md +102 -5
  1003. package/daemon/docs/reference/project-workspace.md +70 -1
  1004. package/daemon/docs/reference/publishing-a-rig-bundle.md +11 -3
  1005. package/daemon/docs/reference/refocus-channel.md +52 -6
  1006. package/daemon/docs/reference/rig-bundle.md +26 -2
  1007. package/daemon/docs/reference/rig-spec.md +67 -6
  1008. package/daemon/docs/reference/rosters.md +29 -2
  1009. package/daemon/docs/reference/schemas/bundle-behaviour.v1.schema.json +105 -2
  1010. package/daemon/docs/reference/scoped-operating-posture.md +12 -2
  1011. package/daemon/docs/reference/sdlc-conventions.md +39 -1
  1012. package/daemon/docs/reference/seat-delivery.md +127 -0
  1013. package/daemon/docs/reference/slack-app-setup.md +187 -29
  1014. package/daemon/docs/reference/topology-naming.md +9 -0
  1015. package/daemon/docs/reference/whats-new.md +99 -151
  1016. package/daemon/specs/agents/apps/vault-specialist/agent.yaml +1 -0
  1017. package/daemon/specs/agents/conveyor/builder/agent.yaml +1 -0
  1018. package/daemon/specs/agents/conveyor/lead/agent.yaml +1 -0
  1019. package/daemon/specs/agents/conveyor/planner/agent.yaml +1 -0
  1020. package/daemon/specs/agents/conveyor/reviewer/agent.yaml +1 -0
  1021. package/daemon/specs/agents/design/product-designer/agent.yaml +1 -0
  1022. package/daemon/specs/agents/development/implementer/agent.yaml +1 -0
  1023. package/daemon/specs/agents/development/qa/agent.yaml +1 -0
  1024. package/daemon/specs/agents/factory-rsi/dogfood/agent.yaml +1 -0
  1025. package/daemon/specs/agents/factory-rsi/release-manager/agent.yaml +1 -0
  1026. package/daemon/specs/agents/orchestration/orchestrator/agent.yaml +1 -0
  1027. package/daemon/specs/agents/product-management/pm/agent.yaml +1 -0
  1028. package/daemon/specs/agents/research/analyst/agent.yaml +1 -0
  1029. package/daemon/specs/agents/research/synthesizer/agent.yaml +1 -0
  1030. package/daemon/specs/agents/review/independent-reviewer/agent.yaml +1 -0
  1031. package/daemon/specs/agents/shared/skills/core/openrig-skills/SKILL.md +1 -1
  1032. package/daemon/specs/agents/shared/skills/core/openrig-software-factory/SKILL.md +0 -1
  1033. package/daemon/specs/agents/shared/skills/core/openrig-software-factory/references/worked-example.md +7 -4
  1034. package/daemon/specs/agents/shared/skills/core/topology-mutation-and-seat-management/SKILL.md +1 -1
  1035. package/daemon/specs/agents/shared/skills/pods/orchestration-team/SKILL.md +1 -1
  1036. package/daemon/specs/rigs/launch/demo/CULTURE.md +1 -1
  1037. package/daemon/specs/rigs/launch/factory/CULTURE.md +1 -1
  1038. package/daemon/specs/rigs/launch/factory/topology/rig/CRAFT.md +2 -2
  1039. package/daemon/specs/rigs/launch/first-project/CULTURE.md +2 -1
  1040. package/daemon/specs/rigs/launch/first-project-claude/CULTURE.md +2 -1
  1041. package/daemon/specs/rigs/launch/first-project-mixed/CULTURE.md +2 -1
  1042. package/daemon/specs/rigs/launch/kernel/agents/operator/agent/guidance/role.md +9 -2
  1043. package/daemon/specs/rigs/launch/starter/CULTURE.md +2 -1
  1044. package/daemon/specs/rigs/preview/product-team/CULTURE.md +1 -1
  1045. package/dist/bin-wrapper.d.ts +2 -1
  1046. package/dist/bin-wrapper.d.ts.map +1 -1
  1047. package/dist/bin-wrapper.js +61 -4
  1048. package/dist/bin-wrapper.js.map +1 -1
  1049. package/dist/build-info.js +3 -3
  1050. package/dist/bundle-behaviour.d.ts.map +1 -1
  1051. package/dist/bundle-behaviour.js +22 -0
  1052. package/dist/bundle-behaviour.js.map +1 -1
  1053. package/dist/client.d.ts.map +1 -1
  1054. package/dist/client.js +3 -3
  1055. package/dist/client.js.map +1 -1
  1056. package/dist/commands/add.d.ts.map +1 -1
  1057. package/dist/commands/add.js +1 -1
  1058. package/dist/commands/add.js.map +1 -1
  1059. package/dist/commands/ask.d.ts.map +1 -1
  1060. package/dist/commands/ask.js +16 -5
  1061. package/dist/commands/ask.js.map +1 -1
  1062. package/dist/commands/bootstrap.d.ts.map +1 -1
  1063. package/dist/commands/bootstrap.js +9 -5
  1064. package/dist/commands/bootstrap.js.map +1 -1
  1065. package/dist/commands/bundle.d.ts.map +1 -1
  1066. package/dist/commands/bundle.js +8 -0
  1067. package/dist/commands/bundle.js.map +1 -1
  1068. package/dist/commands/capture.d.ts +4 -0
  1069. package/dist/commands/capture.d.ts.map +1 -1
  1070. package/dist/commands/capture.js +29 -2
  1071. package/dist/commands/capture.js.map +1 -1
  1072. package/dist/commands/community-harness.d.ts +4 -0
  1073. package/dist/commands/community-harness.d.ts.map +1 -0
  1074. package/dist/commands/community-harness.js +48 -0
  1075. package/dist/commands/community-harness.js.map +1 -0
  1076. package/dist/commands/compact.d.ts.map +1 -1
  1077. package/dist/commands/compact.js +8 -4
  1078. package/dist/commands/compact.js.map +1 -1
  1079. package/dist/commands/config.d.ts.map +1 -1
  1080. package/dist/commands/config.js +9 -2
  1081. package/dist/commands/config.js.map +1 -1
  1082. package/dist/commands/crash-cart.d.ts.map +1 -1
  1083. package/dist/commands/crash-cart.js +3 -5
  1084. package/dist/commands/crash-cart.js.map +1 -1
  1085. package/dist/commands/create.d.ts.map +1 -1
  1086. package/dist/commands/create.js +7 -5
  1087. package/dist/commands/create.js.map +1 -1
  1088. package/dist/commands/doctor.d.ts.map +1 -1
  1089. package/dist/commands/doctor.js +8 -7
  1090. package/dist/commands/doctor.js.map +1 -1
  1091. package/dist/commands/down.d.ts.map +1 -1
  1092. package/dist/commands/down.js +2 -1
  1093. package/dist/commands/down.js.map +1 -1
  1094. package/dist/commands/grow.d.ts.map +1 -1
  1095. package/dist/commands/grow.js +24 -7
  1096. package/dist/commands/grow.js.map +1 -1
  1097. package/dist/commands/launch.d.ts +1 -0
  1098. package/dist/commands/launch.d.ts.map +1 -1
  1099. package/dist/commands/launch.js +61 -10
  1100. package/dist/commands/launch.js.map +1 -1
  1101. package/dist/commands/policy.d.ts +2 -1
  1102. package/dist/commands/policy.d.ts.map +1 -1
  1103. package/dist/commands/policy.js +66 -7
  1104. package/dist/commands/policy.js.map +1 -1
  1105. package/dist/commands/provider.d.ts +2 -0
  1106. package/dist/commands/provider.d.ts.map +1 -1
  1107. package/dist/commands/provider.js +372 -49
  1108. package/dist/commands/provider.js.map +1 -1
  1109. package/dist/commands/ps.d.ts +13 -0
  1110. package/dist/commands/ps.d.ts.map +1 -1
  1111. package/dist/commands/ps.js +52 -7
  1112. package/dist/commands/ps.js.map +1 -1
  1113. package/dist/commands/queue.d.ts.map +1 -1
  1114. package/dist/commands/queue.js +59 -10
  1115. package/dist/commands/queue.js.map +1 -1
  1116. package/dist/commands/remove.d.ts.map +1 -1
  1117. package/dist/commands/remove.js +20 -1
  1118. package/dist/commands/remove.js.map +1 -1
  1119. package/dist/commands/requirements.d.ts.map +1 -1
  1120. package/dist/commands/requirements.js +8 -5
  1121. package/dist/commands/requirements.js.map +1 -1
  1122. package/dist/commands/restore.js +1 -1
  1123. package/dist/commands/restore.js.map +1 -1
  1124. package/dist/commands/rig-mode.d.ts.map +1 -1
  1125. package/dist/commands/rig-mode.js +106 -18
  1126. package/dist/commands/rig-mode.js.map +1 -1
  1127. package/dist/commands/roster.d.ts +2 -1
  1128. package/dist/commands/roster.d.ts.map +1 -1
  1129. package/dist/commands/roster.js +10 -5
  1130. package/dist/commands/roster.js.map +1 -1
  1131. package/dist/commands/scope.d.ts.map +1 -1
  1132. package/dist/commands/scope.js +47 -9
  1133. package/dist/commands/scope.js.map +1 -1
  1134. package/dist/commands/seat.d.ts.map +1 -1
  1135. package/dist/commands/seat.js +203 -25
  1136. package/dist/commands/seat.js.map +1 -1
  1137. package/dist/commands/send.d.ts +2 -2
  1138. package/dist/commands/send.d.ts.map +1 -1
  1139. package/dist/commands/send.js +79 -12
  1140. package/dist/commands/send.js.map +1 -1
  1141. package/dist/commands/slack.d.ts.map +1 -1
  1142. package/dist/commands/slack.js +23 -5
  1143. package/dist/commands/slack.js.map +1 -1
  1144. package/dist/commands/start.d.ts +11 -0
  1145. package/dist/commands/start.d.ts.map +1 -1
  1146. package/dist/commands/start.js +77 -3
  1147. package/dist/commands/start.js.map +1 -1
  1148. package/dist/commands/status.d.ts.map +1 -1
  1149. package/dist/commands/status.js +60 -2
  1150. package/dist/commands/status.js.map +1 -1
  1151. package/dist/commands/up.d.ts.map +1 -1
  1152. package/dist/commands/up.js +58 -0
  1153. package/dist/commands/up.js.map +1 -1
  1154. package/dist/commands/usage.d.ts +12 -0
  1155. package/dist/commands/usage.d.ts.map +1 -1
  1156. package/dist/commands/usage.js +108 -0
  1157. package/dist/commands/usage.js.map +1 -1
  1158. package/dist/commands/watchdog.d.ts.map +1 -1
  1159. package/dist/commands/watchdog.js +20 -7
  1160. package/dist/commands/watchdog.js.map +1 -1
  1161. package/dist/commands/whoami.d.ts +36 -0
  1162. package/dist/commands/whoami.d.ts.map +1 -1
  1163. package/dist/commands/whoami.js +125 -7
  1164. package/dist/commands/whoami.js.map +1 -1
  1165. package/dist/commands/workflow.d.ts.map +1 -1
  1166. package/dist/commands/workflow.js +53 -5
  1167. package/dist/commands/workflow.js.map +1 -1
  1168. package/dist/config-store.d.ts +14 -1
  1169. package/dist/config-store.d.ts.map +1 -1
  1170. package/dist/config-store.js +50 -1
  1171. package/dist/config-store.js.map +1 -1
  1172. package/dist/daemon-lifecycle.d.ts +2 -2
  1173. package/dist/daemon-lifecycle.d.ts.map +1 -1
  1174. package/dist/daemon-lifecycle.js +5 -4
  1175. package/dist/daemon-lifecycle.js.map +1 -1
  1176. package/dist/host-registry.d.ts.map +1 -1
  1177. package/dist/host-registry.js +6 -3
  1178. package/dist/host-registry.js.map +1 -1
  1179. package/dist/index.d.ts +1 -0
  1180. package/dist/index.d.ts.map +1 -1
  1181. package/dist/index.js +4 -7
  1182. package/dist/index.js.map +1 -1
  1183. package/dist/lazy-program.d.ts +9 -0
  1184. package/dist/lazy-program.d.ts.map +1 -0
  1185. package/dist/lazy-program.js +111 -0
  1186. package/dist/lazy-program.js.map +1 -0
  1187. package/dist/lib/bundle-check.js +2 -2
  1188. package/dist/lib/bundle-check.js.map +1 -1
  1189. package/dist/lib/bundle-source.d.ts +1 -0
  1190. package/dist/lib/bundle-source.d.ts.map +1 -1
  1191. package/dist/lib/bundle-source.js +1 -0
  1192. package/dist/lib/bundle-source.js.map +1 -1
  1193. package/dist/lib/codex-auth.d.ts.map +1 -1
  1194. package/dist/lib/codex-auth.js +17 -1
  1195. package/dist/lib/codex-auth.js.map +1 -1
  1196. package/dist/lib/context-install.js +1 -1
  1197. package/dist/lib/context-install.js.map +1 -1
  1198. package/dist/lib/file-transfer.js +1 -1
  1199. package/dist/lib/file-transfer.js.map +1 -1
  1200. package/dist/lib/frontmatter.d.ts +11 -0
  1201. package/dist/lib/frontmatter.d.ts.map +1 -0
  1202. package/dist/lib/frontmatter.js +20 -0
  1203. package/dist/lib/frontmatter.js.map +1 -0
  1204. package/dist/lib/model-observation.d.ts +13 -0
  1205. package/dist/lib/model-observation.d.ts.map +1 -0
  1206. package/dist/lib/model-observation.js +25 -0
  1207. package/dist/lib/model-observation.js.map +1 -0
  1208. package/dist/lib/permission-policy/policy-spec.d.ts.map +1 -1
  1209. package/dist/lib/permission-policy/policy-spec.js +5 -7
  1210. package/dist/lib/permission-policy/policy-spec.js.map +1 -1
  1211. package/dist/lib/scope/logical-checkbox.js +1 -1
  1212. package/dist/lib/scope/logical-checkbox.js.map +1 -1
  1213. package/dist/lib/scope/scope-fs.d.ts +8 -0
  1214. package/dist/lib/scope/scope-fs.d.ts.map +1 -1
  1215. package/dist/lib/scope/scope-fs.js +50 -1
  1216. package/dist/lib/scope/scope-fs.js.map +1 -1
  1217. package/dist/lib/work-install.d.ts.map +1 -1
  1218. package/dist/lib/work-install.js +3 -13
  1219. package/dist/lib/work-install.js.map +1 -1
  1220. package/dist/mcp-server.d.ts.map +1 -1
  1221. package/dist/mcp-server.js +114 -0
  1222. package/dist/mcp-server.js.map +1 -1
  1223. package/dist/program-root.d.ts +5 -0
  1224. package/dist/program-root.d.ts.map +1 -0
  1225. package/dist/program-root.js +11 -0
  1226. package/dist/program-root.js.map +1 -0
  1227. package/dist/queue-read-compact.d.ts +29 -0
  1228. package/dist/queue-read-compact.d.ts.map +1 -0
  1229. package/dist/queue-read-compact.js +74 -0
  1230. package/dist/queue-read-compact.js.map +1 -0
  1231. package/dist/remote-host-ops.d.ts +21 -0
  1232. package/dist/remote-host-ops.d.ts.map +1 -1
  1233. package/dist/remote-host-ops.js +25 -12
  1234. package/dist/remote-host-ops.js.map +1 -1
  1235. package/package.json +1 -1
  1236. package/tui/dist/attention/attention-model.js +6 -5
  1237. package/tui/dist/attention/attention-model.js.map +1 -1
  1238. package/tui/dist/attention/source-continuity.d.ts.map +1 -1
  1239. package/tui/dist/attention/source-continuity.js +2 -0
  1240. package/tui/dist/attention/source-continuity.js.map +1 -1
  1241. package/tui/dist/commands/registry.d.ts.map +1 -1
  1242. package/tui/dist/commands/registry.js +3 -0
  1243. package/tui/dist/commands/registry.js.map +1 -1
  1244. package/tui/dist/config/config-model.d.ts.map +1 -1
  1245. package/tui/dist/config/config-model.js +6 -1
  1246. package/tui/dist/config/config-model.js.map +1 -1
  1247. package/tui/dist/cost/cost-model.d.ts +12 -0
  1248. package/tui/dist/cost/cost-model.d.ts.map +1 -0
  1249. package/tui/dist/cost/cost-model.js +141 -0
  1250. package/tui/dist/cost/cost-model.js.map +1 -0
  1251. package/tui/dist/cost/cost-read.d.ts +19 -0
  1252. package/tui/dist/cost/cost-read.d.ts.map +1 -0
  1253. package/tui/dist/cost/cost-read.js +4 -0
  1254. package/tui/dist/cost/cost-read.js.map +1 -0
  1255. package/tui/dist/daemon-client.d.ts +13 -0
  1256. package/tui/dist/daemon-client.d.ts.map +1 -1
  1257. package/tui/dist/daemon-client.js +77 -1
  1258. package/tui/dist/daemon-client.js.map +1 -1
  1259. package/tui/dist/detail.d.ts +2 -0
  1260. package/tui/dist/detail.d.ts.map +1 -1
  1261. package/tui/dist/detail.js +5 -0
  1262. package/tui/dist/detail.js.map +1 -1
  1263. package/tui/dist/execution/execution-model.d.ts.map +1 -1
  1264. package/tui/dist/execution/execution-model.js +38 -18
  1265. package/tui/dist/execution/execution-model.js.map +1 -1
  1266. package/tui/dist/helper-cwd.d.ts +3 -0
  1267. package/tui/dist/helper-cwd.d.ts.map +1 -0
  1268. package/tui/dist/helper-cwd.js +16 -0
  1269. package/tui/dist/helper-cwd.js.map +1 -0
  1270. package/tui/dist/hydrate.d.ts.map +1 -1
  1271. package/tui/dist/hydrate.js +32 -1
  1272. package/tui/dist/hydrate.js.map +1 -1
  1273. package/tui/dist/input.d.ts.map +1 -1
  1274. package/tui/dist/input.js +2 -1
  1275. package/tui/dist/input.js.map +1 -1
  1276. package/tui/dist/local-reading.d.ts.map +1 -1
  1277. package/tui/dist/local-reading.js +2 -1
  1278. package/tui/dist/local-reading.js.map +1 -1
  1279. package/tui/dist/main.js +6 -5
  1280. package/tui/dist/main.js.map +1 -1
  1281. package/tui/dist/pulse/render-pulse.d.ts.map +1 -1
  1282. package/tui/dist/pulse/render-pulse.js +7 -1
  1283. package/tui/dist/pulse/render-pulse.js.map +1 -1
  1284. package/tui/dist/reading.js +1 -1
  1285. package/tui/dist/reading.js.map +1 -1
  1286. package/tui/dist/render.d.ts.map +1 -1
  1287. package/tui/dist/render.js +10 -1
  1288. package/tui/dist/render.js.map +1 -1
  1289. package/tui/dist/scopes/scopes-model.d.ts +6 -1
  1290. package/tui/dist/scopes/scopes-model.d.ts.map +1 -1
  1291. package/tui/dist/scopes/scopes-model.js +33 -13
  1292. package/tui/dist/scopes/scopes-model.js.map +1 -1
  1293. package/tui/dist/sections.d.ts.map +1 -1
  1294. package/tui/dist/sections.js +4 -1
  1295. package/tui/dist/sections.js.map +1 -1
  1296. package/tui/dist/socket-server.d.ts.map +1 -1
  1297. package/tui/dist/socket-server.js +2 -1
  1298. package/tui/dist/socket-server.js.map +1 -1
  1299. package/tui/dist/specs/launch.d.ts.map +1 -1
  1300. package/tui/dist/specs/launch.js +2 -1
  1301. package/tui/dist/specs/launch.js.map +1 -1
  1302. package/tui/dist/startup.js +1 -1
  1303. package/tui/dist/startup.js.map +1 -1
  1304. package/tui/dist/state.d.ts.map +1 -1
  1305. package/tui/dist/state.js +8 -4
  1306. package/tui/dist/state.js.map +1 -1
  1307. package/tui/dist/terminals/open-window.d.ts +11 -1
  1308. package/tui/dist/terminals/open-window.d.ts.map +1 -1
  1309. package/tui/dist/terminals/open-window.js +48 -2
  1310. package/tui/dist/terminals/open-window.js.map +1 -1
  1311. package/tui/dist/types.d.ts +9 -0
  1312. package/tui/dist/types.d.ts.map +1 -1
  1313. package/tui/dist/usage/usage-hosts-model.d.ts +17 -0
  1314. package/tui/dist/usage/usage-hosts-model.d.ts.map +1 -0
  1315. package/tui/dist/usage/usage-hosts-model.js +73 -0
  1316. package/tui/dist/usage/usage-hosts-model.js.map +1 -0
  1317. package/tui/dist/usage/usage-model.d.ts +41 -0
  1318. package/tui/dist/usage/usage-model.d.ts.map +1 -0
  1319. package/tui/dist/usage/usage-model.js +196 -0
  1320. package/tui/dist/usage/usage-model.js.map +1 -0
  1321. package/ui/dist/assets/{index-DhmxKuZf.js → index-CVgod3Pm.js} +2 -2
  1322. package/ui/dist/index.html +1 -1
@@ -1,15 +1,22 @@
1
+ import { currentClaudeMcpMenu } from "./claude-mcp-menu.js";
2
+ import stringWidth from "string-width";
1
3
  import { CODEX_TURN_STATUS_PATTERN } from "./codex-pane-patterns.js";
2
4
  import { randomUUID } from "node:crypto";
3
- import { inspectStartupStagedText, startupSubmissionEvidence } from "./startup-submission-evidence.js";
5
+ import { COLLAPSED_PASTE, composerRegion, isComposerFooter, inspectStartupStagedText, QUEUED_PLACEHOLDER, startupSubmissionEvidence } from "./startup-submission-evidence.js";
6
+ import { composerContainsOwnedText, composerNumberedChoicePattern, composerPromptClassForRuntime, composerPromptIsAmbiguous, composerSelectionPrefixPattern, COMPOSER_DRAFT_PATTERN, COMPOSER_EMPTY_CODEX_PLACEHOLDER_PATTERN as CODEX_EMPTY_COMPOSER_PATTERN, COMPOSER_EMPTY_PATTERN, COMPOSER_PROMPT_CLASS, COMPOSER_SELECTION_PATTERN, COMPOSER_SELECTION_SCAN_PATTERN, findComposerInputLineIndex, stripComposerPromptGlyph, readComposerPromptLine, COMPOSER_COLLAPSED_PASTE_PATTERN, } from "./composer-prompts.js";
4
7
  export { inspectStartupStagedText } from "./startup-submission-evidence.js";
5
8
  import { OutboxHandler } from "./outbox-handler.js";
9
+ import { DraftAwareDelivery } from "./draft-aware-delivery.js";
6
10
  import { claudeResumeRotation } from "./session-registry.js";
7
- import { latestHookWaitsOnPerson } from "./agent-activity-store.js";
11
+ import { latestHookPermissionPending, latestHookWaitsOnPerson } from "./agent-activity-store.js";
12
+ import { DeliveryGuardError } from "./seat-delivery-guard.js";
8
13
  import { wrapPaneEnvelope, appendDeliveredSegment } from "../lib/pane-envelope.js";
9
14
  import { SeatIdentityStore } from "./seat-identity-store.js";
10
15
  import { isShellForeground } from "./shell-classifier.js";
11
16
  import { observeClaudeDelivery, verifyCodexPaneProcess } from "./native-process-lineage.js";
12
17
  import { hashSentText } from "./capture-observer.js";
18
+ import { isAttentionRequiredReadinessCode } from "./runtime-adapter.js";
19
+ import { getLatestStartupFailedEvent, hasClaudeResumeIdentityWork, isRecoverableStartupFailure } from "./startup-orchestrator.js";
13
20
  // OPR.0.4.1.10 — send-readiness freshness. The runtime-hook store keeps a 5min freshness for activity
14
21
  // DISPLAY, but "safe to send NOW" needs a tight window: a stale "idle" read must not authorize a send
15
22
  // into what may since have become a prompt. Beyond this window the hook is ignored for send-readiness
@@ -20,13 +27,18 @@ import { hashSentText } from "./capture-observer.js";
20
27
  // window comfortably spans one inter-tool gap (so a mid-turn reading stays trusted) while refusing to
21
28
  // authorize a send from a reading tens of seconds old. (EXA: agtx#14, daintree#3938.)
22
29
  const SEND_READINESS_FRESHNESS_MS = 15_000;
30
+ // transport.verify_input_box: each extra input-box read gives up after this long and reads `unavailable`. A deadline
31
+ // budget, not a guarantee: the pre-Enter read comes after the existing 200 ms wait and can add about this much before
32
+ // Enter, and the post-Enter process read about this much before the reply. A read that misses it leaves the send
33
+ // unconfirmed, and Enter is still sent once.
34
+ const INPUT_BOX_READ_DEADLINE_MS = 500;
23
35
  // Mid-work detection patterns (cheap heuristics)
24
36
  const MID_WORK_PATTERNS = [
25
37
  /[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/, // spinner chars
26
38
  /Working/,
27
39
  /^[✶✢✳✻✽·]\s+\S.*(?:…|\.{3})\s+\([^)]*\bthinking\)$/m,
28
40
  /esc to interrupt/,
29
- /^[❯›]\s*\d+\.\s/m, // trust/consent prompt choices (e.g. '› 1. Yes, continue')
41
+ COMPOSER_SELECTION_SCAN_PATTERN, // trust/consent prompt choices (e.g. '› 1. Yes, continue')
30
42
  ];
31
43
  // Idle-prompt patterns: empty prompt line (no typed text after the char).
32
44
  // Lines like '❯ Working on a task.' have text after the prompt char and
@@ -37,13 +49,12 @@ const MID_WORK_PATTERNS = [
37
49
  // normally sits above it, but Codex hides that row while it streams assistant output. So the
38
50
  // placeholder counts as idle only through MID_WORK_PATTERNS here, and classifySendReadiness
39
51
  // never lets a placeholder-only verdict override a display-fresh running/needs_input hook.
40
- const CODEX_EMPTY_COMPOSER_PATTERN = /^›\s+Ask Codex to do anything\s*$/;
41
52
  const IDLE_PROMPT_PATTERNS = [
42
- /^[❯›]\s*$/, // prompt char + optional whitespace + end-of-line only
53
+ COMPOSER_EMPTY_PATTERN, // prompt char + optional whitespace + end-of-line only
43
54
  CODEX_EMPTY_COMPOSER_PATTERN,
44
55
  ];
45
56
  const PROMPT_DRAFT_PATTERNS = [
46
- /^[❯›]\s+\S/,
57
+ COMPOSER_DRAFT_PATTERN,
47
58
  ];
48
59
  // Claude's permission-mode footers: default, accept edits, bypass, auto and plan, optionally after a
49
60
  // vim-mode marker such as "-- INSERT --" (#808). A suffix such as "· 1 shell" can follow the mode.
@@ -133,15 +144,20 @@ const CLAUDE_LIVE_STATUS_PATTERN = /^[✶✢✳✻✽·*]\s+\S[^(]*(?:…|\.{3})
133
144
  // ("✻ Crunched for 2s") have no trailing ellipsis. Only callers that must not send into a busy
134
145
  // pane opt in (ClassifyPaneOptions.timerlessStatusIsLive); ordinary send readiness is unchanged.
135
146
  const CLAUDE_TIMERLESS_STATUS_PATTERN = /^[✶✢✳✻✽·*]\s+\S.*(?:…|\.{3})$/;
147
+ /** The current Claude footer follows the input frame; earlier examples are history or draft text. */
148
+ function findClaudeFooterIndex(lines) {
149
+ let bar = lines.length - 1;
150
+ while (bar >= 0 && (!lines[bar].trim() || CLAUDE_STATUS_WARNINGS.some(pattern => pattern.test(lines[bar].trim()))))
151
+ bar--;
152
+ return bar;
153
+ }
136
154
  function findClaudeComposer(paneContent, options = {}) {
137
155
  // Preserve columns: a multiline draft may contain indented border/prompt text.
138
156
  // This classifier scans at most 20 physical lines; captures can be taller.
139
157
  // Exhausting the scan without reaching the status head is unknown, not idle.
140
158
  const lines = paneContent.split("\n").slice(-20)
141
159
  .map((line) => line.trimEnd()).filter((line) => line.trim().length > 0);
142
- let bar = lines.length - 1;
143
- while (bar >= 0 && CLAUDE_STATUS_WARNINGS.some((pattern) => pattern.test(lines[bar].trim())))
144
- bar--;
160
+ const bar = findClaudeFooterIndex(lines);
145
161
  const supportedWarningFooter = lines[bar]?.trim() === "⏵⏵ accept edits on (shift+tab to cycle) · ← for agents";
146
162
  const modeFooter = CLAUDE_MODE_FOOTER.test(lines[bar]?.trim() ?? "");
147
163
  let indent = /^([ \t]*)─{3,}$/.exec(lines[bar - 1] ?? "")?.[1];
@@ -154,7 +170,8 @@ function findClaudeComposer(paneContent, options = {}) {
154
170
  if (bar < 2 || !CLAUDE_ANCHORED_MODE_FOOTER.test(lines[bar].trim()))
155
171
  return null;
156
172
  prompt = lines[bar - 1] ?? "";
157
- indent = /^([ \t]*)❯\s*$/.exec(prompt)?.[1];
173
+ const input = readComposerPromptLine(prompt, composerPromptClassForRuntime("claude-code"));
174
+ indent = input && !input.text.trim() ? input.indent : undefined;
158
175
  if (indent === undefined)
159
176
  return null;
160
177
  statusStart = bar - 2;
@@ -162,7 +179,7 @@ function findClaudeComposer(paneContent, options = {}) {
162
179
  else {
163
180
  const upper = lines[bar - 3] ?? "";
164
181
  if (!upper.startsWith(indent) || !/^─{3,}(?: .+ ─+)?$/.test(upper.slice(indent.length)) ||
165
- !prompt.startsWith(`${indent}❯`) || !/^❯(?:\s|$)/.test(prompt.slice(indent.length)))
182
+ !prompt.startsWith(`${indent}❯`) || !readComposerPromptLine(prompt, composerPromptClassForRuntime("claude-code")))
166
183
  return null;
167
184
  }
168
185
  let liveStatus = null;
@@ -188,6 +205,163 @@ function findClaudeComposer(paneContent, options = {}) {
188
205
  }
189
206
  return { text: prompt.slice(indent.length), bar: lines[bar].trim(), framed, hasWarnings: bar < lines.length - 1, supportedWarningFooter, modeFooter, headSeen, liveStatus };
190
207
  }
208
+ const unknown = () => ({ state: "unknown", rows: [], columns: 0, cursorAtEnd: false, collapsedPaste: null });
209
+ const rule = (line) => /^[─━═-]{6,}(?: .+ [─━═-]+)?$/.test(line.trim());
210
+ const codexFooter = (line) => isComposerFooter(line)
211
+ || /(?:\b\d+% context left\b|\bContext \[)/.test(line);
212
+ /** Preserve faint styling so autocomplete ghosts can be separated from authored
213
+ * text. Color channel values must never be interpreted as style codes. */
214
+ function styledTextLines(screen) {
215
+ let faint = false;
216
+ return screen.replace(/\r\n/g, "\n").split("\n").map(raw => {
217
+ let text = "", offset = 0;
218
+ const faintCells = [];
219
+ const append = (value) => { text += value; faintCells.push(...Array(value.length).fill(faint)); };
220
+ for (const match of raw.matchAll(/\x1b\[([0-9;:]*)m/g)) {
221
+ append(raw.slice(offset, match.index));
222
+ const codes = match[1].split(";");
223
+ for (let i = 0; i < codes.length; i++) {
224
+ const code = codes[i];
225
+ if (/^(?:38|48|58):/.test(code))
226
+ continue; // Colon-form color is one parameter.
227
+ if (code === "38" || code === "48" || code === "58") {
228
+ const skip = codes[i + 1] === "5" ? 2 : codes[i + 1] === "2" ? (codes[i + 2] === "" ? 5 : 4) : 0;
229
+ // Color channels are not style codes. Ambiguous compound sequences
230
+ // provide no faint-style proof, rather than interpreting an RGB 2 as dim.
231
+ if (!skip || i + skip !== codes.length - 1) {
232
+ faint = false;
233
+ break;
234
+ }
235
+ i += skip;
236
+ }
237
+ else if (code === "" || code === "0" || code === "22")
238
+ faint = false;
239
+ else if (code === "2")
240
+ faint = true;
241
+ }
242
+ offset = match.index + match[0].length;
243
+ }
244
+ append(raw.slice(offset));
245
+ return { text, faintCells };
246
+ });
247
+ }
248
+ /** Inspect only the visible input containing the current cursor. Activity, hook
249
+ * freshness and text elsewhere in the pane are not evidence that input is empty.
250
+ */
251
+ export function inspectComposerInput(snapshot, runtime = null, owned) {
252
+ const promptClass = composerPromptClassForRuntime(runtime);
253
+ if (!snapshot || snapshot.inMode)
254
+ return unknown();
255
+ const { x, y, width, height } = snapshot.cursor;
256
+ if (![x, y, width, height].every(Number.isInteger) || x < 0 || y < 0 || x >= width || y >= height || width < 3 || height < 1)
257
+ return unknown();
258
+ const styled = styledTextLines(snapshot.screen);
259
+ const lines = styled.map(line => line.text);
260
+ if (lines.at(-1) === "")
261
+ lines.pop();
262
+ if (lines.length > height || !lines[y])
263
+ return unknown();
264
+ const footerAt = findClaudeFooterIndex(lines);
265
+ const footer = lines[footerAt]?.trim() ?? "";
266
+ let claudeEnd = footerAt - 1;
267
+ while (claudeEnd >= 0 && !lines[claudeEnd].trim())
268
+ claudeEnd--;
269
+ const candidates = [];
270
+ for (let start = 0; start <= y; start++) {
271
+ const prompt = readComposerPromptLine(lines[start] ?? "", promptClass);
272
+ if (!prompt || !/^ *$/.test(prompt.indent))
273
+ continue;
274
+ const numbered = composerSelectionPrefixPattern(promptClass).test(lines[start].trimStart()) || /^\d+[.)]\s/.test(prompt.text);
275
+ const prefix = prompt.prefix;
276
+ let end = -1;
277
+ if (prompt.glyph === "❯") {
278
+ // Share the main reader's terminal footer anchor. A quoted frame within a
279
+ // multiline draft cannot supply either the closing rule or its indentation.
280
+ const alignedRule = (line) => rule(line) && line.length - line.trimStart().length === prompt.indent.length;
281
+ if ((!isComposerFooter(footer) && !CLAUDE_MODE_FOOTER.test(footer))
282
+ || !alignedRule(lines[start - 1] ?? "") || !alignedRule(lines[claudeEnd] ?? ""))
283
+ continue;
284
+ end = claudeEnd;
285
+ }
286
+ else {
287
+ for (let i = start + 1; i < lines.length; i++) {
288
+ if (lines[i].trim())
289
+ continue;
290
+ const tail = lines.slice(i + 1).filter(line => line.trim());
291
+ if (lines[i + 1]?.trim() && tail.length === 1 && codexFooter(tail[0].trim())) {
292
+ end = i;
293
+ break;
294
+ }
295
+ }
296
+ }
297
+ if (end <= y || x < prefix)
298
+ continue;
299
+ const rows = [prompt.text];
300
+ let valid = true;
301
+ for (let i = start + 1; i < end; i++) {
302
+ const line = lines[i];
303
+ if (line.trim() && !line.startsWith(" ".repeat(prefix))) {
304
+ valid = false;
305
+ break;
306
+ }
307
+ rows.push(line.slice(prefix));
308
+ }
309
+ if (!valid)
310
+ continue;
311
+ // A suggestion is a wholly dim suffix beginning at the current cursor.
312
+ // A literal placeholder or text before a moved cursor remains authored input.
313
+ const cursorRow = y - start;
314
+ let ghost;
315
+ const currentRow = rows[cursorRow];
316
+ let cursorOffset = x === prefix ? 0 : -1;
317
+ let offset = 0;
318
+ for (const character of currentRow) {
319
+ offset += character.length;
320
+ if (stringWidth(currentRow.slice(0, offset)) === x - prefix)
321
+ cursorOffset = offset;
322
+ }
323
+ if (cursorOffset >= 0) {
324
+ const suffix = rows.slice(cursorRow).map((row, i) => ({
325
+ text: row.slice(i === 0 ? cursorOffset : 0),
326
+ faint: styled[y + i].faintCells.slice(prefix + (i === 0 ? cursorOffset : 0), prefix + row.length),
327
+ }));
328
+ if (suffix.some(row => row.text.length > 0) && suffix.every(row => row.faint.every(Boolean))) {
329
+ ghost = { line: y, offset: prefix + cursorOffset, continuationRows: rows.length - cursorRow - 1 };
330
+ rows.splice(cursorRow + 1);
331
+ rows[cursorRow] = currentRow.slice(0, cursorOffset);
332
+ }
333
+ }
334
+ const visible = rows.map(line => line.trimEnd());
335
+ // The cursor bounds authored whitespace on the last input row. Captures may
336
+ // omit trailing blank cells or include screen padding; neither changes the bytes to submit.
337
+ if (cursorRow === rows.length - 1) {
338
+ const cellWidth = stringWidth(rows[cursorRow]);
339
+ if (x - prefix >= cellWidth)
340
+ visible[cursorRow] = rows[cursorRow] + " ".repeat(x - prefix - cellWidth);
341
+ else if (cursorOffset >= 0 && !rows[cursorRow].slice(cursorOffset).trim())
342
+ visible[cursorRow] = rows[cursorRow].slice(0, cursorOffset);
343
+ }
344
+ const atStart = y === start && x === prefix;
345
+ const empty = atStart && rows.length === 1 && rows[0] === "";
346
+ const last = visible.length - 1;
347
+ const cursorAtEnd = y === start + last && x === prefix + stringWidth(visible[last] ?? "");
348
+ const label = visible.length === 1 && COMPOSER_COLLAPSED_PASTE_PATTERN.test(visible[0]) ? visible[0] : null;
349
+ const frame = JSON.stringify([prompt.glyph, prefix, width, footer]);
350
+ const input = { state: empty ? "empty" : "text", rows: visible, columns: width - prefix, cursorAtEnd, collapsedPaste: label, frame,
351
+ ...(ghost ? { ghost } : {}) };
352
+ // A numbered body is allowed only as this operation's exact text in its
353
+ // previously empty composer. A newly observed menu gains no such authority.
354
+ if (numbered && !(owned?.frame === frame && composerContainsOwnedText(input, owned.text)))
355
+ continue;
356
+ if (owned?.frame === frame && cursorAtEnd && visible.join("\n").includes("[Pasted text #")
357
+ && hasExpectedStagedText(`${prompt.glyph} ${visible.join("\n")}`, owned.text, runtime)) {
358
+ input.collapsedPaste = visible.join("\n");
359
+ input.pasteMatchesExpected = true;
360
+ }
361
+ candidates.push(input);
362
+ }
363
+ return candidates.length === 1 ? candidates[0] : unknown();
364
+ }
191
365
  function findPromptDraftBeforeFooter(paneContent) {
192
366
  const rawLines = paneContent.split("\n").map((line) => line.trimEnd());
193
367
  let lastLineIndex = rawLines.length - 1;
@@ -204,8 +378,9 @@ function findPromptDraftBeforeFooter(paneContent) {
204
378
  if (priorLine.trim().length === 0)
205
379
  return null;
206
380
  const priorTrimmed = priorLine.trim();
207
- const looksLikeDraft = PROMPT_DRAFT_PATTERNS.some((pattern) => pattern.test(priorTrimmed));
208
- const looksLikeSelection = /^[❯›]\s*\d+\.\s/.test(priorTrimmed);
381
+ const prompt = readComposerPromptLine(priorLine);
382
+ const looksLikeDraft = prompt !== null && prompt.text.trim().length > 0;
383
+ const looksLikeSelection = COMPOSER_SELECTION_PATTERN.test(priorTrimmed);
209
384
  if (!looksLikeDraft || looksLikeSelection)
210
385
  return null;
211
386
  return truncateEvidence(priorTrimmed);
@@ -282,7 +457,25 @@ function draftComposerLine(lines, verdict) {
282
457
  return null;
283
458
  return lines[last].trim();
284
459
  }
460
+ /** A community harness's screen never proves it's idle: a quiet prompt can be an approval or a pause, so its idle
461
+ * reading becomes unknown. Positive work and attention are kept. `communityHarness` is the seat's recorded selection
462
+ * (`communityHarnessOf`); without it the classification is returned as it is. */
463
+ export function communityHarnessPaneActivity(classification, communityHarness) {
464
+ return communityHarness && classification.state === "agent_idle"
465
+ ? { state: "unknown", reason: "community_harness_no_idle_signal", evidence: classification.evidence }
466
+ : classification;
467
+ }
285
468
  export function classifyPaneActivity(paneContent, options = {}) {
469
+ if (options.composerSnapshot) {
470
+ const snapshot = options.composerSnapshot;
471
+ const { ghost } = inspectComposerInput(snapshot, options.runtime);
472
+ const lines = styledTextLines(snapshot.screen).map(line => line.text);
473
+ if (ghost) {
474
+ lines[ghost.line] = lines[ghost.line].slice(0, ghost.offset);
475
+ lines.splice(ghost.line + 1, ghost.continuationRows);
476
+ }
477
+ paneContent = lines.join("\n");
478
+ }
286
479
  const lastNonBlank = trimPaneLines(paneContent);
287
480
  if (lastNonBlank.length === 0) {
288
481
  return { state: "unknown", reason: "empty_capture", evidence: null };
@@ -299,15 +492,18 @@ export function classifyPaneActivity(paneContent, options = {}) {
299
492
  : null;
300
493
  const claudeComposer = findClaudeComposer(paneContent, options);
301
494
  const oldQuestionEnd = promptScanLines.lastIndexOf(CLAUDE_QUESTION_FOOTER);
495
+ const permissionPromptEvidence = findPatternEvidence(promptScanLines, PERMISSION_PROMPT_PATTERNS);
302
496
  // A complete later empty composer with a recognized Claude bar makes the preceding question history.
303
497
  // Keep draft handling and selectors without this dialog boundary unchanged.
304
498
  const selectionLines = claudeComposer?.framed &&
305
499
  CLAUDE_ANCHORED_MODE_FOOTER.test(claudeComposer.bar) &&
306
500
  IDLE_PROMPT_PATTERNS.some(pattern => pattern.test(claudeComposer.text)) &&
307
501
  oldQuestionEnd >= 0 && oldQuestionEnd < promptScanLines.length - 1
308
- ? promptScanLines.slice(oldQuestionEnd + 1) : promptScanLines;
309
- const selectionPromptEvidence = findCurrentClaudeQuestion(paneContent) ??
310
- findPatternEvidence(selectionLines, [/^[❯›]\s*\d+\.\s/m]);
502
+ ? promptScanLines.slice(oldQuestionEnd + 1)
503
+ : options.runtime === "codex" && !permissionPromptEvidence
504
+ ? codexCurrentSelectionLines(paneContent) ?? promptScanLines : promptScanLines;
505
+ const selectionPromptEvidence = currentClaudeMcpMenu(paneContent) ?? findCurrentClaudeQuestion(paneContent) ??
506
+ findPatternEvidence(selectionLines, [COMPOSER_SELECTION_SCAN_PATTERN]);
311
507
  if (selectionPromptEvidence) {
312
508
  return {
313
509
  state: "attention",
@@ -318,7 +514,6 @@ export function classifyPaneActivity(paneContent, options = {}) {
318
514
  // OPR.0.4.1.10 (FR-1c): a permission/confirmation question is attention even when its selector is
319
515
  // not in view — checked before the idle short-circuits so a permission block above an idle-looking
320
516
  // footer never reads as idle (which would let a default send land on it).
321
- const permissionPromptEvidence = findPatternEvidence(promptScanLines, PERMISSION_PROMPT_PATTERNS);
322
517
  if (permissionPromptEvidence) {
323
518
  return {
324
519
  state: "attention",
@@ -514,10 +709,12 @@ export async function probeSessionActivity(input) {
514
709
  };
515
710
  const captureSeq = observed ? nextCaptureSeq++ : 0;
516
711
  try {
517
- const paneContent = await tmuxAdapter.capturePaneContent(sessionName, 20);
712
+ const snapshot = input.composerSnapshot ? await input.composerSnapshot().catch(() => null) : null;
713
+ const paneContent = snapshot?.screen ?? await tmuxAdapter.capturePaneContent(sessionName, 20);
518
714
  const capturedAt = new Date().toISOString();
519
715
  const classifyOptions = { timerlessStatusIsLive: input.timerlessStatusIsLive === true };
520
- let classification = classifyPaneActivity(paneContent ?? "", classifyOptions);
716
+ let classification = communityHarnessPaneActivity(classifyPaneActivity(paneContent ?? "", { ...classifyOptions,
717
+ composerSnapshot: snapshot ?? undefined, runtime }), input.communityHarness);
521
718
  // Claude renders its prompt suggestion faint, which the plain capture loses. When a Claude pane reads
522
719
  // as a draft, one capture with styling is classified on its own. Newer attention or work there is
523
720
  // kept. A faint suggestion counts only when it's the same composer text this capture read as a draft;
@@ -625,7 +822,9 @@ function verificationSnippet(text) {
625
822
  /** Count whole visible envelope blocks, not prefixes of longer messages.
626
823
  * A new envelope, prompt or same-column output ends an indented user block.
627
824
  * Whitespace is compared only after retaining boundaries and quote indentation. */
628
- function countEnvelopeOccurrences(transcript, expected) {
825
+ function countEnvelopeOccurrences(transcript, expected, promptClass) {
826
+ const promptPrefix = new RegExp(`^[${promptClass}]\\s+`);
827
+ const promptBoundary = new RegExp(`^[${promptClass}](?:\\s|$)`);
629
828
  const lines = transcript.split("\n");
630
829
  let count = 0;
631
830
  let block = null;
@@ -640,8 +839,8 @@ function countEnvelopeOccurrences(transcript, expected) {
640
839
  for (let i = 0; i < lines.length; i++) {
641
840
  const line = lines[i];
642
841
  const indent = /^[ \t]*/.exec(line)[0].length;
643
- const prompted = /^[❯›]\s+/.test(line.slice(indent));
644
- const header = line.slice(indent).replace(/^[❯›]\s+/, "");
842
+ const prompted = promptPrefix.test(line.slice(indent));
843
+ const header = line.slice(indent).replace(promptPrefix, "");
645
844
  const atBoundary = (!block && !inAgentOutput) || indent <= blockIndent;
646
845
  const candidate = normalizeVerificationText([header, ...lines.slice(i + 1, i + 5)].join("\n"));
647
846
  const envelopeHeader = /^From: [^\n]+\nTo: [^\n]+\n(?:Sent: [^\n]+\n)?---\n/.test(candidate);
@@ -660,7 +859,7 @@ function countEnvelopeOccurrences(transcript, expected) {
660
859
  finish();
661
860
  inAgentOutput = true;
662
861
  }
663
- else if (atBoundary && /^[❯›](?:\s|$)/.test(line.slice(indent))) {
862
+ else if (atBoundary && promptBoundary.test(line.slice(indent))) {
664
863
  finish();
665
864
  }
666
865
  else if (block) {
@@ -673,15 +872,16 @@ function countEnvelopeOccurrences(transcript, expected) {
673
872
  /** The last prompt marker owns the current input region. Never count it or its
674
873
  * continuation lines as submitted transcript, in either capture. If a capture
675
874
  * ends at a historical prompt with no later input marker, leave it unconfirmed. */
676
- function verificationTranscript(content) {
875
+ function verificationTranscript(content, promptClass) {
876
+ const indentedPrompt = new RegExp(`^([ \\t]*)[${promptClass}]`);
677
877
  const lines = (content ?? "").replace(/\r\n/g, "\n").split("\n");
678
- let inputAt = lastPromptLine(lines);
878
+ let inputAt = findComposerInputLineIndex(lines, promptClass);
679
879
  if (inputAt >= 0) {
680
880
  let indent = /^[ \t]*/.exec(lines[inputAt])[0].length;
681
881
  // Claude indents draft continuation lines. A quoted prompt on one of them
682
882
  // belongs to the enclosing input, not a new transcript/composer boundary.
683
883
  for (let i = inputAt - 1; i >= 0; i--) {
684
- const prompt = /^([ \t]*)[❯›]/.exec(lines[i]);
884
+ const prompt = indentedPrompt.exec(lines[i]);
685
885
  if (prompt && prompt[1].length < indent) {
686
886
  inputAt = i;
687
887
  indent = prompt[1].length;
@@ -715,33 +915,36 @@ export function scopeForTarget(target, recipients) {
715
915
  }
716
916
  /** The existing submit-only identity check, also used to inspect startup's own paste.
717
917
  * A false result is no matching staged evidence, not proof of model consumption. */
718
- export function hasExpectedStagedText(pane, expected) {
918
+ export function hasExpectedStagedText(pane, expected, runtime = null) {
719
919
  const norm = (s) => s.replace(/\s+/g, "");
920
+ // The composer glyph set is scoped to the runtime when known: a Claude draft's
921
+ // continuation line can start with a Codex glyph, and taking that line as the
922
+ // input marker would misread the staged text. Unknown runtimes keep the union.
923
+ const promptClass = composerPromptClassForRuntime(runtime);
720
924
  // Round-2 (r2 HIGH-1): the evidence must be the CURRENT ACTIVE INPUT and must identify
721
925
  // THIS piece — stale scrollback can carry an old placeholder while a LATER interactive
722
926
  // prompt owns the input, and an Enter there approves the prompt. So:
723
927
  // 1. Only the pane's LAST input-marker line counts (the current input; everything above
724
928
  // is history).
725
- // 2. A numbered-option line (`❯ 1. …`) is a PROMPT SELECTION, never staged input: refuse.
929
+ // 2. A numbered-option line (`❯ 1. …`, `› 1.Yes`) is a PROMPT SELECTION, never staged
930
+ // input: refuse.
726
931
  // 3. A pasted-text placeholder is identity-qualified: "[Pasted text #N +X lines]" counts
727
932
  // only when X matches the expected piece's own line count (±1 for a trailing newline).
728
933
  // More than one placeholder is COALESCED staging (several pieces, one Enter): refuse.
729
934
  // 4. Otherwise the line must carry the content's own head (24 normalized chars — a short
730
935
  // paste renders inline, possibly truncated).
731
936
  const paneLines = (pane ?? "").split("\n");
732
- let currentInputAt = -1;
733
- for (let i = paneLines.length - 1; i >= 0; i--) {
734
- if (paneLines[i].trimStart().startsWith("❯")) {
735
- currentInputAt = i;
736
- break;
737
- }
738
- }
937
+ const currentInputAt = findComposerInputLineIndex(paneLines, promptClass);
739
938
  let stagedEvidence = false;
740
939
  if (currentInputAt >= 0) {
940
+ // Unknown runtime: a composer block that mixes glyph kinds has no runtime to
941
+ // say which line is live, so fail closed exactly as the pre-union check did.
942
+ if (composerPromptIsAmbiguous(paneLines, currentInputAt, promptClass))
943
+ return false;
741
944
  const inputLine = paneLines[currentInputAt].trimStart();
742
- // The region is the last ❯-line through the input box's closing separator (a box-drawing
743
- // line) or pane end — wrapped input continues below the marker; everything ABOVE the
744
- // marker is history and everything below the separator is hint-bar chrome.
945
+ // The region is the last composer-marker line through the input box's closing separator
946
+ // (a box-drawing line) or pane end — wrapped input continues below the marker; everything
947
+ // ABOVE the marker is history and everything below the separator is hint-bar chrome.
745
948
  let regionEnd = paneLines.length;
746
949
  for (let i = currentInputAt + 1; i < paneLines.length; i++) {
747
950
  const t = paneLines[i].trim();
@@ -751,7 +954,7 @@ export function hasExpectedStagedText(pane, expected) {
751
954
  }
752
955
  }
753
956
  const region = paneLines.slice(currentInputAt, regionEnd).join("\n");
754
- if (!/^❯\s*\d+\./.test(inputLine)) {
957
+ if (!composerSelectionPrefixPattern(promptClass).test(inputLine)) {
755
958
  // Round-3 (r2 R2 HIGH-1, specimen-pinned): Claude renders ONE staged piece as MANY
756
959
  // placeholders whose displayed counts are SEGMENT sizes (sum ≤ source lines), followed
757
960
  // by the piece's own literal tail wrapped across pane lines — and the placeholder
@@ -777,7 +980,7 @@ export function hasExpectedStagedText(pane, expected) {
777
980
  // or one that is not the piece's own suffix, FAIL CLOSED — the TUI did not expose
778
981
  // enough content to identify the staged state, and a bare Enter is never guessed.
779
982
  const chrome = /paste again to expand|ctrl\+g to edit( in Vim)?/gi;
780
- const residual = norm(regionFlat.replace(placeholderRe, "").replace(chrome, "")).replace(/^❯/, "");
983
+ const residual = stripComposerPromptGlyph(norm(regionFlat.replace(placeholderRe, "").replace(chrome, "")), promptClass);
781
984
  const pieceNorm = norm(expected);
782
985
  const sum = counts.reduce((a, b) => a + b, 0);
783
986
  // Round-5 (r2 R4 HIGH-1): the suffix anchor is JOINED to the opaque prefix. The
@@ -814,23 +1017,490 @@ export function hasExpectedStagedText(pane, expected) {
814
1017
  function promptAnswerStaged(pane, answer, runtime) {
815
1018
  if (!pane || !answer.trim() || /[\r\n\x1b]/.test(answer))
816
1019
  return false;
1020
+ // Fail closed for an unknown runtime: with no scoped glyph set there is no way to
1021
+ // say which prompt line is the live input (the pre-shared-matcher check did the same).
1022
+ if (runtime !== "codex" && runtime !== "claude-code")
1023
+ return false;
1024
+ const promptClass = composerPromptClassForRuntime(runtime);
817
1025
  const lines = pane.split("\n");
818
- for (let i = lines.length - 1; i >= 0; i--) {
819
- const input = lines[i].trimStart();
820
- if (!/^[❯›]/.test(input))
821
- continue;
822
- if (/^[❯›]\s*\d+[.)]/.test(input))
823
- return false;
824
- if (runtime === "codex" ? !input.startsWith("›") : runtime !== "claude-code" || !input.startsWith("❯"))
825
- return false;
826
- const end = lines.findIndex((line, index) => index > i && (runtime === "codex"
827
- ? line.trim() === "" : /^[─═-]{10,}$/.test(line.trim())));
828
- return end > i && lines.slice(i, end).join("\n").trimStart().slice(1).trim() === answer.trim();
1026
+ // Only the LAST prompt line of this runtime's glyph set can be the current input;
1027
+ // everything above it is transcript history, and another harness's glyph is not ours.
1028
+ const inputIndex = findComposerInputLineIndex(lines, promptClass);
1029
+ if (inputIndex < 0)
1030
+ return false;
1031
+ const input = lines[inputIndex].trimStart();
1032
+ // A numbered choice (`.` or `)`), e.g. `» 1)` or `❯ 1.`, is a menu selection, never the answer.
1033
+ if (composerNumberedChoicePattern(promptClass).test(input))
1034
+ return false;
1035
+ // Where the input ends: Codex closes the composer on a blank line, Claude Code on a rule line.
1036
+ const end = lines.findIndex((line, index) => index > inputIndex && (runtime === "codex"
1037
+ ? line.trim() === "" : /^[─═-]{10,}$/.test(line.trim())));
1038
+ return end > inputIndex && lines.slice(inputIndex, end).join("\n").trimStart().slice(1).trim() === answer.trim();
1039
+ }
1040
+ // Codex 0.160 input box: the last prompt line plus indented continuation rows, one blank row, then a footer
1041
+ // of at most two rows: the `<model> · <~ or /path>` status line, optionally followed by one known hint row.
1042
+ const CODEX_STATUS_LINE = /^ {2}[^\s·][^·]* · [~/]/;
1043
+ const CODEX_HINT_ROW = /(?:^|\s)\? for shortcuts(?:\s|$)|⚠ \d+ warnings? · f2 to view\s*$/;
1044
+ // Codex shows a long paste as this label until it is submitted. Matched on the normalized body.
1045
+ const CODEX_COLLAPSED_PASTE = /^\[PastedContent\d+chars\]$/;
1046
+ /** The current Codex input box: its normalized text ("" when empty), its rows as drawn (the prompt glyph removed)
1047
+ * and where its prompt row is, or null when the layout is not the recognized one (another footer, a selector, no
1048
+ * blank separator, an older TUI). */
1049
+ function codexInputBox(pane) {
1050
+ const lines = pane.split("\n");
1051
+ let last = lines.length - 1;
1052
+ while (last >= 0 && !lines[last].trim())
1053
+ last--;
1054
+ let footerStart = last;
1055
+ while (footerStart > 0 && lines[footerStart - 1].trim())
1056
+ footerStart--;
1057
+ const footer = lines.slice(footerStart, last + 1);
1058
+ if (last < 0 || footer.length > 2 || !CODEX_STATUS_LINE.test(footer[0])
1059
+ || !footer.slice(1).every((row) => CODEX_HINT_ROW.test(row)))
1060
+ return null;
1061
+ // promptAnswerStaged also ends a Codex input at a blank row.
1062
+ const separator = footerStart - 1;
1063
+ if (separator < 1 || lines[separator].trim())
1064
+ return null;
1065
+ const promptClass = composerPromptClassForRuntime("codex");
1066
+ const inputAt = findComposerInputLineIndex(lines.slice(0, separator), promptClass);
1067
+ if (inputAt < 0 || composerNumberedChoicePattern(promptClass).test(lines[inputAt].trimStart()))
1068
+ return null;
1069
+ const rows = lines.slice(inputAt, separator);
1070
+ // Only indented continuation rows belong to the input; anything else is not an input box.
1071
+ if (rows.slice(1).some((row) => row.trim() && !/^\s/.test(row)))
1072
+ return null;
1073
+ if (CODEX_EMPTY_COMPOSER_PATTERN.test(rows[0].trimStart()) && rows.slice(1).every((row) => !row.trim())) {
1074
+ return { text: "", rows: [], lines, inputAt };
829
1075
  }
830
- return false;
1076
+ return { text: rows.join("\n").trimStart().slice(1).replace(/\s+/g, ""), rows: stripBoxGlyph(rows), lines, inputAt };
1077
+ }
1078
+ /** A box's rows with the prompt glyph taken off the first row (`❯`, `›`), as composerRegion does for its body. */
1079
+ function stripBoxGlyph(rows) {
1080
+ return rows.map((row, i) => (i === 0 ? row.trimStart().slice(1) : row));
1081
+ }
1082
+ // Bounds of a string's terminal width. Upper: a non-ASCII character may take two cells. Lower: a printable ASCII
1083
+ // character takes exactly one cell, and any other character is counted as none (a combining mark, a variation
1084
+ // selector or a joiner takes none, and an emoji sequence of several code points takes two cells in all).
1085
+ const widthAtMost = (text) => [...text].reduce((cells, ch) => cells + (ch.charCodeAt(0) < 0x80 ? 1 : 2), 0);
1086
+ const widthAtLeast = (text) => [...text].filter((ch) => ch >= " " && ch <= "~").length;
1087
+ // Pairs of characters that no wrapper separates across rows inside a word that fits in a row, under the conditions
1088
+ // rowBreakIsSpace adds. Claude Code 2.1.296 wraps its input with wrap-ansi, which breaks only at spaces. Codex 0.160.0
1089
+ // wraps it with textwrap's FirstFit, its UAX #14 word separator and its hyphen splitter, then re-passes the rows
1090
+ // (tui/src/bottom_pane/textarea/wrapping.rs at rust-v0.160.0), which also break with no space drawn after a hyphen
1091
+ // or a slash, between ideographs and at other UAX #14 break opportunities. UAX #14 has none between two ASCII
1092
+ // letters or digits (LB23, LB25, LB28), nor between a printable ASCII character and a following hyphen, slash,
1093
+ // closing bracket or `.,:;!?` (LB13, LB15d, LB21).
1094
+ const ASCII_LETTER_OR_DIGIT = /^[A-Za-z0-9]$/;
1095
+ const ASCII_VISIBLE = /^[!-~]$/;
1096
+ const ASCII_NO_BREAK_BEFORE = /^[-/.,:;!?)\]}]$/;
1097
+ // A word of ASCII letters, digits, `'` and `.,:;`, which has no UAX #14 break opportunity inside it (LB13, LB19, LB23,
1098
+ // LB25, LB28, LB29): Codex's re-pass keeps such a word whole when it starts on a row that already holds text.
1099
+ const ASCII_UNBROKEN_WORD = /^[A-Za-z0-9'.,:;]+$/;
1100
+ /** Whether the break between two drawn rows of a box must be whitespace of the box's text, given a lower bound of
1101
+ * the box's width. Rows are measured as drawn, the box's own indentation included (an upper bound of their width);
1102
+ * words are split at ASCII spaces only, which neither TUI breaks inside a word. `ownIndent` is how many cells of
1103
+ * `row` the box draws itself (1 after the prompt glyph, 2 on a continuation row). Either:
1104
+ * - the next row's first word would have fit on this row after a space: both wrappers fill a row first
1105
+ * (wrap-ansi and FirstFit are greedy), so where the rows follow the wrapper's own lines the box breaks its rows
1106
+ * there only at a line break of its text. Residual (review-safety-3 F1, from a model of the rust-v0.160.0 source,
1107
+ * not a live capture): on Codex, once a row holding only indentation has absorbed and split a word (below), the
1108
+ * re-pass can push a later row early at a hyphen, since its keep-whole check measures a hyphenated compound as one
1109
+ * word, so a short row can be followed by a fragment that would have fit with no whitespace between them, and this
1110
+ * rule counts that break as a space. Every such box in that model also holds the earlier absorbed split, which
1111
+ * only this rule reads and which it rejects (the row is full), so the box reads `ambiguous`, never `exact`. That
1112
+ * relies on the row-start check below, which the tests pin; or
1113
+ * - no wrapper breaks between the two characters (ASCII_NO_BREAK_BEFORE above), the two words they end and begin,
1114
+ * joined, fit in a row, the row's last word has no break opportunity inside it (ASCII_UNBROKEN_WORD), and the
1115
+ * row's text starts at its first cell with a printable ASCII character. Codex 0.160.0 keeps a word that fits in a
1116
+ * row whole only where the word starts on a row that already holds text, after whitespace or at one of textwrap's
1117
+ * breaks: a row holding only indentation absorbs the next word and splits it where the row fills ("Partial
1118
+ * whitespace rows absorb the next word"), and a word after a hyphen or a slash in a row that took the next
1119
+ * textwrap line is split the same way. So on Codex a split between two letters is no space at all on a row that
1120
+ * starts with indentation, or after a break opportunity inside the row's last word; only the first rule above
1121
+ * reads such a break. */
1122
+ function rowBreakIsSpace(row, next, width, ownIndent) {
1123
+ const firstWord = next.trim().split(" ")[0];
1124
+ if (widthAtMost(row.trimEnd()) + 1 + widthAtMost(firstWord) <= width)
1125
+ return true;
1126
+ if (row.slice(0, ownIndent).trim() || !ASCII_VISIBLE.test(row.slice(ownIndent, ownIndent + 1)))
1127
+ return false;
1128
+ const lastWord = row.trim().split(" ").at(-1);
1129
+ if (!ASCII_UNBROKEN_WORD.test(lastWord))
1130
+ return false;
1131
+ const before = lastWord.at(-1);
1132
+ const after = next.trimStart()[0] ?? "";
1133
+ const noBreak = (ASCII_LETTER_OR_DIGIT.test(before) && ASCII_LETTER_OR_DIGIT.test(after))
1134
+ || (ASCII_VISIBLE.test(before) && ASCII_NO_BREAK_BEFORE.test(after));
1135
+ return noBreak && widthAtMost(lastWord + firstWord) <= width;
831
1136
  }
1137
+ /** Whether a Claude Code or Codex box, given as its rows, holds exactly `text` with its word boundaries kept.
1138
+ * Whitespace inside a row is compared as one space (runs and Unicode spaces count the same, so `now here` is not
1139
+ * `nowhere`), and letters are compared as they are (case included). A row break counts as the text's space only
1140
+ * where it must be whitespace of the box's text (rowBreakIsSpace), measured against the box's widest row in
1141
+ * printable ASCII cells (widthAtLeast), a lower bound of the box's width whatever else the rows hold. Any other
1142
+ * break may fall inside a word of the box's text, or be how the box draws different spacing, so the reading is
1143
+ * `ambiguous`, never `exact`, and so is a break that falls inside a word of the sent text. */
1144
+ function boxHoldsText(rows, text) {
1145
+ // The box draws its first row one cell after the prompt glyph, and its continuation rows two cells in.
1146
+ const drawn = rows.flatMap((row, index) => (row.trim() ? [{ row, ownIndent: index === 0 ? 1 : 2 }] : []));
1147
+ const shown = drawn.map(({ row }) => row.trim().replace(/\s+/g, " "));
1148
+ const sent = text.trim().replace(/\s+/g, " ");
1149
+ // Measured on the rows as drawn: whitespace compares as a space, but U+FEFF, which `\s` matches, takes no cell.
1150
+ const width = Math.max(0, ...drawn.map(({ row }) => widthAtLeast(row.trim())));
1151
+ let at = 0;
1152
+ let ambiguous = false;
1153
+ for (let i = 0; i < shown.length; i++) {
1154
+ const row = shown[i];
1155
+ if (!sent.startsWith(row, at))
1156
+ return "other";
1157
+ at += row.length;
1158
+ if (i === shown.length - 1)
1159
+ break;
1160
+ if (sent[at] === " ") {
1161
+ at += 1;
1162
+ if (!rowBreakIsSpace(drawn[i].row, drawn[i + 1].row, width, drawn[i].ownIndent))
1163
+ ambiguous = true;
1164
+ }
1165
+ else {
1166
+ ambiguous = true;
1167
+ }
1168
+ }
1169
+ if (at !== sent.length)
1170
+ return "other";
1171
+ return ambiguous ? "ambiguous" : "exact";
1172
+ }
1173
+ /** The row of Codex's running-turn status (`• Working (… esc to interrupt)`) above its input box, or -1. */
1174
+ function codexTurnStatusAt(box) {
1175
+ for (let i = box.inputAt - 1; i >= 0; i--)
1176
+ if (CODEX_TURN_STATUS_PATTERN.test(box.lines[i]))
1177
+ return i;
1178
+ return -1;
1179
+ }
1180
+ /** Codex 0.160.0's parent bottom-pane renderer replaces the normal composer for a modal; inline choices and
1181
+ * previews follow the running status. Only scope generic selector history at this adjacent normal-frame slot.
1182
+ * A draft, queued input, different visible version, missing footer or intervening content keeps the full scan.
1183
+ * Source: openai/codex a956835d, tui/src/bottom_pane/mod.rs:2164-2310. Permission detectors keep their own scan. */
1184
+ function codexCurrentSelectionLines(pane) {
1185
+ if (readInputBox(pane, "codex", "") !== "empty")
1186
+ return null;
1187
+ const box = codexInputBox(pane);
1188
+ let status = box.inputAt - 1;
1189
+ while (status >= 0 && !box.lines[status].trim())
1190
+ status--;
1191
+ if (status < 0 || !CODEX_TURN_STATUS_PATTERN.test(box.lines[status]))
1192
+ return null;
1193
+ return trimPaneLines(box.lines.slice(status).join("\n"));
1194
+ }
1195
+ /** Whether a Claude Code frame shows a running turn: `esc to interrupt` in the footer under its input box, or a
1196
+ * live status row above the box (timer-less rows included): in the two rows above the box's opening rule, where
1197
+ * Claude Code 2.1.296 draws its status row over a one-row margin whatever the box's height, or as
1198
+ * `findClaudeComposer` reads it above a one-row box. Claude Code queues a message submitted during a turn, and it
1199
+ * labels the queue only while a per-user hint counter is low, so an unlabelled queued message leaves the same
1200
+ * empty box as a submit. With a statusLine configured (OpenRig installs one on every seat) and while the box holds
1201
+ * text, the footer never draws `esc to interrupt`, so the status row is the running signal, but only while it is
1202
+ * drawn: Claude Code hides it while streamed text is shown (SEAT_IDLE_SHAPES.claude.statusSlot). So this reading
1203
+ * can only say a turn ran; a turn it misses is ruled out, if at all, by the seat reading before the paste. */
1204
+ function claudeTurnRunning(pane) {
1205
+ const { markerLine, closingRuleLine } = composerRegion(pane, "claude-code");
1206
+ if (closingRuleLine === null || markerLine === null)
1207
+ return false;
1208
+ const lines = pane.split("\n");
1209
+ if (lines.slice(closingRuleLine).some((row) => /esc to interrupt/.test(row)))
1210
+ return true;
1211
+ // markerLine is 1-based: the box's first row is lines[markerLine - 1] and its opening rule the row above it.
1212
+ const statusSlot = lines.slice(Math.max(0, markerLine - 4), Math.max(0, markerLine - 2)).map((row) => row.trim());
1213
+ if (statusSlot.some((row) => CLAUDE_LIVE_STATUS_PATTERN.test(row) || CLAUDE_TIMERLESS_STATUS_PATTERN.test(row)))
1214
+ return true;
1215
+ return findClaudeComposer(pane, { timerlessStatusIsLive: true })?.liveStatus != null;
1216
+ }
1217
+ // Claude's faint `Try "…"` suggestion in an empty box. The capture is unstyled, so it is read as other text and
1218
+ // never as the sent text, even when the text is the suggestion itself.
1219
+ const CLAUDE_TRY_SUGGESTION = /^Try"[^"]*"$/;
1220
+ // Codex 0.160 lists messages waiting for a running turn above its input box under one of these headers, with
1221
+ // `↳ <text>` rows (tui/src/bottom_pane/pending_input_preview.rs). Like Claude's queued label, such a row is generic:
1222
+ // it says a message waits, not which one.
1223
+ const CODEX_PENDING_INPUT_HEADER = /^• (?:Messages to be submitted (?:after next tool call|at end of turn)|Queued follow-up inputs)\b/;
1224
+ const CODEX_PENDING_INPUT_ROW = new RegExp(`${CODEX_PENDING_INPUT_HEADER.source}|^↳ `);
1225
+ const BRAILLE_SPINNER = /[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/;
1226
+ // Rows that rule out an idle seat wherever they are in the capture (compared trimmed). A transcript row that
1227
+ // happens to match also reads not-idle, which only leaves the send unconfirmed.
1228
+ const SEAT_NOT_IDLE_ROWS = {
1229
+ "claude-code": [CLAUDE_LIVE_STATUS_PATTERN, CLAUDE_TIMERLESS_STATUS_PATTERN, /esc to interrupt/, /paste again to expand/,
1230
+ /Press up to edit queued messages/, BRAILLE_SPINNER],
1231
+ codex: [CODEX_TURN_STATUS_PATTERN, /esc to interrupt/, CODEX_PENDING_INPUT_ROW, BRAILLE_SPINNER],
1232
+ };
1233
+ // Codex 0.160's completion row (tui/src/history_cell/separators.rs, FinalMessageSeparator::label at rust-v0.160.0):
1234
+ // `Worked for <elapsed>`, then ` • ` and the completion time (an optional date for another day, then the host's
1235
+ // 12- or 24-hour clock, tui/src/clock_format.rs), then optional runtime metrics, each after ` • `, in source order.
1236
+ // A live completion always has both the elapsed time and the time (chatwidget/completion.rs), so both are required.
1237
+ // `<1s`, `{s}s`, `{m}m {s}s` or `{h}h {m}m {s}s`, with minutes and seconds below 60 and no leading zeros.
1238
+ const CODEX_SIXTY = "(?:[0-9]|[1-5][0-9])";
1239
+ const CODEX_NONZERO_SIXTY = "(?:[1-9]|[1-5][0-9])";
1240
+ const CODEX_ELAPSED = `(?:<1s|${CODEX_NONZERO_SIXTY}s|${CODEX_NONZERO_SIXTY}m ${CODEX_SIXTY}s|[1-9][0-9]*h ${CODEX_SIXTY}m ${CODEX_SIXTY}s)`;
1241
+ // `%b %-d at ` (this year) or `%b %-d, %Y at ` (another year); nothing for today.
1242
+ const CODEX_DATE = "(?:(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (?:[1-9]|[12][0-9]|3[01])(?:, [0-9]{4})? at )?";
1243
+ // `%-I:%M %p` (TwelveHour) or `%H:%M` (TwentyFourHour).
1244
+ const CODEX_CLOCK = "(?:(?:[1-9]|1[0-2]):[0-5][0-9] [AP]M|(?:[01][0-9]|2[0-3]):[0-5][0-9])";
1245
+ // runtime_metrics_label: format_duration_ms gives `<n>.<d>s` from one second, `<n>ms` below it.
1246
+ const CODEX_MS = "(?:[0-9]+\\.[0-9]s|[0-9]+ms)";
1247
+ const CODEX_TTFT_TBT = `(?:${CODEX_MS} \\(iapi\\)(?: ${CODEX_MS} \\(service\\))?|${CODEX_MS} \\(service\\))`;
1248
+ // A metric is shown only for a count above zero; pluralize() gives the singular for exactly one.
1249
+ const CODEX_MANY = "(?:[2-9]|[1-9][0-9]+)";
1250
+ const CODEX_COUNT = "[1-9][0-9]*";
1251
+ const CODEX_METRICS = [
1252
+ `Local tools: (?:1 call|${CODEX_MANY} calls) \\(${CODEX_MS}\\)`,
1253
+ `Inference: (?:1 call|${CODEX_MANY} calls) \\(${CODEX_MS}\\)`,
1254
+ `WebSocket: ${CODEX_COUNT} events send \\(${CODEX_MS}\\)`,
1255
+ `(?:Stream: 1 event|Streams: ${CODEX_MANY} events) \\(${CODEX_MS}\\)`,
1256
+ `${CODEX_COUNT} events received \\(${CODEX_MS}\\)`,
1257
+ `Responses API overhead: ${CODEX_MS}`,
1258
+ `Responses API inference: ${CODEX_MS}`,
1259
+ `TTFT: ${CODEX_TTFT_TBT}`,
1260
+ `TBT: ${CODEX_TTFT_TBT}`,
1261
+ ].map((metric) => `(?: • ${metric})?`).join("");
1262
+ const CODEX_COMPLETION_ROW = new RegExp(`^ {2}Worked for ${CODEX_ELAPSED} • ${CODEX_DATE}${CODEX_CLOCK}${CODEX_METRICS}$`);
1263
+ const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1264
+ // Claude Code 2.1.296's completion row, which ends a finished turn in the transcript (the bundle's turn-duration
1265
+ // row): the `✻` glyph in a two-cell column at column 0, then `<verb> for <duration> · done <time>`. The verb is one
1266
+ // of the bundle's past-tense list, picked from the turn's id. The duration is the bundle's duration format:
1267
+ // `<s>s` below a minute (`0.0s` below 1 ms), `<m>m <s>s`, `<h>h <m>m <s>s`, `<d>d <h>h <m>m`, without leading
1268
+ // zeros. The time is the clock of a turn that finished today in the en-US forms of the bundle's time-format
1269
+ // presets: `auto` (en-US) and `12-hour` (`5:24 PM`), `24-hour` (`17:24`, `09:05`), `24-hour-utc` (`17:24Z`); real
1270
+ // frames of OpenRig-launched seats show the 12-hour form. Anything after the time
1271
+ // (a still-running note, hidden-message or budget notes), a row without `· done`, another locale, a custom
1272
+ // pattern or the weekday and date of an earlier day is not on this list.
1273
+ const CLAUDE_COMPLETION_VERBS = ["Baked", "Brewed", "Churned", "Cogitated", "Cooked", "Crunched", "Saut\u00e9ed", "Worked"];
1274
+ const CLAUDE_SIXTY = "(?:[0-9]|[1-5][0-9])";
1275
+ const CLAUDE_NONZERO_SIXTY = "(?:[1-9]|[1-5][0-9])";
1276
+ const CLAUDE_HOURS = "(?:[0-9]|1[0-9]|2[0-3])";
1277
+ const CLAUDE_NONZERO_HOURS = "(?:[1-9]|1[0-9]|2[0-3])";
1278
+ const CLAUDE_DURATION = `(?:0\\.0s|${CLAUDE_SIXTY}s|${CLAUDE_NONZERO_SIXTY}m ${CLAUDE_SIXTY}s`
1279
+ + `|${CLAUDE_NONZERO_HOURS}h ${CLAUDE_SIXTY}m ${CLAUDE_SIXTY}s|[1-9][0-9]*d ${CLAUDE_HOURS}h ${CLAUDE_SIXTY}m)`;
1280
+ const CLAUDE_CLOCK = "(?:(?:[1-9]|1[0-2]):[0-5][0-9] [AP]M|(?:[01][0-9]|2[0-3]):[0-5][0-9]Z?)";
1281
+ const CLAUDE_COMPLETION_ROW = new RegExp(`^✻ (?:${CLAUDE_COMPLETION_VERBS.join("|")}) for ${CLAUDE_DURATION} · done ${CLAUDE_CLOCK}$`);
1282
+ /** The idle allow-list. Each entry comes from real Claude Code 2.1.296 and Codex 0.160.0 frames
1283
+ * (test/fixtures/input-box, including idle seats OpenRig launched: openrig-seat-*), or, where no frame was captured,
1284
+ * from the Claude Code 2.1.296 bundle, the Codex 0.160.0 source or OpenRig's own launch code, as each entry says; a
1285
+ * frame that matches none of them is not proven idle. */
1286
+ const SEAT_IDLE_SHAPES = {
1287
+ claude: {
1288
+ // The box's opening rule. Plain (the captured frames: a seat started without a name), or carrying this seat's
1289
+ // own session name: OpenRig launches every Claude seat with `--name <session>` (claude-code-adapter.ts), which
1290
+ // Claude Code draws on the box's top border as `─…─ <name> ─`, as wide as the closing rule (2.1.296 bundle:
1291
+ // the prompt-border banner draws the rule, the name padded by one space each side, then one `─`). Real frames
1292
+ // of OpenRig seats from earlier Claude Code versions end it with `──` (session-transport.test.ts,
1293
+ // fixtures/walk-staged-specimen), so one or two are accepted; real 2.1.296 OpenRig seats (openrig-seat-*) draw
1294
+ // one. Any other name, or a truncated one, is unknown.
1295
+ openingRules: {
1296
+ plain: /^─{10,}$/,
1297
+ named: (sessionName) => new RegExp(`^─+ ${escapeRegExp(sessionName)} ─{1,2}$`),
1298
+ },
1299
+ // The footer under an empty box when no turn runs, one per permission posture claudePostureFlag can launch
1300
+ // (yolo-mode.ts: --dangerously-skip-permissions, or --permission-mode with any mode Claude Code 2.1.296 offers).
1301
+ // Bypass and accept edits are real (openrig-seat-*; bypass also after-enter-15s.J, idle-fresh-suggestion). The
1302
+ // others are drawn by the 2.1.296 bundle's footer (the mode's symbol and indicator plus ` on`,
1303
+ // ` (shift+tab to cycle)` for every mode but default, then ` · ← for agents` while the box is empty); accept edits
1304
+ // also matches a real 2.1.220 capture (claude-warning-footer-readiness.test.ts). OpenRig's statusLine makes the footer drop its hints
1305
+ // (`? for shortcuts`, `esc to interrupt`), so these rows are the same whether or not a turn runs: the status
1306
+ // slot above the box (`statusSlot` below) is what tells a running turn apart.
1307
+ footers: {
1308
+ bypassPermissions: "⏵⏵ bypass permissions on (shift+tab to cycle) · ← for agents",
1309
+ acceptEdits: "⏵⏵ accept edits on (shift+tab to cycle) · ← for agents",
1310
+ auto: "⏵⏵ auto mode on (shift+tab to cycle) · ← for agents",
1311
+ dontAsk: "⏵⏵ don't ask on (shift+tab to cycle) · ← for agents",
1312
+ plan: "⏸ plan mode on (shift+tab to cycle) · ← for agents",
1313
+ default: "⏸ manual mode on · ← for agents",
1314
+ },
1315
+ // The rows OpenRig's statusLine collector (provisionContextCollector, assets/claude-statusline-context.cjs) draws
1316
+ // between the closing rule and the footer: none. It prints nothing to stdout, and Claude Code 2.1.296 draws no
1317
+ // row for an empty statusLine in the classic renderer OpenRig launches with (CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN=1).
1318
+ // A statusLine that prints anything (a person's own command) is not on this list.
1319
+ statusLineRows: [],
1320
+ // The one row between the box's two rules: empty (after-enter-15s.J), or the Try suggestion (idle-fresh-suggestion).
1321
+ boxes: [/^❯$/, /^❯\s+Try "[^"]*"$/],
1322
+ // The row just above the opening rule: blank (after-enter-15s.J), the effort row (idle-fresh-suggestion), or a
1323
+ // tmux notice (cleared-without-enter.J, staged-short).
1324
+ notices: ["", "◐ medium · /effort",
1325
+ "tmux detected · scroll with PgUp/PgDn · or add 'set -g mouse on' to ~/.tmux.conf for wheel scroll",
1326
+ "tmux focus-events off · add 'set -g focus-events on' to ~/.tmux.conf and reattach for focus tracking"],
1327
+ // The status slot, the row above that, read together with the rows above it when it is blank: the first
1328
+ // non-blank row at or above the slot must be one of these. With OpenRig's statusLine the footer shows no running
1329
+ // signal, so this slot is what tells a running turn apart, and the 2.1.296 bundle says what a running turn leaves
1330
+ // there. Its REPL draws the live status row (the spinner) only while
1331
+ // `isLoading && (!hasDisplayedStreamingText || tailBlank || …)`: once streamed text with a complete line is on
1332
+ // screen it hides the spinner, and draws nothing in its place (the slot component renders only a retry banner,
1333
+ // or null), so the last streamed row moves down into the slot. That row is never the completion row: Claude Code
1334
+ // draws a message behind a two-cell `●` column (continuation rows two spaces in), the user's echo after `❯ `,
1335
+ // tool output indented. But the bundle does not show that the last streamed row is never blank (the streamed
1336
+ // markdown is laid out in a column with a one-row gap, and its unfinished last line is hidden), so a blank slot
1337
+ // under transcript text is not proof of an idle seat and reads unknown. A blank slot counts only where nothing
1338
+ // of a newer turn can be under the row it sits below:
1339
+ // - finishedTurn: the completion row at column 0 (CLAUDE_COMPLETION_ROW). In the classic renderer OpenRig
1340
+ // launches Claude with, the box follows the transcript, so an idle seat that has finished a turn shows that
1341
+ // turn's completion row in the slot itself (openrig-seat-*: real OpenRig-launched seats); in the fullscreen
1342
+ // renderer the box is pinned to the bottom and blank rows sit between them (after-enter-15s.J). A newer turn
1343
+ // draws its echo, its status row or its `●` rows under the completion row.
1344
+ // - freshSession: the last row of the welcome banner of Claude Code v2.1.296, before any turn (idle-fresh-
1345
+ // suggestion, cleared-without-enter.J).
1346
+ // Any other row reads unknown.
1347
+ statusSlot: {
1348
+ finishedTurn: (lines, at) => CLAUDE_COMPLETION_ROW.test(lines[at] ?? ""),
1349
+ freshSession: (lines, at) => at >= 2 && /^ ▝▝ {3}▝▝ {3}[~/]/.test(lines[at])
1350
+ && /^▝▜██████▀ {2}\S/.test(lines[at - 1]) && /^ ▐▛███▛█ {3}Claude Code v2\.1\.296$/.test(lines[at - 2]),
1351
+ },
1352
+ },
1353
+ codex: {
1354
+ // The hint row under the status line of an empty box (product-path-idle.J, idle).
1355
+ hints: [/^\? for shortcuts$/, /^\? for shortcuts\s+⚠ \d+ warnings? · f2 to view$/],
1356
+ // The last non-blank row above the empty box: the completion row Codex adds when a turn completes
1357
+ // (after-enter-15s.J; its grammar is CODEX_COMPLETION_ROW above), or the greeting under the session header
1358
+ // before any turn (idle, product-path-idle.J, cleared-without-enter.J). A running or streaming turn draws rows
1359
+ // below either.
1360
+ above: {
1361
+ finishedTurn: (lines, at) => CODEX_COMPLETION_ROW.test(lines[at] ?? ""),
1362
+ freshSession: (lines, at) => {
1363
+ if (!/^ {2}\S/.test(lines[at] ?? ""))
1364
+ return false;
1365
+ let i = at - 1;
1366
+ while (i >= 0 && !lines[i].trim())
1367
+ i--;
1368
+ return i >= 2 && i < at - 1 && /^ {2}permissions: \S/.test(lines[i]) && /^ {5}[~/]/.test(lines[i - 1])
1369
+ && /^ {2}>_ OpenAI Codex \(v0\.160\.0\)$/.test(lines[i - 2]);
1370
+ },
1371
+ },
1372
+ },
1373
+ };
1374
+ function claudeSeatIdle(lines, sessionName) {
1375
+ const idle = SEAT_IDLE_SHAPES.claude;
1376
+ let footer = lines.length - 1;
1377
+ while (footer >= 0 && !lines[footer].trim())
1378
+ footer--;
1379
+ const closing = footer - 1 - idle.statusLineRows.length;
1380
+ if (closing < 4)
1381
+ return false;
1382
+ const width = (row) => [...row].length;
1383
+ const plainRule = (row) => idle.openingRules.plain.test(row);
1384
+ const openingRule = (row) => plainRule(row)
1385
+ || (!!sessionName && idle.openingRules.named(sessionName).test(row) && width(row) === width(lines[closing]));
1386
+ return Object.values(idle.footers).includes(lines[footer].trim())
1387
+ && idle.statusLineRows.every((row, i) => lines[closing + 1 + i].trim() === row) && plainRule(lines[closing])
1388
+ && idle.boxes.some((box) => box.test(lines[closing - 1])) && openingRule(lines[closing - 2])
1389
+ && idle.notices.includes(lines[closing - 3].trim()) && claudeStatusSlotIdle(lines, closing - 4);
1390
+ }
1391
+ /** The status slot (see SEAT_IDLE_SHAPES.claude.statusSlot): its own row, or, when it is blank, the first non-blank
1392
+ * row above it. */
1393
+ function claudeStatusSlotIdle(lines, slot) {
1394
+ let at = slot;
1395
+ while (at >= 0 && !lines[at].trim())
1396
+ at--;
1397
+ return at >= 0 && Object.values(SEAT_IDLE_SHAPES.claude.statusSlot).some((shape) => shape(lines, at));
1398
+ }
1399
+ function codexSeatIdle(pane) {
1400
+ const box = codexInputBox(pane);
1401
+ if (box === null || box.text)
1402
+ return false;
1403
+ const lines = box.lines.map((row) => row.trimEnd());
1404
+ let hint = lines.length - 1;
1405
+ while (hint >= 0 && !lines[hint].trim())
1406
+ hint--;
1407
+ if (!SEAT_IDLE_SHAPES.codex.hints.some((row) => row.test(lines[hint].trim())))
1408
+ return false;
1409
+ let above = box.inputAt - 1;
1410
+ while (above >= 0 && !lines[above].trim())
1411
+ above--;
1412
+ return Object.values(SEAT_IDLE_SHAPES.codex.above).some((shape) => shape(lines, above));
1413
+ }
1414
+ // The client versions the idle layouts and box readers were recorded from, and the rows in which each client names
1415
+ // the version it runs: Claude Code's welcome banner (`Claude Code v2.1.296`), Codex's session header
1416
+ // (`>_ OpenAI Codex (v0.160.0)`) and its update notice, whose first version is the running one
1417
+ // (`Update available! 0.160.0 -> 0.162.1`). These are only what the frames show; nothing here discovers a version. A frame where one of them names another version is not read as these
1418
+ // layouts. Most frames name no version (the banner and header scroll away), and then a matching layout is evidence
1419
+ // about the layout only, not proof of the client version: another version that draws the same rows reads the same.
1420
+ const RECORDED_CLIENT_VERSION = { "claude-code": "2.1.296", codex: "0.160.0" };
1421
+ const VISIBLE_CLIENT_VERSION = {
1422
+ "claude-code": [/Claude Code v(\d[\w.+-]*)/g],
1423
+ codex: [/OpenAI Codex \(v(\d[\w.+-]*)\)/g, /Update available! (\d[\w.+-]*) -> /g],
1424
+ };
1425
+ /** Whether a frame visibly names a client version other than the recorded one (see RECORDED_CLIENT_VERSION). */
1426
+ function namesAnotherClientVersion(pane, runtime) {
1427
+ return VISIBLE_CLIENT_VERSION[runtime].some((pattern) => [...pane.matchAll(pattern)].some((match) => match[1] !== RECORDED_CLIENT_VERSION[runtime]));
1428
+ }
1429
+ /** The seat before the paste (see SeatReading), read from the capture the transport already takes before it.
1430
+ * `sessionName` is the seat's own session name, which a Claude Code seat launched by OpenRig draws on its box.
1431
+ * A frame that names another client version is `unknown`. */
1432
+ export function readSeatBeforePaste(pane, runtime, sessionName) {
1433
+ if (!pane?.trim())
1434
+ return "unknown";
1435
+ const lines = pane.split("\n").map((row) => row.trimEnd());
1436
+ if (lines.some((row) => SEAT_NOT_IDLE_ROWS[runtime].some((pattern) => pattern.test(row.trim()))))
1437
+ return "not-idle";
1438
+ if (namesAnotherClientVersion(pane, runtime))
1439
+ return "unknown";
1440
+ return (runtime === "codex" ? codexSeatIdle(pane) : claudeSeatIdle(lines, sessionName)) ? "idle" : "unknown";
1441
+ }
1442
+ /** What a Claude Code or Codex input box shows, read from the input box only (never the transcript). Labels and
1443
+ * placeholders (Claude's queued label and Try suggestion, collapsed pastes, Codex's empty-box placeholder) are
1444
+ * checked before the sent text, so none of them can read as `exact`. The sent text is `exact` only with its word
1445
+ * boundaries kept, and `ambiguous` where the box's row breaks leave them uncertain (boxHoldsText). A frame that
1446
+ * names another client version reads `unrecognized` (RECORDED_CLIENT_VERSION).
1447
+ *
1448
+ * An empty box also reads `queued` where the frames say the message may wait behind a turn: Codex's
1449
+ * pending-input rows above the box, rows between Codex's running-turn status row and the box, or
1450
+ * `beforeEnter` (the frame read just before Enter) showing a running turn (Codex's status row, or
1451
+ * `claudeTurnRunning`). These readings decide the verdict: verified needs `empty` after Enter. For a seat that
1452
+ * was not idle before the paste they only say why the send is unconfirmed (readSeatBeforePaste already rules it
1453
+ * out), but for a seat that was idle they are the only check that rejects a message queued behind a turn that
1454
+ * started after the pre-paste capture and shows in the frame read before Enter.
1455
+ * Used only to report a send with transport.verify_input_box on. */
1456
+ export function readInputBox(pane, runtime, text, beforeEnter) {
1457
+ if (!pane?.trim())
1458
+ return "unavailable";
1459
+ if (namesAnotherClientVersion(pane, runtime))
1460
+ return "unrecognized";
1461
+ if (runtime === "codex") {
1462
+ const box = codexInputBox(pane);
1463
+ if (box === null)
1464
+ return "unrecognized";
1465
+ if (!box.text) {
1466
+ const before = beforeEnter ? codexInputBox(beforeEnter) : null;
1467
+ const status = codexTurnStatusAt(box);
1468
+ const rowsBehindTurn = status >= 0 && box.lines.slice(status + 1, box.inputAt).some((row) => row.trim());
1469
+ const pendingInput = box.lines.slice(0, box.inputAt).some((row) => CODEX_PENDING_INPUT_ROW.test(row.trim()));
1470
+ return pendingInput || rowsBehindTurn || (before !== null && codexTurnStatusAt(before) >= 0) ? "queued" : "empty";
1471
+ }
1472
+ if (CODEX_COLLAPSED_PASTE.test(box.text))
1473
+ return "hidden";
1474
+ return boxHoldsText(box.rows, text);
1475
+ }
1476
+ const { body, markerLine, closingRuleLine } = composerRegion(pane, runtime);
1477
+ if (body === null)
1478
+ return "unrecognized";
1479
+ if (!body)
1480
+ return beforeEnter && claudeTurnRunning(beforeEnter) ? "queued" : "empty";
1481
+ if (body === QUEUED_PLACEHOLDER)
1482
+ return "queued";
1483
+ if (COLLAPSED_PASTE.test(body))
1484
+ return "hidden";
1485
+ if (CLAUDE_TRY_SUGGESTION.test(body))
1486
+ return "other";
1487
+ // composerRegion's body has every space removed (the startup guard's matcher, unchanged); identity reads the rows.
1488
+ return boxHoldsText(stripBoxGlyph(pane.split("\n").slice(markerLine - 1, closingRuleLine - 1)), text);
1489
+ }
1490
+ const CALLER_CANCELLED = "caller_cancelled";
1491
+ /** The caller's cancellation signal, or none for sends no caller owns (queue wakes, committed outbox deliveries). */
1492
+ function callerSignal(opts) {
1493
+ return opts?.queueWake || opts?.committedOutboxIds ? undefined : opts?.signal;
1494
+ }
1495
+ function callerCancelled(sessionName) {
1496
+ return { ok: false, sessionName, sent: false, inputPhase: "not_reached", reason: CALLER_CANCELLED,
1497
+ error: `The request was cancelled before any input started. Nothing was typed into '${sessionName}', and nothing will be.` };
1498
+ }
1499
+ /** The evidence of a verified send whose path does not read the input box (an audited prompt answer). */
1500
+ const INPUT_BOX_UNCHECKED = { seat: "unchecked", before: "unchecked", after: "unchecked", sameProcess: false };
832
1501
  export class SessionTransport {
833
1502
  db;
1503
+ guardedDelivery;
834
1504
  rigRepo;
835
1505
  sessionRegistry;
836
1506
  tmuxAdapter;
@@ -844,6 +1514,9 @@ export class SessionTransport {
844
1514
  activityEndpointFile;
845
1515
  captureObserver;
846
1516
  listProcesses;
1517
+ runtimeAdapters;
1518
+ inFlightReconciliations = new Map();
1519
+ verifyInputBox;
847
1520
  constructor(deps) {
848
1521
  this.db = deps.db;
849
1522
  this.rigRepo = deps.rigRepo;
@@ -859,6 +1532,17 @@ export class SessionTransport {
859
1532
  this.activityEndpointFile = deps.activityEndpointFile ?? (() => null);
860
1533
  this.captureObserver = deps.captureObserver;
861
1534
  this.listProcesses = deps.listProcesses;
1535
+ this.runtimeAdapters = deps.runtimeAdapters ?? {};
1536
+ this.verifyInputBox = deps.verifyInputBox;
1537
+ const guard = this.tmuxAdapter.deliveryGuard;
1538
+ if (guard && this.db.prepare("PRAGMA table_info(outbox_entries)").all().some(c => c.name === "guard_delivery"))
1539
+ this.guardedDelivery = new DraftAwareDelivery(this.db, guard, (session, text, opts) => this.sendUnguarded(session, text, opts));
1540
+ }
1541
+ configureGuardedWakes(applicable) {
1542
+ this.guardedDelivery?.configureWakes(applicable);
1543
+ }
1544
+ attachRuntimeAdapters(adapters) {
1545
+ this.runtimeAdapters = { ...this.runtimeAdapters, ...adapters };
862
1546
  }
863
1547
  /** Freeze the registration and binding before asking tmux about this name. */
864
1548
  absenceProbeTarget(sessionName) {
@@ -1132,17 +1816,33 @@ export class SessionTransport {
1132
1816
  const pref = guard.preference(target.nodeId);
1133
1817
  return pref.desired || pref.effective ? target : null;
1134
1818
  }
1819
+ draftAwareTarget(sessionName) {
1820
+ const guard = this.tmuxAdapter.deliveryGuard;
1821
+ const target = guard?.maybeTarget(sessionName);
1822
+ if (!guard || !target)
1823
+ return null;
1824
+ const policy = guard.configuration(target.nodeId);
1825
+ return policy.desired.mode === "draft-aware" || policy.effective.mode === "draft-aware" ? target : null;
1826
+ }
1135
1827
  /** Deliver text and Enter to the resolved recipient under the send guards.
1136
1828
  * With verification requested, compare rendered message counts, excluding
1137
1829
  * agent input; a confirmed render does not prove agent consumption. */
1138
1830
  async send(sessionName, text, opts) {
1139
1831
  const guard = this.tmuxAdapter.deliveryGuard;
1140
- if (!guard)
1141
- return this.sendUnguarded(sessionName, text, opts);
1832
+ if (!guard) {
1833
+ // No guard, so no recorded delivery to read back: a caller already gone ends the send here.
1834
+ if (callerSignal(opts)?.aborted)
1835
+ return callerCancelled(sessionName);
1836
+ const result = await this.sendUnguarded(sessionName, text, opts);
1837
+ if (result.ok && result.sent !== false) {
1838
+ await this.reconcileStartupReadiness(sessionName).catch(() => { });
1839
+ }
1840
+ return result;
1841
+ }
1142
1842
  const outbox = new OutboxHandler(this.db);
1143
1843
  const ids = opts?.committedOutboxIds ?? [opts?.deliveryId ?? `guard-send-${randomUUID()}`];
1144
- const retainedResult = () => ({ ok: true, sessionName, outcome: "retained", sent: false, verified: false,
1145
- outboxIds: ids, reason: "typing_guard_enabled", warning: `Retained, not delivered. Inspect with rig seat held-messages ${sessionName}; disabling does not replay held messages.` });
1844
+ const retainedResult = (reason = "typing_guard_enabled") => ({ ok: true, sessionName, outcome: "retained", sent: false, verified: false,
1845
+ outboxIds: ids, reason, warning: `Retained, not delivered. Inspect with rig seat held-messages ${sessionName}; changing protection does not replay held messages.` });
1146
1846
  try {
1147
1847
  if (opts?.committedOutboxIds) {
1148
1848
  const target = guard.target(sessionName);
@@ -1155,6 +1855,9 @@ export class SessionTransport {
1155
1855
  }
1156
1856
  }
1157
1857
  }
1858
+ const existing = this.guardedDelivery?.existing(sessionName, text, opts ?? {}, ids);
1859
+ if (existing)
1860
+ return existing;
1158
1861
  // Idempotent readback also after disabling: an old retained ID never becomes a new send.
1159
1862
  // (P2: this readback is NOT a new retention and is outside the retained_no_write seam.)
1160
1863
  if (!opts?.committedOutboxIds && opts?.deliveryId) {
@@ -1167,6 +1870,10 @@ export class SessionTransport {
1167
1870
  return retainedResult();
1168
1871
  }
1169
1872
  }
1873
+ // A recorded delivery has answered with its own outcome above; with nothing recorded, a caller already gone
1874
+ // ends the send here.
1875
+ if (callerSignal(opts)?.aborted)
1876
+ return callerCancelled(sessionName);
1170
1877
  // #1084 — the idle wait must not occupy the seat's serial lease: a long
1171
1878
  // --wait-for-idle inside guard.operation holds up other sends and queue
1172
1879
  // creates to this seat for the whole wait. The lease-free wait, main's
@@ -1204,9 +1911,28 @@ export class SessionTransport {
1204
1911
  sendOpts = { ...opts, preWaitedIdle: pre.evidence, preWaitBound: pre.bound };
1205
1912
  }
1206
1913
  }
1207
- return await guard.operation(sessionName, () => this.sendUnguarded(sessionName, text, sendOpts), async (target) => {
1914
+ const result = await guard.operation(sessionName, async () => {
1915
+ if (this.guardedDelivery && !sendOpts?.submitOnly && guard.needsInputCheck(sessionName)) {
1916
+ // Up to its durable record a draft-aware send is still the caller's: one that went away while this send
1917
+ // waited for the lease ends here, unless a record for this delivery already exists. DraftAwareDelivery.send
1918
+ // commits that record synchronously before its first await, so no cancellation lands between this check
1919
+ // and the commit; after the commit a caller going away doesn't cancel it.
1920
+ if (callerSignal(sendOpts)?.aborted && !this.guardedDelivery.existing(sessionName, text, sendOpts ?? {}, ids)) {
1921
+ return callerCancelled(sessionName);
1922
+ }
1923
+ return this.guardedDelivery.send(sessionName, text, { ...sendOpts, signal: undefined }, ids);
1924
+ }
1925
+ const prior = this.guardedDelivery?.existing(sessionName, text, sendOpts ?? {}, ids);
1926
+ return prior ?? this.sendUnguarded(sessionName, text, sendOpts);
1927
+ }, async (target) => {
1928
+ const prior = this.guardedDelivery?.existing(sessionName, text, sendOpts ?? {}, ids);
1929
+ if (prior)
1930
+ return prior;
1208
1931
  if (opts?.submitOnly)
1209
1932
  return { ok: false, sessionName, sent: false, reason: "typing_guard_enabled", error: "Typing guard prevents submit-only; no Enter was sent." };
1933
+ // Nothing is retained yet: a caller that went away while this send waited for the lease ends it here.
1934
+ if (callerSignal(opts)?.aborted)
1935
+ return callerCancelled(sessionName);
1210
1936
  this.db.transaction(() => {
1211
1937
  for (const id of ids) {
1212
1938
  const prior = opts?.committedOutboxIds ? outbox.getById(id) : null;
@@ -1214,6 +1940,7 @@ export class SessionTransport {
1214
1940
  throw new Error("Committed wake target/ID mismatch");
1215
1941
  outbox.retain(prior ? { ...prior, outboxId: id, tags: prior.tags ?? undefined, auditPointer: prior.auditPointer ?? undefined } : {
1216
1942
  outboxId: id, senderSession: opts?.actorSession ?? "unknown", destinationSession: sessionName, body: text, auditPointer: opts?.auditPointer,
1943
+ identityProvenance: opts?.identityProvenance,
1217
1944
  }, target, !!opts?.committedOutboxIds);
1218
1945
  }
1219
1946
  })();
@@ -1234,8 +1961,24 @@ export class SessionTransport {
1234
1961
  }
1235
1962
  return retainedResult();
1236
1963
  });
1964
+ if (result.ok && result.sent !== false) {
1965
+ await this.reconcileStartupReadiness(sessionName).catch(() => { });
1966
+ }
1967
+ return result;
1237
1968
  }
1238
1969
  catch (error) {
1970
+ // A refusal thrown after the text was written (for example a guard check before Enter) is not "nothing sent".
1971
+ if (error.inputWritten === true) {
1972
+ // Only a throw before the Enter key reached the adapter positively means Enter was never sent.
1973
+ const enter = error.enterAttempted === true ? "the submission (Enter) outcome is unconfirmed" : "Enter was not sent";
1974
+ return { ok: false, sessionName, sent: true, outcome: "failed", inputPhase: "written", reason: error.code ?? "submit_refused",
1975
+ error: `Text was written to '${sessionName}' but ${enter}: ${error.message}` };
1976
+ }
1977
+ // A throw after the text write was issued: whether it landed is unknown, so it is not "nothing sent" either.
1978
+ if (error.inputAttempted === true) {
1979
+ return { ok: false, sessionName, outcome: "failed", inputPhase: "attempted", reason: error.code ?? "delivery_indeterminate",
1980
+ error: `A text write to '${sessionName}' was issued, but its result was lost: ${error.message}` };
1981
+ }
1239
1982
  return { ok: false, sessionName, sent: false, reason: error.code ?? "guard_unavailable", error: error.message };
1240
1983
  }
1241
1984
  }
@@ -1261,7 +2004,10 @@ export class SessionTransport {
1261
2004
  if (!preTarget)
1262
2005
  return null;
1263
2006
  const prePref = guard.preference(preTarget.nodeId);
1264
- if (prePref.effective || prePref.desired)
2007
+ // Hold mode retains in the lease, and draft-aware mode reads the input there first and returns its
2008
+ // immediate result (held, or refused with its reason): neither takes the lease-free wait. A
2009
+ // draft-aware send whose input is empty waits for idle inside the lease, as before this wait existed.
2010
+ if (prePref.effective || prePref.desired || prePref.desiredMode === "draft-aware" || prePref.effectiveMode === "draft-aware")
1265
2011
  return null;
1266
2012
  meta = this.getSessionMeta(sessionName);
1267
2013
  bound = guard.target(sessionName);
@@ -1349,6 +2095,7 @@ export class SessionTransport {
1349
2095
  timeoutMs,
1350
2096
  binding: { sessionName, nodeId: meta.nodeId, occupant: meta.occupant, pane: meta.pane },
1351
2097
  readinessFromPaneOnly: opts.readinessFromPaneOnly === true,
2098
+ signal: callerSignal(opts), cancelledBy: "caller",
1352
2099
  });
1353
2100
  if (!preWait.ok) {
1354
2101
  return { ok: false, failure: {
@@ -1357,6 +2104,7 @@ export class SessionTransport {
1357
2104
  reason: preWait.reason,
1358
2105
  error: preWait.error,
1359
2106
  sent: false,
2107
+ inputPhase: "not_reached",
1360
2108
  activity: preWait.activity,
1361
2109
  waitedMs: preWait.waitedMs,
1362
2110
  attempts: preWait.attempts,
@@ -1388,6 +2136,31 @@ export class SessionTransport {
1388
2136
  runtimeAdvisory = `runtime: ${observation.detail}; delivery proceeds without verified native identity.`;
1389
2137
  return null;
1390
2138
  };
2139
+ // #331: with mouse on, scrolling a seat pane puts it in tmux copy mode. A paste still reaches
2140
+ // the input there, but copy mode swallows the Enter, so the send would report ok while the
2141
+ // text sits unsubmitted. Leave the mode once (send-keys -X cancel) and deliver; fail before
2142
+ // typing anything only if the pane still reads as in a mode. An unknown answer keeps today's path.
2143
+ const readPaneInMode = async () => {
2144
+ try {
2145
+ return typeof this.tmuxAdapter.isPaneInMode === "function" ? await this.tmuxAdapter.isPaneInMode(sessionName) : null;
2146
+ }
2147
+ catch {
2148
+ return null;
2149
+ }
2150
+ };
2151
+ const leavePaneMode = async (outcome) => {
2152
+ if (await readPaneInMode() !== true)
2153
+ return null;
2154
+ try {
2155
+ if (typeof this.tmuxAdapter.cancelPaneMode === "function")
2156
+ await this.tmuxAdapter.cancelPaneMode(sessionName);
2157
+ }
2158
+ catch { /* the re-read below decides */ }
2159
+ if (await readPaneInMode() !== true)
2160
+ return null;
2161
+ return { ok: false, sessionName, sent: false, reason: "pane_in_mode",
2162
+ error: `'${sessionName}' is in a tmux mode (such as copy mode) and could not leave it, so Enter would not reach the agent. ${outcome}` };
2163
+ };
1391
2164
  // S01/S02 P2 observation context, frozen at attempt entry before any await.
1392
2165
  const observed = this.captureObserver ? {
1393
2166
  attemptId: randomUUID(),
@@ -1507,7 +2280,7 @@ export class SessionTransport {
1507
2280
  if (!opts.requireFullStagedText || !opts.onStartupMismatch)
1508
2281
  return;
1509
2282
  try {
1510
- const evidence = startupSubmissionEvidence(pane, expected, opts.submitOnlyCaptureLines ?? 50);
2283
+ const evidence = startupSubmissionEvidence(pane, expected, opts.submitOnlyCaptureLines ?? 50, runtime);
1511
2284
  if (evidence)
1512
2285
  opts.onStartupMismatch(evidence);
1513
2286
  }
@@ -1522,8 +2295,8 @@ export class SessionTransport {
1522
2295
  throw error; // Preserve the existing guarded/unguarded error handling.
1523
2296
  }
1524
2297
  const staged = opts.requireFullStagedText
1525
- ? inspectStartupStagedText(pane, expected) === "staged"
1526
- : hasExpectedStagedText(pane, expected);
2298
+ ? inspectStartupStagedText(pane, expected, runtime) === "staged"
2299
+ : hasExpectedStagedText(pane, expected, runtime);
1527
2300
  if (!staged) {
1528
2301
  recordMismatch(pane);
1529
2302
  return {
@@ -1533,13 +2306,41 @@ export class SessionTransport {
1533
2306
  error: `submitOnly refused: the pane of '${sessionName}' does not show the expected staged text — pressing Enter here could drive something else entirely. Nothing was submitted.`,
1534
2307
  };
1535
2308
  }
2309
+ this.tmuxAdapter.deliveryGuard?.expectStagedInput(sessionName, expected);
2310
+ // This Enter is the request's first input, so a caller that has gone ends it here, before any key, as for a paste.
2311
+ const cancel = callerSignal(opts);
2312
+ if (cancel?.aborted)
2313
+ return callerCancelled(sessionName);
1536
2314
  const targetFailure = await checkClaudeTarget();
1537
2315
  if (targetFailure)
1538
2316
  return targetFailure;
1539
- const submitResult = await this.runStage("session_transport.submit", () => this.tmuxAdapter.sendKeys(sessionName, ["Enter"]), (result) => result.ok ? "ok" : "failed");
2317
+ const copyModeFailure = await leavePaneMode("Nothing was submitted.");
2318
+ if (copyModeFailure)
2319
+ return copyModeFailure;
2320
+ if (cancel?.aborted)
2321
+ return callerCancelled(sessionName);
2322
+ // The last check runs in the adapter, immediately before the key (after its guard checks).
2323
+ const beforeKey = cancel ? () => { if (cancel.aborted)
2324
+ throw new DeliveryGuardError(CALLER_CANCELLED, "caller cancelled before the key"); } : undefined;
2325
+ let submitResult;
2326
+ try {
2327
+ submitResult = await this.runStage("session_transport.submit", () => beforeKey ? this.tmuxAdapter.sendKeys(sessionName, ["Enter"], beforeKey) : this.tmuxAdapter.sendKeys(sessionName, ["Enter"]), (result) => result.ok ? "ok" : "failed");
2328
+ }
2329
+ catch (error) {
2330
+ // Unguarded, the adapter's pre-key check throws instead of returning a result; no key was sent.
2331
+ if (error instanceof DeliveryGuardError && error.code === CALLER_CANCELLED && cancel?.aborted)
2332
+ return callerCancelled(sessionName);
2333
+ throw error;
2334
+ }
2335
+ if (!submitResult.ok && submitResult.code === CALLER_CANCELLED && cancel?.aborted)
2336
+ return callerCancelled(sessionName);
1540
2337
  if (!submitResult.ok) {
1541
2338
  return { ok: false, sessionName, reason: "submit_failed", outcome: "failed", error: `submitOnly: Enter did not land on '${sessionName}': ${submitResult.message}` };
1542
2339
  }
2340
+ try {
2341
+ opts.onInputEffect?.("submitted");
2342
+ }
2343
+ catch { /* Observation cannot change an input result. */ }
1543
2344
  return observe({ ok: true, sessionName, outcome: "rendered-unconfirmed", submitOnly: true });
1544
2345
  }
1545
2346
  // #1084: when send() already waited lease-free (preWaitedIdle), the wait
@@ -1553,6 +2354,7 @@ export class SessionTransport {
1553
2354
  timeoutMs: waitForIdleMs,
1554
2355
  binding: observed?.binding,
1555
2356
  readinessFromPaneOnly: opts?.readinessFromPaneOnly === true,
2357
+ signal: callerSignal(opts), cancelledBy: "caller",
1556
2358
  });
1557
2359
  waitEvidence = {
1558
2360
  activity: waitResult.activity,
@@ -1566,6 +2368,7 @@ export class SessionTransport {
1566
2368
  reason: waitResult.reason,
1567
2369
  error: waitResult.error,
1568
2370
  sent: false,
2371
+ inputPhase: "not_reached",
1569
2372
  ...waitEvidence,
1570
2373
  };
1571
2374
  }
@@ -1610,6 +2413,7 @@ export class SessionTransport {
1610
2413
  timeoutLabelMs: waitForIdleMs,
1611
2414
  binding: observed?.binding,
1612
2415
  readinessFromPaneOnly: opts.readinessFromPaneOnly === true,
2416
+ signal: callerSignal(opts), cancelledBy: "caller",
1613
2417
  });
1614
2418
  waitEvidence = {
1615
2419
  activity: rewait.activity,
@@ -1623,6 +2427,7 @@ export class SessionTransport {
1623
2427
  reason: rewait.reason,
1624
2428
  error: rewait.error,
1625
2429
  sent: false,
2430
+ inputPhase: "not_reached",
1626
2431
  ...waitEvidence,
1627
2432
  };
1628
2433
  }
@@ -1654,6 +2459,7 @@ export class SessionTransport {
1654
2459
  ok: false,
1655
2460
  sessionName,
1656
2461
  sent: false,
2462
+ inputPhase: "not_reached",
1657
2463
  reason: late.reason,
1658
2464
  error: late.error,
1659
2465
  activity: recheck,
@@ -1680,7 +2486,7 @@ export class SessionTransport {
1680
2486
  // the positive-picker guard (FR-4 — the footgun separation). The advisory is carried on the
1681
2487
  // success result via `warning` so the honest telemetry is surfaced.
1682
2488
  let sendAdvisory;
1683
- let promptOverride = false;
2489
+ const promptOverride = waitForIdleMs === undefined && Boolean(opts?.dangerouslyInteract);
1684
2490
  if (waitForIdleMs === undefined) {
1685
2491
  const readiness = await this.classifySendReadiness({
1686
2492
  sessionName,
@@ -1688,42 +2494,37 @@ export class SessionTransport {
1688
2494
  attachmentType: sessionMeta.attachmentType,
1689
2495
  binding: observed?.binding,
1690
2496
  });
1691
- // Single state dispatch (B1 code-review fix): flattened so `unknown` ALWAYS attaches the advisory
1692
- // regardless of whether --dangerously-interact was passed — the deliberate-override branch no
1693
- // longer bypasses unknown handling.
1694
- if (readiness.state === "needs_input") {
1695
- // The POSITIVE picker/approval footgun. --dangerously-interact is the deliberate audited
1696
- // override (reason required + an auditable record persisted BEFORE the send; fail closed if it
1697
- // cannot be audited so an unauditable override never sends). Otherwise refuse with the
1698
- // proceed-path. This is the ONLY state --dangerously-interact bypasses.
1699
- if (opts?.dangerouslyInteract) {
1700
- if (!opts.reason || opts.reason.trim().length === 0) {
1701
- return {
1702
- ok: false,
1703
- sessionName,
1704
- reason: "dangerously_interact_requires_reason",
1705
- error: "--dangerously-interact requires --reason explaining why the prompt is being driven. No text was sent.",
1706
- };
1707
- }
1708
- const audit = this.recordPromptOverride({
2497
+ // Explicit interaction selects key input even when readiness cannot recognize the prompt.
2498
+ // Only bypassing a positive picker requires a successful audit; other states already sent.
2499
+ if (opts?.dangerouslyInteract) {
2500
+ if (readiness.state === "needs_input" && (!opts.reason || opts.reason.trim().length === 0)) {
2501
+ return {
2502
+ ok: false,
1709
2503
  sessionName,
1710
- readiness,
1711
- actorSession: opts.actorSession ?? null,
1712
- overrideReason: opts.reason,
1713
- });
1714
- if (!audit.ok) {
1715
- return {
1716
- ok: false,
1717
- sessionName,
1718
- reason: "prompt_override_audit_unavailable",
1719
- activity: readiness,
1720
- error: `Refused: --dangerously-interact requires an auditable override record, which could not be persisted (${audit.reason}). No text was sent.`,
1721
- };
1722
- }
1723
- // Audited answers use unbracketed input; a choice may submit itself.
1724
- promptOverride = true;
2504
+ reason: "dangerously_interact_requires_reason",
2505
+ error: "--dangerously-interact requires --reason explaining why the prompt is being driven. No text was sent.",
2506
+ };
1725
2507
  }
1726
- else {
2508
+ const audit = this.recordPromptOverride({
2509
+ sessionName,
2510
+ readiness,
2511
+ actorSession: opts.actorSession ?? null,
2512
+ overrideReason: opts.reason ?? null,
2513
+ });
2514
+ if (!audit.ok && readiness.state === "needs_input") {
2515
+ return {
2516
+ ok: false,
2517
+ sessionName,
2518
+ reason: "prompt_override_audit_unavailable",
2519
+ activity: readiness,
2520
+ error: `Refused: --dangerously-interact requires an auditable override record, which could not be persisted (${audit.reason}). No text was sent.`,
2521
+ };
2522
+ }
2523
+ if (!audit.ok)
2524
+ sendAdvisory = "prompt-override: audit unavailable (" + audit.reason + "); input sent anyway.";
2525
+ }
2526
+ if (readiness.state === "needs_input") {
2527
+ if (!promptOverride) {
1727
2528
  return {
1728
2529
  ok: false,
1729
2530
  sessionName,
@@ -1741,7 +2542,7 @@ export class SessionTransport {
1741
2542
  // surfaced without ever blocking communication. Hooks are advisory telemetry, not authority
1742
2543
  // over whether agents can talk.
1743
2544
  const linkDiagnosis = await this.diagnoseProducerLink(sessionName);
1744
- sendAdvisory = `producer-link: ${linkDiagnosis} — activity could not be determined (${readiness.reason}); sent anyway (telemetry is advisory).`;
2545
+ sendAdvisory = [sendAdvisory, `producer-link: ${linkDiagnosis} — activity could not be determined (${readiness.reason}); sent anyway (telemetry is advisory).`].filter(Boolean).join(" ");
1745
2546
  // fall through to the send below.
1746
2547
  }
1747
2548
  else if (readiness.state === "running") {
@@ -1750,11 +2551,15 @@ export class SessionTransport {
1750
2551
  // attach it on the success result + PROCEED (mirrors the unknown inversion). --force is now a
1751
2552
  // no-op here (the option is kept for back-compat). needs_input (positive picker) remains the
1752
2553
  // ONLY hard refuse; unknown/stale/missing already proceed-with-advisory above.
1753
- sendAdvisory = `target pane appears mid-task; sent anyway (busy is advisory, not a block).`;
2554
+ sendAdvisory = [sendAdvisory, "target pane appears mid-task; sent anyway (busy is advisory, not a block)."].filter(Boolean).join(" ");
1754
2555
  // fall through to the send below.
1755
2556
  }
1756
2557
  // idle (or running/unknown — now advisory-and-proceed) → proceed to send.
1757
2558
  }
2559
+ // transport.verify_input_box: a verified Claude Code or Codex send reports on input-box evidence. The
2560
+ // setting is read only for those sends; everything else keeps today's path.
2561
+ const inputBoxRuntime = opts?.verify && (runtime === "claude-code" || runtime === "codex") && this.inputBoxSettingOn()
2562
+ ? runtime : null;
1758
2563
  if (opts?.verify) {
1759
2564
  const captureSeq = observed ? nextCaptureSeq++ : 0;
1760
2565
  try {
@@ -1776,55 +2581,116 @@ export class SessionTransport {
1776
2581
  if (opts?.stampISO) {
1777
2582
  text = appendDeliveredSegment(text, this.now().getTime() - Date.parse(opts.stampISO));
1778
2583
  }
2584
+ // A caller that went away while this send waited: nothing has been written, so nothing ever is.
2585
+ if (callerSignal(opts)?.aborted)
2586
+ return observe({ ...callerCancelled(sessionName), ...(waitMode ? waitEvidence : {}) });
1779
2587
  // Recheck the selected recipient after readiness/capture awaits, at the input boundary.
1780
2588
  const targetFailure = await checkClaudeTarget();
1781
2589
  if (targetFailure)
1782
2590
  return observe(targetFailure);
2591
+ const copyModeFailure = await leavePaneMode("No text was sent.");
2592
+ if (copyModeFailure)
2593
+ return observe({ ...copyModeFailure, ...(waitMode ? waitEvidence : {}) });
1783
2594
  // 3. Deliver ordinary messages as paste, audited prompt answers as key input.
1784
2595
  if (observed)
1785
2596
  observed.sentHash = hashSentText(text);
2597
+ // The caller's cancellation is checked once more at the adapter's last step before the paste (after its buffer
2598
+ // setup). A refusal there is proven pre-input and returns caller_cancelled; Enter below never checks it.
2599
+ const cancel = callerSignal(opts);
2600
+ if (cancel?.aborted)
2601
+ return observe({ ...callerCancelled(sessionName), ...(waitMode ? waitEvidence : {}) });
2602
+ const beforePaste = cancel ? () => { opts?.beforeWrite?.(); if (cancel.aborted)
2603
+ throw new DeliveryGuardError(CALLER_CANCELLED, "caller cancelled before the paste"); } : opts?.beforeWrite;
2604
+ // Set once the text write has been handed to the adapter: from then on a throw is not "nothing sent".
2605
+ let writeIssued = false;
2606
+ const tagIssued = (error) => {
2607
+ if (writeIssued && error && typeof error === "object")
2608
+ error.inputAttempted = true;
2609
+ throw error;
2610
+ };
1786
2611
  const textResult = await this.runStage("session_transport.send_text", () => {
1787
2612
  opts?.beforeWrite?.();
2613
+ writeIssued = true;
1788
2614
  if (promptOverride)
1789
- return this.tmuxAdapter.sendText(sessionName, text, opts?.beforeWrite, { bracketed: false });
1790
- return opts?.beforeWrite ? this.tmuxAdapter.sendText(sessionName, text, opts.beforeWrite) : this.tmuxAdapter.sendText(sessionName, text);
1791
- }, (result) => result.ok ? "ok" : "failed");
2615
+ return this.tmuxAdapter.sendText(sessionName, text, beforePaste, { bracketed: false });
2616
+ return beforePaste ? this.tmuxAdapter.sendText(sessionName, text, beforePaste) : this.tmuxAdapter.sendText(sessionName, text);
2617
+ }, (result) => result.ok ? "ok" : "failed").catch(tagIssued);
2618
+ if (!textResult.ok && textResult.code === CALLER_CANCELLED && cancel?.aborted) {
2619
+ return observe({ ...callerCancelled(sessionName), ...(waitMode ? waitEvidence : {}) });
2620
+ }
1792
2621
  if (!textResult.ok) {
2622
+ // A draft refusal is the guard holding the write before any input; it is not an attempted write.
2623
+ const policyRefusal = typeof textResult.code === "string" && textResult.code.startsWith("draft_");
1793
2624
  return observe({
1794
2625
  ok: false,
1795
2626
  sessionName,
1796
- reason: "send_failed",
2627
+ reason: policyRefusal ? textResult.code : "send_failed",
1797
2628
  outcome: "failed",
2629
+ ...(policyRefusal ? {} : { inputPhase: "attempted" }),
1798
2630
  error: `Failed to send text to '${sessionName}': ${textResult.message}`,
1799
- ...(waitMode ? { sent: false, ...waitEvidence } : {}),
2631
+ ...(waitMode || policyRefusal ? { sent: false, ...waitEvidence } : {}),
1800
2632
  });
1801
2633
  }
1802
- // 4. Wait 200ms (spike-proven delay)
1803
- await this.sleep(200);
1804
- if (runtime === "claude-code" && bindingChanged())
1805
- return observe(changedRecipient(true));
1806
- if (promptOverride) {
1807
- const pane = await this.runStage("session_transport.prompt_override_pre_submit_capture", () => this.tmuxAdapter.capturePaneContent(sessionName, 50)).catch(() => null);
2634
+ try {
2635
+ opts?.onInputEffect?.("pasted");
2636
+ }
2637
+ catch { /* Observation cannot change a completed paste. */ }
2638
+ // From the successful paste to Enter the text may sit in the seat's input. Any throw in this span is
2639
+ // tagged inputWritten, so no caller reports it as unsent; a step added here later stays covered.
2640
+ let boxBefore = null;
2641
+ let submitResult;
2642
+ let enterIssued = false; // set as the Enter key is handed to the adapter
2643
+ try {
2644
+ // 4. Wait 200ms (spike-proven delay)
2645
+ await this.sleep(200);
2646
+ // With transport.verify_input_box on, one bounded read of the input box and its pane process before
2647
+ // Enter (a 500 ms budget after the 200 ms wait). It only reports: nothing here decides whether Enter is sent.
2648
+ // The seat is read from the existing --verify capture taken before the paste (the whole screen); no new read.
2649
+ if (inputBoxRuntime && !promptOverride) {
2650
+ const read = await this.readWithinDeadline("session_transport.input_box_capture", () => Promise.all([
2651
+ this.tmuxAdapter.getPanePid(sessionName).catch(() => null),
2652
+ this.tmuxAdapter.capturePaneContent(sessionName, 30).catch(() => null),
2653
+ ]));
2654
+ boxBefore = { seat: readSeatBeforePaste(preVerifyContent, inputBoxRuntime, sessionName),
2655
+ reading: readInputBox(read?.[1] ?? null, inputBoxRuntime, text), pid: read?.[0] ?? null, pane: read?.[1] ?? null };
2656
+ }
1808
2657
  if (runtime === "claude-code" && bindingChanged())
1809
2658
  return observe(changedRecipient(true));
1810
- if (!promptAnswerStaged(pane, text, runtime)) {
1811
- return observe({ ok: true, sessionName, sent: true, verified: false, outcome: "rendered-unconfirmed", promptInteraction: "unverified",
1812
- warning: "prompt-override: answer sent as unbracketed input; submission unverified. No trailing Enter: the complete answer is not visibly staged (it may have been consumed, the prompt changed, or observation is unavailable)." });
2659
+ if (promptOverride) {
2660
+ const pane = await this.runStage("session_transport.prompt_override_pre_submit_capture", () => this.tmuxAdapter.capturePaneContent(sessionName, 50)).catch(() => null);
2661
+ if (runtime === "claude-code" && bindingChanged())
2662
+ return observe(changedRecipient(true));
2663
+ if (!promptAnswerStaged(pane, text, runtime)) {
2664
+ return observe({ ok: true, sessionName, sent: true, verified: false, outcome: "rendered-unconfirmed", promptInteraction: "unverified",
2665
+ ...(inputBoxRuntime ? { inputBox: INPUT_BOX_UNCHECKED } : {}),
2666
+ warning: [sendAdvisory, "prompt-override: answer sent as unbracketed input; submission unverified. No trailing Enter: the complete answer is not visibly staged (it may have been consumed, the prompt changed, or observation is unavailable)."].filter(Boolean).join(" ") });
2667
+ }
2668
+ sendAdvisory = [sendAdvisory, "prompt-override: answer sent as unbracketed input; Enter submitted the complete still-staged answer."].filter(Boolean).join(" ");
2669
+ }
2670
+ // 5. Submit (Enter)
2671
+ submitResult = await this.runStage("session_transport.submit", () => { opts?.beforeWrite?.(); enterIssued = true; return opts?.beforeWrite ? this.tmuxAdapter.sendKeys(sessionName, ["Enter"], opts.beforeWrite) : this.tmuxAdapter.sendKeys(sessionName, ["Enter"]); }, (result) => result.ok ? "ok" : "failed");
2672
+ if (!submitResult.ok) {
2673
+ const policyRefusal = typeof submitResult.code === "string" && submitResult.code.startsWith("draft_");
2674
+ return observe({
2675
+ ok: false,
2676
+ sessionName,
2677
+ reason: policyRefusal ? submitResult.code : "submit_failed",
2678
+ outcome: "failed",
2679
+ inputPhase: "written",
2680
+ error: `Text is in '${sessionName}', but the Enter reported a failure, so the submission outcome is unconfirmed. The agent may need manual attention.`,
2681
+ ...(waitMode || policyRefusal ? { sent: true, ...waitEvidence } : {}),
2682
+ });
1813
2683
  }
1814
- sendAdvisory = "prompt-override: answer sent as unbracketed input; Enter submitted the complete still-staged answer.";
1815
2684
  }
1816
- // 5. Submit (Enter)
1817
- const submitResult = await this.runStage("session_transport.submit", () => { opts?.beforeWrite?.(); return opts?.beforeWrite ? this.tmuxAdapter.sendKeys(sessionName, ["Enter"], opts.beforeWrite) : this.tmuxAdapter.sendKeys(sessionName, ["Enter"]); }, (result) => result.ok ? "ok" : "failed");
1818
- if (!submitResult.ok) {
1819
- return observe({
1820
- ok: false,
1821
- sessionName,
1822
- reason: "submit_failed",
1823
- outcome: "failed",
1824
- error: `Text is visible in '${sessionName}' but was not submitted (Enter failed). The agent may need manual attention.`,
1825
- ...(waitMode ? { sent: true, ...waitEvidence } : {}),
1826
- });
2685
+ catch (error) {
2686
+ if (error && typeof error === "object")
2687
+ Object.assign(error, { inputWritten: true, enterAttempted: enterIssued });
2688
+ throw error;
1827
2689
  }
2690
+ try {
2691
+ opts?.onInputEffect?.("submitted");
2692
+ }
2693
+ catch { /* Observation cannot change a completed submission. */ }
1828
2694
  const interaction = promptOverride ? { promptInteraction: "enter-sent" } : {};
1829
2695
  // 6. Verify if requested. At this point text + Enter BOTH succeeded, so the
1830
2696
  // message LANDED; the capture only re-confirms the render. Not re-confirming
@@ -1842,23 +2708,77 @@ export class SessionTransport {
1842
2708
  // its original prefix-count verification even while a command is running.
1843
2709
  const terminal = runtime === "terminal";
1844
2710
  const { snippet, envelope } = terminal ? { snippet: text.slice(0, 40), envelope: false } : verificationSnippet(text);
2711
+ // Only Codex gains »; all other runtimes keep the existing boundaries.
2712
+ const promptClass = runtime === "codex" ? COMPOSER_PROMPT_CLASS : "❯›";
1845
2713
  const transcript = terminal ? (pane) => pane ?? ""
1846
- : envelope ? verificationTranscript : (pane) => normalizeVerificationText(verificationTranscript(pane));
1847
- const count = envelope ? countEnvelopeOccurrences : countOccurrences;
2714
+ : (pane) => envelope ? verificationTranscript(pane, promptClass)
2715
+ : normalizeVerificationText(verificationTranscript(pane, promptClass));
2716
+ const count = envelope ? (pane, expected) => countEnvelopeOccurrences(pane, expected, promptClass) : countOccurrences;
1848
2717
  const preCount = count(transcript(preVerifyContent), snippet);
1849
2718
  const postCount = count(transcript(content), snippet);
1850
- const verified = postCount > preCount;
1851
- return observe({ ok: true, sessionName, ...interaction, verified, outcome: verified ? "delivered" : "rendered-unconfirmed", ...(sendAdvisory ? { warning: sendAdvisory } : {}), ...(waitMode ? { sent: true, ...waitEvidence } : {}) });
2719
+ // An audited prompt answer reads no box (no extra read is added for it), so it says so: unchecked, and
2720
+ // unconfirmed; it never falls back to the count below.
2721
+ const inputBox = boxBefore && inputBoxRuntime
2722
+ ? await this.inputBoxEvidence(sessionName, sessionMeta, boxBefore, readInputBox(content, inputBoxRuntime, text, boxBefore.pane))
2723
+ : inputBoxRuntime && promptOverride ? INPUT_BOX_UNCHECKED : undefined;
2724
+ // With the setting on, only a seat proven idle before the paste and input-box evidence verify: exact before
2725
+ // Enter, empty after, same process. A not-idle or unknown seat always reads unconfirmed.
2726
+ const verified = inputBoxRuntime
2727
+ ? inputBox?.seat === "idle" && inputBox.before === "exact" && inputBox.after === "empty" && inputBox.sameProcess
2728
+ : postCount > preCount;
2729
+ return observe({ ok: true, sessionName, ...interaction, verified, outcome: verified ? "delivered" : "rendered-unconfirmed", ...(inputBox ? { inputBox } : {}), ...(sendAdvisory ? { warning: sendAdvisory } : {}), ...(waitMode ? { sent: true, ...waitEvidence } : {}) });
1852
2730
  }
1853
2731
  catch {
1854
2732
  if (observed && observed.post.state === "not_reached") {
1855
2733
  observed.post = { state: "unavailable", cause: "capture_error", capturedAt: this.now().toISOString(), captureSeq };
1856
2734
  }
1857
- return observe({ ok: true, sessionName, ...interaction, verified: false, outcome: "rendered-unconfirmed", ...(sendAdvisory ? { warning: sendAdvisory } : {}), ...(waitMode ? { sent: true, ...waitEvidence } : {}) });
2735
+ const inputBox = boxBefore
2736
+ ? { seat: boxBefore.seat, before: boxBefore.reading, after: "unavailable", sameProcess: false }
2737
+ : inputBoxRuntime && promptOverride ? INPUT_BOX_UNCHECKED : undefined;
2738
+ return observe({ ok: true, sessionName, ...interaction, verified: false, outcome: "rendered-unconfirmed", ...(inputBox ? { inputBox } : {}), ...(sendAdvisory ? { warning: sendAdvisory } : {}), ...(waitMode ? { sent: true, ...waitEvidence } : {}) });
1858
2739
  }
1859
2740
  }
1860
2741
  return observe({ ok: true, sessionName, ...interaction, ...(sendAdvisory ? { warning: sendAdvisory } : {}), ...(waitMode ? { sent: true, ...waitEvidence } : {}) });
1861
2742
  }
2743
+ inputBoxSettingOn() {
2744
+ try {
2745
+ return this.verifyInputBox?.() === true;
2746
+ }
2747
+ catch {
2748
+ return false;
2749
+ }
2750
+ }
2751
+ /** One input-box read, timed by the slow-op recorder and raced against the deadline. Null on timeout or
2752
+ * error; an abandoned read is ignored. A read never decides whether Enter is sent. */
2753
+ async readWithinDeadline(site, read) {
2754
+ let timer;
2755
+ try {
2756
+ const pending = this.runStage(site, read);
2757
+ pending.catch(() => undefined);
2758
+ return await Promise.race([
2759
+ pending,
2760
+ new Promise((resolve) => { timer = setTimeout(() => resolve(null), INPUT_BOX_READ_DEADLINE_MS); }),
2761
+ ]);
2762
+ }
2763
+ catch {
2764
+ return null;
2765
+ }
2766
+ finally {
2767
+ if (timer !== undefined)
2768
+ clearTimeout(timer);
2769
+ }
2770
+ }
2771
+ /** After the post-Enter capture: the same pane process (pid read before Enter and now) and the same binding. */
2772
+ async inputBoxEvidence(sessionName, meta, before, after) {
2773
+ const pid = await this.readWithinDeadline("session_transport.input_box_pane_pid", () => this.tmuxAdapter.getPanePid(sessionName));
2774
+ let sameBinding = false;
2775
+ try {
2776
+ const now = this.getSessionMeta(sessionName);
2777
+ sameBinding = now.nodeId === meta.nodeId && now.pane === meta.pane && now.occupant === meta.occupant;
2778
+ }
2779
+ catch { /* unknown binding is not the same binding */ }
2780
+ return { seat: before.seat, before: before.reading, after, sameProcess: before.pid !== null && pid !== null && pid === before.pid && sameBinding };
2781
+ }
1862
2782
  runStage(site, fn, classify) {
1863
2783
  return this.slowOpRecorder?.runStage
1864
2784
  ? this.slowOpRecorder.runStage(site, fn, classify)
@@ -1902,12 +2822,15 @@ export class SessionTransport {
1902
2822
  activity, waitedMs: input.timeoutMs, attempts,
1903
2823
  };
1904
2824
  };
2825
+ const cancelReason = input.cancelledBy === "caller" ? CALLER_CANCELLED : "preparation_cancelled";
1905
2826
  const cancelled = () => ({
1906
2827
  ok: false,
1907
- reason: "preparation_cancelled",
1908
- error: "Managed preparation ended; no compact authorized.",
2828
+ reason: cancelReason,
2829
+ error: input.cancelledBy === "caller"
2830
+ ? "The request was cancelled while it waited for the seat to be idle. No text was sent, and none will be."
2831
+ : "Managed preparation ended; no compact authorized.",
1909
2832
  activity: last ?? {
1910
- state: "unknown", reason: "preparation_cancelled", evidenceSource: "pane_heuristic",
2833
+ state: "unknown", reason: cancelReason, evidenceSource: "pane_heuristic",
1911
2834
  sampledAt: this.now().toISOString(), evidence: null,
1912
2835
  },
1913
2836
  waitedMs: Math.max(0, Math.min(input.timeoutMs, input.timeoutMs - (deadline - Date.now()))),
@@ -1919,7 +2842,9 @@ export class SessionTransport {
1919
2842
  if (attempts > 0 && Date.now() > deadline)
1920
2843
  return expire();
1921
2844
  attempts++;
1922
- const activity = await this.observeReadinessWithin(input, deadline - Date.now());
2845
+ // A caller's cancellation also ends a pending observation (the abandoned read is ignored, never delivered on).
2846
+ // Managed preparation's wait keeps its own behaviour: it doesn't race the read against its signal.
2847
+ const activity = await this.observeReadinessWithin(input, deadline - Date.now(), input.cancelledBy === "caller" ? input.signal : undefined);
1923
2848
  if (input.signal?.aborted)
1924
2849
  return cancelled();
1925
2850
  if (activity === null || Date.now() > deadline)
@@ -1945,21 +2870,28 @@ export class SessionTransport {
1945
2870
  await this.sleep(Math.min(this.waitForIdlePollMs, remainingMs));
1946
2871
  }
1947
2872
  }
1948
- /** One readiness observation, raced against the time left before the wait's deadline. Null
1949
- * when the deadline wins; the abandoned observation is ignored, never delivered on. */
1950
- async observeReadinessWithin(input, remainingMs) {
2873
+ /** One readiness observation, raced against the time left before the wait's deadline and, when given, the
2874
+ * caller's cancellation. Null when the deadline or the cancellation wins; the abandoned observation is ignored,
2875
+ * never delivered on. */
2876
+ async observeReadinessWithin(input, remainingMs, cancel) {
2877
+ if (cancel?.aborted)
2878
+ return null;
1951
2879
  const observation = this.classifySendReadiness(input);
1952
2880
  observation.catch(() => undefined);
1953
2881
  let timer;
2882
+ let onAbort;
1954
2883
  try {
1955
2884
  return await Promise.race([
1956
2885
  observation,
1957
2886
  new Promise((resolve) => { timer = setTimeout(() => resolve(null), Math.max(0, remainingMs)); }),
2887
+ ...(cancel ? [new Promise((resolve) => { onAbort = () => resolve(null); cancel.addEventListener("abort", onAbort, { once: true }); })] : []),
1958
2888
  ]);
1959
2889
  }
1960
2890
  finally {
1961
2891
  if (timer !== undefined)
1962
2892
  clearTimeout(timer);
2893
+ if (cancel && onAbort)
2894
+ cancel.removeEventListener("abort", onAbort);
1963
2895
  }
1964
2896
  }
1965
2897
  async claudeDeliveryObservation(sessionName, pane, resumeToken, rotation = null) {
@@ -2015,10 +2947,16 @@ export class SessionTransport {
2015
2947
  }
2016
2948
  async classifySendReadiness(input) {
2017
2949
  const now = this.now();
2950
+ const composerSnapshot = this.tmuxAdapter.deliveryGuard?.needsInputCheck(input.sessionName)
2951
+ ? () => this.tmuxAdapter.captureComposerSnapshot(input.binding?.pane ?? input.sessionName) : undefined;
2018
2952
  const hookActivity = this.agentActivityStore?.getLatestForNode({
2019
2953
  sessionName: input.sessionName,
2020
2954
  now,
2021
2955
  });
2956
+ const permissionPending = latestHookPermissionPending(hookActivity, input.runtime);
2957
+ if (hookActivity && permissionPending && this.hookFreshForSend(hookActivity, now)) {
2958
+ return { ...hookActivity, state: "needs_input" };
2959
+ }
2022
2960
  // Pane-only readiness never lets a hook authorise a send, but a fresh hook showing work or a
2023
2961
  // waiting person still refuses one, as it does for every other send.
2024
2962
  if (input.readinessFromPaneOnly &&
@@ -2035,7 +2973,9 @@ export class SessionTransport {
2035
2973
  if (!input.readinessFromPaneOnly &&
2036
2974
  hookActivity &&
2037
2975
  hookActivity.evidenceSource === "runtime_hook" &&
2038
- hookActivity.stale !== true) {
2976
+ hookActivity.stale !== true &&
2977
+ hookActivity.reason !== "permission_request_pending" &&
2978
+ hookActivity.reason !== "permission_prompt_cleared") {
2039
2979
  // Fresh hook (within the 15s send window): authoritative for any state.
2040
2980
  if (this.hookFreshForSend(hookActivity, now)) {
2041
2981
  return hookActivity;
@@ -2055,6 +2995,7 @@ export class SessionTransport {
2055
2995
  // vetoes (→ refuse); an unknown pane (the flaky-Codex case this trust
2056
2996
  // exists for) or a clean idle pane does NOT veto → trust the stale hook.
2057
2997
  const paneVeto = await probeSessionActivity({
2998
+ composerSnapshot,
2058
2999
  sessionName: input.sessionName,
2059
3000
  runtime: input.runtime,
2060
3001
  attachmentType: input.attachmentType,
@@ -2070,6 +3011,7 @@ export class SessionTransport {
2070
3011
  }
2071
3012
  }
2072
3013
  const probe = await probeSessionActivity({
3014
+ composerSnapshot,
2073
3015
  sessionName: input.sessionName,
2074
3016
  runtime: input.runtime,
2075
3017
  attachmentType: input.attachmentType,
@@ -2093,6 +3035,9 @@ export class SessionTransport {
2093
3035
  (hookActivity.state === "running" || hookActivity.state === "needs_input")) {
2094
3036
  return hookActivity;
2095
3037
  }
3038
+ if (probe.state === "unknown" && permissionPending) {
3039
+ return { ...probe, state: "needs_input", reason: "permission_request_unverified" };
3040
+ }
2096
3041
  // A latest hook saying the seat waits on a person (approval, picker or elicitation) stays positive
2097
3042
  // picker evidence past the send window, and past the store's freshness window: nothing newer was
2098
3043
  // recorded, so nothing answered it. The pane alone can miss a picker it has no signature for (a
@@ -2219,10 +3164,9 @@ export class SessionTransport {
2219
3164
  }
2220
3165
  return `a recent activity hook exists but the live pane probe could not confirm idle (possible identity mismatch between the seat env, the DB, and the stored payload)`;
2221
3166
  }
2222
- // OPR.0.4.1.10 — persist the audit record for a --dangerously-interact prompt override. Audit-all-
2223
- // or-nothing: if there is no eventBus, the target rig/node can't be resolved, or the event cannot be
2224
- // persisted, return !ok so the caller fails closed and does NOT send. Payload keeps the caller's
2225
- // overrideReason distinct from the classifier's detectedReason/evidenceSource (not overloaded).
3167
+ // Record actor/reason with the actual readiness, never a fabricated prompt classification.
3168
+ // Report an unavailable sink to the caller: a positive-picker override still fails closed,
3169
+ // while other readiness states keep their existing accepted-send behavior with an advisory.
2226
3170
  recordPromptOverride(input) {
2227
3171
  if (!this.eventBus)
2228
3172
  return { ok: false, reason: "audit_unconfigured" };
@@ -2247,6 +3191,9 @@ export class SessionTransport {
2247
3191
  return { ok: false, reason: "audit_persist_failed" };
2248
3192
  }
2249
3193
  }
3194
+ /** `inputBox`: also describe a Claude Code or Codex seat's live input box (three more tmux reads), for callers that
3195
+ * use it: the capture route asks when its request does (`rig capture`, the MCP tool). `inputBoxBudget` is the time
3196
+ * those reads may still take, shared across a fan-out; a single capture gets its own CAPTURE_INPUT_BOX_DEADLINE_MS. */
2250
3197
  async capture(sessionName, opts) {
2251
3198
  const sessionMeta = this.getSessionMeta(sessionName);
2252
3199
  if (sessionMeta.attachmentType === "external_cli") {
@@ -2299,7 +3246,48 @@ export class SessionTransport {
2299
3246
  error: `Could not capture pane content for '${sessionName}'.`,
2300
3247
  };
2301
3248
  }
2302
- return { ok: true, sessionName, content, lines };
3249
+ await this.reconcileStartupReadiness(sessionName).catch(() => { });
3250
+ const runtime = sessionMeta.runtime;
3251
+ const inputBox = opts?.inputBox && (runtime === "claude-code" || runtime === "codex")
3252
+ ? await this.captureInputBox(sessionName, runtime, opts.inputBoxBudget ?? { remainingMs: CAPTURE_INPUT_BOX_DEADLINE_MS })
3253
+ : undefined;
3254
+ return { ok: true, sessionName, content, lines, ...(inputBox ? { inputBox } : {}) };
3255
+ }
3256
+ /** The input box, or unknown when its reads don't finish within the budget's remaining time (or none is left): the
3257
+ * pane text is already in hand, so a slow read never holds the capture. The time spent comes off the budget. A read
3258
+ * still pending then settles on its own, unused. */
3259
+ async captureInputBox(sessionName, runtime, budget) {
3260
+ if (budget.remainingMs <= 0)
3261
+ return annotateInputBox(null, null, runtime, { budgetSpent: true });
3262
+ const started = this.now().getTime();
3263
+ let timer;
3264
+ const late = new Promise((resolve) => {
3265
+ timer = setTimeout(() => resolve(annotateInputBox(null, null, runtime, { timedOut: true })), budget.remainingMs);
3266
+ });
3267
+ try {
3268
+ return await Promise.race([this.readInputBoxAnnotation(sessionName, runtime), late]);
3269
+ }
3270
+ finally {
3271
+ clearTimeout(timer);
3272
+ budget.remainingMs -= this.now().getTime() - started;
3273
+ }
3274
+ }
3275
+ /** One styled read of the visible screen with the cursor read before and after it: a cursor that moved, or a pane
3276
+ * that resized, in between is reported as moved; when only one read succeeds, its reading is passed on as seen once.
3277
+ * Never throws. */
3278
+ async readInputBoxAnnotation(sessionName, runtime) {
3279
+ try {
3280
+ const before = await this.tmuxAdapter.getPaneCursorPosition(sessionName);
3281
+ const screen = await this.tmuxAdapter.capturePaneContent(sessionName, 0, { escapeSequences: true });
3282
+ const after = await this.tmuxAdapter.getPaneCursorPosition(sessionName);
3283
+ const steady = before && after && before.x === after.x && before.y === after.y
3284
+ && before.width === after.width && before.height === after.height ? after : null;
3285
+ const cursorSeenOnce = (before === null) !== (after === null) ? before ?? after : null;
3286
+ return annotateInputBox(screen, steady, runtime, { cursorMoved: before !== null && after !== null && steady === null, cursorSeenOnce });
3287
+ }
3288
+ catch {
3289
+ return annotateInputBox(null, null, runtime);
3290
+ }
2303
3291
  }
2304
3292
  async broadcast(target, text, opts) {
2305
3293
  const resolved = await this.resolveSessions(target);
@@ -2352,5 +3340,295 @@ export class SessionTransport {
2352
3340
  results,
2353
3341
  };
2354
3342
  }
3343
+ getReconciliationSnapshot(sessionName) {
3344
+ return this.db.prepare(`
3345
+ SELECT n.id as node_id, r.id as rig_id, s.id as session_id, s.session_name, n.runtime, n.cwd,
3346
+ s.startup_status, s.status as session_status, s.startup_completed_at,
3347
+ s.resume_type, s.resume_token,
3348
+ b.tmux_session as binding_session, b.tmux_window as binding_window, b.tmux_pane as binding_pane,
3349
+ b.updated_at as binding_updated_at,
3350
+ (SELECT generation_uuid FROM occupant_tenures WHERE node_id = n.id ORDER BY generation_ordinal DESC LIMIT 1) as occupant_generation,
3351
+ (SELECT id FROM sessions WHERE node_id = n.id ORDER BY id DESC LIMIT 1) as latest_session_id,
3352
+ (SELECT MAX(seq) FROM events e WHERE e.node_id = n.id AND e.type IN ('node.startup_pending', 'node.startup_ready', 'node.startup_failed')) as startup_event_seq
3353
+ FROM sessions s
3354
+ JOIN nodes n ON n.id = s.node_id
3355
+ JOIN rigs r ON r.id = n.rig_id
3356
+ LEFT JOIN bindings b ON b.node_id = n.id
3357
+ WHERE s.session_name = ?
3358
+ ORDER BY s.id DESC LIMIT 1
3359
+ `).get(sessionName);
3360
+ }
3361
+ /**
3362
+ * Reconcile non-ready startup state after successful session interaction (Refs #7).
3363
+ *
3364
+ * If an active session is in a non-ready startup state (`failed` or `attention_required`),
3365
+ * re-check readiness via the runtime adapter with currentness verification and atomic commit.
3366
+ */
3367
+ async reconcileStartupReadiness(sessionName) {
3368
+ const existing = this.inFlightReconciliations.get(sessionName);
3369
+ if (existing)
3370
+ return existing;
3371
+ const promise = this.doReconcileStartupReadiness(sessionName);
3372
+ this.inFlightReconciliations.set(sessionName, promise);
3373
+ try {
3374
+ return await promise;
3375
+ }
3376
+ finally {
3377
+ this.inFlightReconciliations.delete(sessionName);
3378
+ }
3379
+ }
3380
+ async doReconcileStartupReadiness(sessionName) {
3381
+ const snapshot = this.getReconciliationSnapshot(sessionName);
3382
+ if (!snapshot)
3383
+ return false;
3384
+ if (snapshot.startup_status !== "failed" && snapshot.startup_status !== "attention_required")
3385
+ return false;
3386
+ if (snapshot.session_status !== "running")
3387
+ return false;
3388
+ if (snapshot.session_id !== snapshot.latest_session_id)
3389
+ return false;
3390
+ if (!isRecoverableStartupFailure(this.db, snapshot.node_id, snapshot.session_id))
3391
+ return false;
3392
+ const checker = this.runtimeAdapters[snapshot.runtime];
3393
+ if (!checker?.checkReady)
3394
+ return false;
3395
+ const hasClaudeIdentityWork = (nodeId, sessionId, runtime) => {
3396
+ if (runtime !== "claude-code")
3397
+ return false;
3398
+ const failedEvent = getLatestStartupFailedEvent(this.db, nodeId, sessionId);
3399
+ if (!failedEvent || failedEvent.requestedResumeToken === undefined || failedEvent.requestedResumeType === undefined) {
3400
+ return true;
3401
+ }
3402
+ return hasClaudeResumeIdentityWork(this.sessionRegistry, sessionId, runtime, failedEvent.requestedResumeToken, failedEvent.requestedResumeType);
3403
+ };
3404
+ const probeBinding = {
3405
+ id: `session-transport:${snapshot.session_id}`,
3406
+ nodeId: snapshot.node_id,
3407
+ attachmentType: "tmux",
3408
+ tmuxSession: sessionName,
3409
+ tmuxWindow: null,
3410
+ tmuxPane: null,
3411
+ cmuxWorkspace: null,
3412
+ cmuxSurface: null,
3413
+ updatedAt: "",
3414
+ cwd: snapshot.cwd ?? "",
3415
+ };
3416
+ let timeoutTimer;
3417
+ const timeoutPromise = new Promise((_, reject) => {
3418
+ timeoutTimer = setTimeout(() => reject(new Error("readiness probe timeout")), 5000);
3419
+ });
3420
+ let readiness;
3421
+ try {
3422
+ readiness = await Promise.race([checker.checkReady(probeBinding), timeoutPromise]);
3423
+ }
3424
+ catch {
3425
+ return false;
3426
+ }
3427
+ finally {
3428
+ if (timeoutTimer)
3429
+ clearTimeout(timeoutTimer);
3430
+ }
3431
+ let emittedReady = false;
3432
+ this.db.transaction(() => {
3433
+ const current = this.getReconciliationSnapshot(sessionName);
3434
+ if (!current || JSON.stringify(current) !== JSON.stringify(snapshot))
3435
+ return;
3436
+ if (!isRecoverableStartupFailure(this.db, snapshot.node_id, snapshot.session_id))
3437
+ return;
3438
+ const currentIdentityWork = hasClaudeIdentityWork(current.node_id, current.session_id, current.runtime);
3439
+ if (readiness.ready) {
3440
+ if (currentIdentityWork) {
3441
+ this.db.prepare(`
3442
+ UPDATE sessions
3443
+ SET startup_status = 'attention_required'
3444
+ WHERE id = ? AND startup_status = 'failed'
3445
+ `).run(snapshot.session_id);
3446
+ return;
3447
+ }
3448
+ const result = this.db.prepare(`
3449
+ UPDATE sessions
3450
+ SET startup_status = 'ready',
3451
+ startup_completed_at = ?
3452
+ WHERE id = ? AND startup_status IN ('failed', 'attention_required')
3453
+ `).run(new Date().toISOString(), snapshot.session_id);
3454
+ if (result.changes > 0) {
3455
+ emittedReady = true;
3456
+ }
3457
+ return;
3458
+ }
3459
+ if (isAttentionRequiredReadinessCode(readiness.code)) {
3460
+ this.db.prepare(`
3461
+ UPDATE sessions
3462
+ SET startup_status = 'attention_required'
3463
+ WHERE id = ? AND startup_status = 'failed'
3464
+ `).run(snapshot.session_id);
3465
+ }
3466
+ }).immediate();
3467
+ if (emittedReady && this.eventBus) {
3468
+ this.eventBus.emit({
3469
+ type: "node.startup_ready",
3470
+ rigId: snapshot.rig_id,
3471
+ nodeId: snapshot.node_id,
3472
+ previousStatus: snapshot.startup_status,
3473
+ });
3474
+ return true;
3475
+ }
3476
+ return false;
3477
+ }
3478
+ }
3479
+ const INPUT_CONTINUATION_COLUMN = 2; // Claude Code and Codex indent an input's later rows by two cells
3480
+ // How long a capture's input-box reads may take in all before the box reports unknown: one seat's three reads, or a
3481
+ // whole fan-out's (the send path's INPUT_BOX_READ_DEADLINE_MS bounds one read each).
3482
+ export const CAPTURE_INPUT_BOX_DEADLINE_MS = 1_000;
3483
+ // Claude Code's input frame rule, as findClaudeComposer and the composer-boundary reader match it (a label may sit in it).
3484
+ const CLAUDE_FRAME_RULE = /^─{3,}(?: .+ ─+)?$/;
3485
+ /** The input box's rows on the screen: Claude Code's prompt row inside its frame (the opening rule directly above it,
3486
+ * through the row before the closing rule), or Codex's box as codexInputBox reads it. A committed prompt row in the
3487
+ * transcript, or a prompt-looking line in output, has no opening rule above it. Null when no input box is recognized
3488
+ * (a selector, a menu instead of a box, busy output with no box, another layout). */
3489
+ function inputBoxRows(lines, runtime) {
3490
+ if (runtime === "codex") {
3491
+ const box = codexInputBox(lines.join("\n"));
3492
+ if (!box)
3493
+ return null;
3494
+ return { first: box.inputAt, last: box.inputAt + Math.max(box.rows.length, 1) - 1, empty: box.text === "" };
3495
+ }
3496
+ const promptClass = composerPromptClassForRuntime("claude-code");
3497
+ const first = findComposerInputLineIndex(lines, promptClass);
3498
+ if (first < 1 || composerNumberedChoicePattern(promptClass).test(lines[first].trimStart()))
3499
+ return null;
3500
+ // A rule at the prompt's own indent: an input's later rows are indented, so a rule typed inside it never closes it.
3501
+ const indent = /^[ \t]*/.exec(lines[first])[0];
3502
+ const frame = (line) => line.startsWith(indent) && CLAUDE_FRAME_RULE.test(line.slice(indent.length).trimEnd());
3503
+ if (!frame(lines[first - 1]))
3504
+ return null;
3505
+ let close = first + 1;
3506
+ while (close < lines.length && !frame(lines[close]))
3507
+ close++;
3508
+ return close < lines.length ? { first, last: close - 1 } : null;
3509
+ }
3510
+ export function annotateInputBox(styledScreen, cursor, runtime, opts = {}) {
3511
+ const harness = runtime === "codex" ? "Codex" : "Claude Code";
3512
+ const result = (status, confidence, hint, input = null, suggestion = null, evidence = []) => ({ status, confidence, cursor, input, suggestion, evidence, hint });
3513
+ const budget = `${CAPTURE_INPUT_BOX_DEADLINE_MS / 1000} s`;
3514
+ if (opts.timedOut) {
3515
+ return result("unknown", "unknown", `The input box's reads didn't finish within this capture's ${budget} budget, so it wasn't checked.`, null, null, ["input box reads timed out"]);
3516
+ }
3517
+ if (opts.budgetSpent) {
3518
+ return result("unknown", "unknown", `The input box wasn't read: this capture's ${budget} budget for input-box reads was spent on earlier seats.`, null, null, ["input box budget spent"]);
3519
+ }
3520
+ if (!styledScreen?.trim())
3521
+ return result("unknown", "unknown", "The screen couldn't be read, so the input box wasn't checked.");
3522
+ const cells = styledLines(styledScreen);
3523
+ const lines = cells.map((row) => row.map((cell) => cell.ch).join(""));
3524
+ const box = inputBoxRows(lines, runtime);
3525
+ if (!box)
3526
+ return result("no-input-box", "unknown", `No ${harness} input box was recognized on the screen.`);
3527
+ // Each box row's text cells with their screen columns. The first row's text starts after the prompt glyph and its
3528
+ // space, a later row's after its two-cell indent.
3529
+ const rows = [];
3530
+ for (let row = box.first; row <= box.last; row++) {
3531
+ let col = 0;
3532
+ const placed = (cells[row] ?? []).map((cell) => { const at = col; col += stringWidth(cell.ch); return { ...cell, col: at }; });
3533
+ const glyphAt = row === box.first ? placed.findIndex((cell) => cell.ch === "❯" || cell.ch === "›") : -1;
3534
+ const startCol = row === box.first ? (glyphAt >= 0 ? placed[glyphAt].col + 2 : INPUT_CONTINUATION_COLUMN) : INPUT_CONTINUATION_COLUMN;
3535
+ let text = placed.filter((cell) => cell.col >= startCol);
3536
+ while (text.length && !text[text.length - 1].ch.trim())
3537
+ text = text.slice(0, -1);
3538
+ rows.push({ row, cells: text });
3539
+ }
3540
+ const startCol = rows[0].cells[0]?.col ?? (lines[box.first].search(/[❯›]/) + 2);
3541
+ const spanOf = (from, to) => ({
3542
+ startRow: from.row, startCol: from.col, endRow: to.row, endCol: to.col,
3543
+ text: rows.filter((r) => r.row >= from.row && r.row <= to.row)
3544
+ .map((r) => r.cells.filter((cell) => (r.row > from.row || cell.col >= from.col) && (r.row < to.row || cell.col < to.col)).map((cell) => cell.ch).join(""))
3545
+ .join("\n"),
3546
+ });
3547
+ const textRows = rows.filter((r) => r.cells.length > 0);
3548
+ const outsideBox = (at) => at.y < box.first || at.y > box.last;
3549
+ // A cursor seen outside the box by the one read that worked still contradicts the box being the live one.
3550
+ const seenOutside = cursor === null && !opts.cursorMoved && opts.cursorSeenOnce != null && outsideBox(opts.cursorSeenOnce);
3551
+ const seenOutsideEvidence = "cursor outside the input box (the other cursor read failed)";
3552
+ if (box.empty || textRows.length === 0) {
3553
+ // Only a steady cursor in the box shows it's the live one: an empty frame drawn in output has the cursor below it, a
3554
+ // moved cursor means the box may have changed, and a missing cursor shows nothing either way.
3555
+ if (cursor !== null && !outsideBox(cursor))
3556
+ return result("empty", "high", "The input box is empty.", null, null, ["no text in the input box"]);
3557
+ if (cursor !== null || seenOutside) {
3558
+ return result("unknown", "unknown", `An empty ${harness} input box was recognized, but the cursor is outside it, so it may not be the live box.`, null, null, ["no text in the input box", cursor !== null ? "cursor outside the input box" : seenOutsideEvidence]);
3559
+ }
3560
+ if (opts.cursorMoved) {
3561
+ return result("unknown", "unknown", `The ${harness} input box read empty, but the cursor moved during the capture, so it may have changed.`, null, null, ["no text in the input box", "cursor moved during the capture"]);
3562
+ }
3563
+ return result("unknown", "unknown", `An empty ${harness} input box was recognized, but the cursor couldn't be read, so it may not be the live box.`, null, null, ["no text in the input box", "cursor unavailable"]);
3564
+ }
3565
+ const lastRow = textRows[textRows.length - 1];
3566
+ const lastCell = lastRow.cells[lastRow.cells.length - 1];
3567
+ const end = { row: lastRow.row, col: lastCell.col + stringWidth(lastCell.ch) };
3568
+ const input = spanOf({ row: box.first, col: startCol }, end);
3569
+ const quote = (text) => {
3570
+ const flat = text.replace(/\s*\n\s*/g, " ").trim();
3571
+ return `"${flat.length > 60 ? `${flat.slice(0, 57)}...` : flat}"`;
3572
+ };
3573
+ const styled = /\x1b\[/.test(styledScreen);
3574
+ const textCells = rows.flatMap((r) => r.cells.filter((cell) => cell.ch.trim()).map((cell) => ({ ...cell, row: r.row })));
3575
+ const allFaint = styled && textCells.every((cell) => cell.faint);
3576
+ const noneFaint = styled && textCells.every((cell) => !cell.faint);
3577
+ // Claude Code's `Try "…"` words are a person's possible words too, so they count only where there's no styling to go
3578
+ // on, and at most as a possible (medium) suggestion. With styling, the faint text decides.
3579
+ const tryShape = runtime === "claude-code" && CLAUDE_TRY_SUGGESTION.test(input.text.replace(/\s+/g, ""));
3580
+ const trySuggestion = tryShape && !styled;
3581
+ const evidence = [];
3582
+ if (tryShape)
3583
+ evidence.push("Claude Code's Try \"...\" words");
3584
+ evidence.push(!styled ? "no styling in the capture" : allFaint ? "text drawn faint" : noneFaint ? "text not faint" : "part of the text drawn faint");
3585
+ const inBox = cursor !== null && cursor.y >= box.first && cursor.y <= box.last;
3586
+ const atStart = inBox && cursor.y === box.first && cursor.x === startCol;
3587
+ const atEnd = inBox && cursor.y === end.row && cursor.x === end.col;
3588
+ if (cursor === null)
3589
+ evidence.unshift(opts.cursorMoved ? "cursor moved during the capture" : seenOutside ? seenOutsideEvidence : "cursor unavailable");
3590
+ else
3591
+ evidence.unshift(!inBox ? "cursor outside the input box" : atStart ? "cursor at the input's start" : atEnd ? "cursor after the text" : "cursor inside the text");
3592
+ const why = evidence.join("; ");
3593
+ const likelySuggestion = (confidence) => result("likely-suggestion", confidence, confidence === "high"
3594
+ ? `The input box shows ${quote(input.text)}, likely a ${harness} suggestion (${why}), not something the person typed: it isn't their intent, a draft or a staged command.`
3595
+ : `The input box shows ${quote(input.text)}, possibly a ${harness} suggestion (${why}; medium confidence): don't take it as the person's intent, and don't assume it isn't their draft.`, input, input, evidence);
3596
+ const typed = (confidence) => result("typed", confidence, `The input box holds ${quote(input.text)}, likely typed by the person and not yet sent (${why}).`, input, null, evidence);
3597
+ const unknown = () => result("unknown", "unknown", `The input box shows ${quote(input.text)}; whether the person typed it or it's a ${harness} suggestion is unknown (${why}).`, input, null, evidence);
3598
+ // A moved cursor or one seen outside the box contradicts any reading; only an unavailable cursor leaves the styling or
3599
+ // the Try words to go on, at medium confidence.
3600
+ if (cursor === null || !inBox) {
3601
+ if (cursor === null && !opts.cursorMoved && !seenOutside && (allFaint || trySuggestion))
3602
+ return likelySuggestion("medium");
3603
+ return unknown();
3604
+ }
3605
+ if (atStart) {
3606
+ if (allFaint)
3607
+ return likelySuggestion("high");
3608
+ if (!styled)
3609
+ return likelySuggestion("medium");
3610
+ // Claude Code draws its suggestion faint (Codex draws its placeholder faint too); unfaint text with the cursor at its
3611
+ // start may be a draft the person moved the cursor back through, in either harness.
3612
+ return unknown();
3613
+ }
3614
+ if (atEnd) {
3615
+ if (noneFaint)
3616
+ return typed("high");
3617
+ if (!styled)
3618
+ return typed("medium");
3619
+ return unknown();
3620
+ }
3621
+ // The cursor is inside the text: the person moved it, or typed a prefix and the harness suggests the rest after it.
3622
+ const split = textCells.findIndex((cell) => cell.row > cursor.y || (cell.row === cursor.y && cell.col >= cursor.x));
3623
+ const before = split < 0 ? textCells : textCells.slice(0, split);
3624
+ const after = split < 0 ? [] : textCells.slice(split);
3625
+ if (styled && before.length > 0 && after.length > 0 && before.every((cell) => !cell.faint) && after.every((cell) => cell.faint)) {
3626
+ const suggestion = spanOf({ row: cursor.y, col: cursor.x }, end);
3627
+ const typedPart = spanOf({ row: box.first, col: startCol }, { row: cursor.y, col: cursor.x });
3628
+ return result("typed-with-suggestion", "high", `The input box holds ${quote(typedPart.text)}, likely typed by the person, then ${quote(suggestion.text)}, likely a ${harness} suggestion after the cursor (${why}; the text after the cursor is faint): the suggestion isn't their intent.`, input, suggestion, [...evidence, "the text after the cursor is faint"]);
3629
+ }
3630
+ if (noneFaint)
3631
+ return typed("medium");
3632
+ return unknown();
2355
3633
  }
2356
3634
  //# sourceMappingURL=session-transport.js.map