@openrig/cli 0.6.10 → 0.6.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1275) hide show
  1. package/README.md +7 -4
  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 +30 -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 +77 -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 +69 -16
  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 +10 -1
  17. package/daemon/assets/plugins/openrig-core/skills/openrig-skills/SKILL.md +2 -2
  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 +6 -1
  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 +56 -10
  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 +131 -37
  28. package/daemon/context-packs/reference/getting-started.md +39 -6
  29. package/daemon/context-packs/reference/manifest.yaml +1 -1
  30. package/daemon/context-packs/reference/rig-spec.md +66 -5
  31. package/daemon/context-packs/reference/whats-new.md +169 -147
  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 +2 -2
  46. package/daemon/context-packs/skills/core/openrig-skills/manifest.yaml +3 -3
  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 +10 -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 +6 -1
  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 +17 -15
  106. package/daemon/dist/adapters/claude-code-adapter.d.ts.map +1 -1
  107. package/daemon/dist/adapters/claude-code-adapter.js +200 -105
  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 +73 -1
  117. package/daemon/dist/adapters/codex-runtime-adapter.d.ts.map +1 -1
  118. package/daemon/dist/adapters/codex-runtime-adapter.js +230 -48
  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 +4 -0
  144. package/daemon/dist/adapters/pi-resume.d.ts.map +1 -1
  145. package/daemon/dist/adapters/pi-resume.js +2 -0
  146. package/daemon/dist/adapters/pi-resume.js.map +1 -1
  147. package/daemon/dist/adapters/pi-runner-protocol.d.ts +4 -0
  148. package/daemon/dist/adapters/pi-runner-protocol.d.ts.map +1 -1
  149. package/daemon/dist/adapters/pi-runner-protocol.js +5 -0
  150. package/daemon/dist/adapters/pi-runner-protocol.js.map +1 -1
  151. package/daemon/dist/adapters/pi-runner.d.ts +67 -2
  152. package/daemon/dist/adapters/pi-runner.d.ts.map +1 -1
  153. package/daemon/dist/adapters/pi-runner.js +336 -13
  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 +18 -1
  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/tmux-control.d.ts +119 -0
  168. package/daemon/dist/adapters/tmux-control.d.ts.map +1 -0
  169. package/daemon/dist/adapters/tmux-control.js +547 -0
  170. package/daemon/dist/adapters/tmux-control.js.map +1 -0
  171. package/daemon/dist/adapters/tmux-exec.d.ts +18 -4
  172. package/daemon/dist/adapters/tmux-exec.d.ts.map +1 -1
  173. package/daemon/dist/adapters/tmux-exec.js +98 -17
  174. package/daemon/dist/adapters/tmux-exec.js.map +1 -1
  175. package/daemon/dist/adapters/tmux.d.ts +75 -15
  176. package/daemon/dist/adapters/tmux.d.ts.map +1 -1
  177. package/daemon/dist/adapters/tmux.js +352 -57
  178. package/daemon/dist/adapters/tmux.js.map +1 -1
  179. package/daemon/dist/build-info.js +3 -3
  180. package/daemon/dist/composer-prompts-surface.d.ts +2 -0
  181. package/daemon/dist/composer-prompts-surface.d.ts.map +1 -0
  182. package/daemon/dist/composer-prompts-surface.js +2 -0
  183. package/daemon/dist/composer-prompts-surface.js.map +1 -0
  184. package/daemon/dist/db/all-migrations.d.ts.map +1 -1
  185. package/daemon/dist/db/all-migrations.js +26 -0
  186. package/daemon/dist/db/all-migrations.js.map +1 -1
  187. package/daemon/dist/db/migrations/100_typing_guard_modes.d.ts +3 -0
  188. package/daemon/dist/db/migrations/100_typing_guard_modes.d.ts.map +1 -0
  189. package/daemon/dist/db/migrations/100_typing_guard_modes.js +10 -0
  190. package/daemon/dist/db/migrations/100_typing_guard_modes.js.map +1 -0
  191. package/daemon/dist/db/migrations/103_session_stop_intent.d.ts +4 -0
  192. package/daemon/dist/db/migrations/103_session_stop_intent.d.ts.map +1 -0
  193. package/daemon/dist/db/migrations/103_session_stop_intent.js +9 -0
  194. package/daemon/dist/db/migrations/103_session_stop_intent.js.map +1 -0
  195. package/daemon/dist/db/migrations/104_node_effort_choice.d.ts +3 -0
  196. package/daemon/dist/db/migrations/104_node_effort_choice.d.ts.map +1 -0
  197. package/daemon/dist/db/migrations/104_node_effort_choice.js +10 -0
  198. package/daemon/dist/db/migrations/104_node_effort_choice.js.map +1 -0
  199. package/daemon/dist/db/migrations/105_usage_samples_client_cost.d.ts +9 -0
  200. package/daemon/dist/db/migrations/105_usage_samples_client_cost.d.ts.map +1 -0
  201. package/daemon/dist/db/migrations/105_usage_samples_client_cost.js +14 -0
  202. package/daemon/dist/db/migrations/105_usage_samples_client_cost.js.map +1 -0
  203. package/daemon/dist/db/migrations/107_pi_usage_entries.d.ts +10 -0
  204. package/daemon/dist/db/migrations/107_pi_usage_entries.d.ts.map +1 -0
  205. package/daemon/dist/db/migrations/107_pi_usage_entries.js +38 -0
  206. package/daemon/dist/db/migrations/107_pi_usage_entries.js.map +1 -0
  207. package/daemon/dist/db/migrations/108_pi_usage_charges.d.ts +12 -0
  208. package/daemon/dist/db/migrations/108_pi_usage_charges.d.ts.map +1 -0
  209. package/daemon/dist/db/migrations/108_pi_usage_charges.js +39 -0
  210. package/daemon/dist/db/migrations/108_pi_usage_charges.js.map +1 -0
  211. package/daemon/dist/db/migrations/109_mode_posture_coexist.d.ts +5 -0
  212. package/daemon/dist/db/migrations/109_mode_posture_coexist.d.ts.map +1 -0
  213. package/daemon/dist/db/migrations/109_mode_posture_coexist.js +11 -0
  214. package/daemon/dist/db/migrations/109_mode_posture_coexist.js.map +1 -0
  215. package/daemon/dist/db/migrations/110_workflow_instance_scope.d.ts +10 -0
  216. package/daemon/dist/db/migrations/110_workflow_instance_scope.d.ts.map +1 -0
  217. package/daemon/dist/db/migrations/110_workflow_instance_scope.js +14 -0
  218. package/daemon/dist/db/migrations/110_workflow_instance_scope.js.map +1 -0
  219. package/daemon/dist/db/migrations/111_seat_env.d.ts +6 -0
  220. package/daemon/dist/db/migrations/111_seat_env.d.ts.map +1 -0
  221. package/daemon/dist/db/migrations/111_seat_env.js +11 -0
  222. package/daemon/dist/db/migrations/111_seat_env.js.map +1 -0
  223. package/daemon/dist/db/migrations/112_queue_transitions_ts_qitem.d.ts +3 -0
  224. package/daemon/dist/db/migrations/112_queue_transitions_ts_qitem.d.ts.map +1 -0
  225. package/daemon/dist/db/migrations/112_queue_transitions_ts_qitem.js +14 -0
  226. package/daemon/dist/db/migrations/112_queue_transitions_ts_qitem.js.map +1 -0
  227. package/daemon/dist/db/migrations/113_applied_launch_daemon_opt_out.d.ts +3 -0
  228. package/daemon/dist/db/migrations/113_applied_launch_daemon_opt_out.d.ts.map +1 -0
  229. package/daemon/dist/db/migrations/113_applied_launch_daemon_opt_out.js +9 -0
  230. package/daemon/dist/db/migrations/113_applied_launch_daemon_opt_out.js.map +1 -0
  231. package/daemon/dist/db/migrations/114_session_agent_app.d.ts +3 -0
  232. package/daemon/dist/db/migrations/114_session_agent_app.d.ts.map +1 -0
  233. package/daemon/dist/db/migrations/114_session_agent_app.js +11 -0
  234. package/daemon/dist/db/migrations/114_session_agent_app.js.map +1 -0
  235. package/daemon/dist/db/migrations/115_thread_seat_map_channel_key.d.ts +3 -0
  236. package/daemon/dist/db/migrations/115_thread_seat_map_channel_key.d.ts.map +1 -0
  237. package/daemon/dist/db/migrations/115_thread_seat_map_channel_key.js +28 -0
  238. package/daemon/dist/db/migrations/115_thread_seat_map_channel_key.js.map +1 -0
  239. package/daemon/dist/domain/active-occupant.d.ts +14 -0
  240. package/daemon/dist/domain/active-occupant.d.ts.map +1 -1
  241. package/daemon/dist/domain/active-occupant.js +17 -0
  242. package/daemon/dist/domain/active-occupant.js.map +1 -1
  243. package/daemon/dist/domain/activity-taxonomy.d.ts +13 -5
  244. package/daemon/dist/domain/activity-taxonomy.d.ts.map +1 -1
  245. package/daemon/dist/domain/activity-taxonomy.js +14 -2
  246. package/daemon/dist/domain/activity-taxonomy.js.map +1 -1
  247. package/daemon/dist/domain/agent-activity-store.d.ts +2 -0
  248. package/daemon/dist/domain/agent-activity-store.d.ts.map +1 -1
  249. package/daemon/dist/domain/agent-activity-store.js +14 -8
  250. package/daemon/dist/domain/agent-activity-store.js.map +1 -1
  251. package/daemon/dist/domain/agent-images/agent-image-library-service.js +1 -1
  252. package/daemon/dist/domain/agent-images/agent-image-library-service.js.map +1 -1
  253. package/daemon/dist/domain/agent-images/manifest-parser.js +1 -1
  254. package/daemon/dist/domain/agent-images/manifest-parser.js.map +1 -1
  255. package/daemon/dist/domain/agent-manifest.d.ts.map +1 -1
  256. package/daemon/dist/domain/agent-manifest.js +21 -3
  257. package/daemon/dist/domain/agent-manifest.js.map +1 -1
  258. package/daemon/dist/domain/applied-launch-observation-store.d.ts +2 -0
  259. package/daemon/dist/domain/applied-launch-observation-store.d.ts.map +1 -1
  260. package/daemon/dist/domain/applied-launch-observation-store.js +27 -3
  261. package/daemon/dist/domain/applied-launch-observation-store.js.map +1 -1
  262. package/daemon/dist/domain/bootstrap-orchestrator.d.ts +3 -0
  263. package/daemon/dist/domain/bootstrap-orchestrator.d.ts.map +1 -1
  264. package/daemon/dist/domain/bootstrap-orchestrator.js +49 -16
  265. package/daemon/dist/domain/bootstrap-orchestrator.js.map +1 -1
  266. package/daemon/dist/domain/built-in-runtimes.d.ts +10 -0
  267. package/daemon/dist/domain/built-in-runtimes.d.ts.map +1 -0
  268. package/daemon/dist/domain/built-in-runtimes.js +14 -0
  269. package/daemon/dist/domain/built-in-runtimes.js.map +1 -0
  270. package/daemon/dist/domain/bundle-archive.d.ts +3 -1
  271. package/daemon/dist/domain/bundle-archive.d.ts.map +1 -1
  272. package/daemon/dist/domain/bundle-archive.js +5 -3
  273. package/daemon/dist/domain/bundle-archive.js.map +1 -1
  274. package/daemon/dist/domain/bundle-behaviour.d.ts +20 -0
  275. package/daemon/dist/domain/bundle-behaviour.d.ts.map +1 -1
  276. package/daemon/dist/domain/bundle-behaviour.js +57 -1
  277. package/daemon/dist/domain/bundle-behaviour.js.map +1 -1
  278. package/daemon/dist/domain/bundle-integrity.d.ts +5 -2
  279. package/daemon/dist/domain/bundle-integrity.d.ts.map +1 -1
  280. package/daemon/dist/domain/bundle-integrity.js +39 -8
  281. package/daemon/dist/domain/bundle-integrity.js.map +1 -1
  282. package/daemon/dist/domain/bundle-source-resolver.d.ts +3 -1
  283. package/daemon/dist/domain/bundle-source-resolver.d.ts.map +1 -1
  284. package/daemon/dist/domain/bundle-source-resolver.js +7 -2
  285. package/daemon/dist/domain/bundle-source-resolver.js.map +1 -1
  286. package/daemon/dist/domain/bundle-types.d.ts +6 -0
  287. package/daemon/dist/domain/bundle-types.d.ts.map +1 -1
  288. package/daemon/dist/domain/bundle-types.js +26 -0
  289. package/daemon/dist/domain/bundle-types.js.map +1 -1
  290. package/daemon/dist/domain/chat-repository.d.ts.map +1 -1
  291. package/daemon/dist/domain/chat-repository.js +3 -2
  292. package/daemon/dist/domain/chat-repository.js.map +1 -1
  293. package/daemon/dist/domain/claim-service.d.ts +6 -0
  294. package/daemon/dist/domain/claim-service.d.ts.map +1 -1
  295. package/daemon/dist/domain/claim-service.js +20 -1
  296. package/daemon/dist/domain/claim-service.js.map +1 -1
  297. package/daemon/dist/domain/claude-compaction-enforcer.d.ts +63 -2
  298. package/daemon/dist/domain/claude-compaction-enforcer.d.ts.map +1 -1
  299. package/daemon/dist/domain/claude-compaction-enforcer.js +154 -16
  300. package/daemon/dist/domain/claude-compaction-enforcer.js.map +1 -1
  301. package/daemon/dist/domain/claude-core-plugin-env.d.ts +26 -0
  302. package/daemon/dist/domain/claude-core-plugin-env.d.ts.map +1 -0
  303. package/daemon/dist/domain/claude-core-plugin-env.js +86 -0
  304. package/daemon/dist/domain/claude-core-plugin-env.js.map +1 -0
  305. package/daemon/dist/domain/claude-managed-launch.d.ts +13 -6
  306. package/daemon/dist/domain/claude-managed-launch.d.ts.map +1 -1
  307. package/daemon/dist/domain/claude-managed-launch.js +58 -5
  308. package/daemon/dist/domain/claude-managed-launch.js.map +1 -1
  309. package/daemon/dist/domain/codex-network-default.d.ts +3 -0
  310. package/daemon/dist/domain/codex-network-default.d.ts.map +1 -1
  311. package/daemon/dist/domain/codex-network-default.js +1 -1
  312. package/daemon/dist/domain/codex-network-default.js.map +1 -1
  313. package/daemon/dist/domain/codex-pane-patterns.d.ts +2 -0
  314. package/daemon/dist/domain/codex-pane-patterns.d.ts.map +1 -0
  315. package/daemon/dist/domain/codex-pane-patterns.js +5 -0
  316. package/daemon/dist/domain/codex-pane-patterns.js.map +1 -0
  317. package/daemon/dist/domain/codex-thread-id.d.ts +22 -3
  318. package/daemon/dist/domain/codex-thread-id.d.ts.map +1 -1
  319. package/daemon/dist/domain/codex-thread-id.js +65 -16
  320. package/daemon/dist/domain/codex-thread-id.js.map +1 -1
  321. package/daemon/dist/domain/community-harness-presets.d.ts +38 -0
  322. package/daemon/dist/domain/community-harness-presets.d.ts.map +1 -0
  323. package/daemon/dist/domain/community-harness-presets.js +155 -0
  324. package/daemon/dist/domain/community-harness-presets.js.map +1 -0
  325. package/daemon/dist/domain/composer-prompts.d.ts +118 -0
  326. package/daemon/dist/domain/composer-prompts.d.ts.map +1 -0
  327. package/daemon/dist/domain/composer-prompts.js +165 -0
  328. package/daemon/dist/domain/composer-prompts.js.map +1 -0
  329. package/daemon/dist/domain/context-monitor.d.ts +3 -0
  330. package/daemon/dist/domain/context-monitor.d.ts.map +1 -1
  331. package/daemon/dist/domain/context-monitor.js +59 -23
  332. package/daemon/dist/domain/context-monitor.js.map +1 -1
  333. package/daemon/dist/domain/context-packs/context-pack-library-service.js +2 -2
  334. package/daemon/dist/domain/context-packs/context-pack-library-service.js.map +1 -1
  335. package/daemon/dist/domain/context-packs/manifest-parser.js +1 -1
  336. package/daemon/dist/domain/context-packs/manifest-parser.js.map +1 -1
  337. package/daemon/dist/domain/context-usage-store.d.ts +4 -0
  338. package/daemon/dist/domain/context-usage-store.d.ts.map +1 -1
  339. package/daemon/dist/domain/context-usage-store.js +1 -0
  340. package/daemon/dist/domain/context-usage-store.js.map +1 -1
  341. package/daemon/dist/domain/current-work.js +2 -2
  342. package/daemon/dist/domain/current-work.js.map +1 -1
  343. package/daemon/dist/domain/cwd-resolution.d.ts +8 -0
  344. package/daemon/dist/domain/cwd-resolution.d.ts.map +1 -1
  345. package/daemon/dist/domain/cwd-resolution.js +24 -3
  346. package/daemon/dist/domain/cwd-resolution.js.map +1 -1
  347. package/daemon/dist/domain/draft-aware-delivery.d.ts +32 -0
  348. package/daemon/dist/domain/draft-aware-delivery.d.ts.map +1 -0
  349. package/daemon/dist/domain/draft-aware-delivery.js +156 -0
  350. package/daemon/dist/domain/draft-aware-delivery.js.map +1 -0
  351. package/daemon/dist/domain/event-bus.d.ts +10 -1
  352. package/daemon/dist/domain/event-bus.d.ts.map +1 -1
  353. package/daemon/dist/domain/event-bus.js +25 -0
  354. package/daemon/dist/domain/event-bus.js.map +1 -1
  355. package/daemon/dist/domain/execution-view.d.ts.map +1 -1
  356. package/daemon/dist/domain/execution-view.js +76 -10
  357. package/daemon/dist/domain/execution-view.js.map +1 -1
  358. package/daemon/dist/domain/frontmatter.d.ts +11 -0
  359. package/daemon/dist/domain/frontmatter.d.ts.map +1 -0
  360. package/daemon/dist/domain/frontmatter.js +20 -0
  361. package/daemon/dist/domain/frontmatter.js.map +1 -0
  362. package/daemon/dist/domain/gateway/gateway-subsystem.d.ts +11 -0
  363. package/daemon/dist/domain/gateway/gateway-subsystem.d.ts.map +1 -1
  364. package/daemon/dist/domain/gateway/gateway-subsystem.js +7 -0
  365. package/daemon/dist/domain/gateway/gateway-subsystem.js.map +1 -1
  366. package/daemon/dist/domain/gateway/human-registry.d.ts +6 -1
  367. package/daemon/dist/domain/gateway/human-registry.d.ts.map +1 -1
  368. package/daemon/dist/domain/gateway/human-registry.js +7 -0
  369. package/daemon/dist/domain/gateway/human-registry.js.map +1 -1
  370. package/daemon/dist/domain/gateway/slack/bot-inbox.d.ts +54 -0
  371. package/daemon/dist/domain/gateway/slack/bot-inbox.d.ts.map +1 -0
  372. package/daemon/dist/domain/gateway/slack/bot-inbox.js +86 -0
  373. package/daemon/dist/domain/gateway/slack/bot-inbox.js.map +1 -0
  374. package/daemon/dist/domain/gateway/slack/capabilities.d.ts.map +1 -1
  375. package/daemon/dist/domain/gateway/slack/capabilities.js +1 -0
  376. package/daemon/dist/domain/gateway/slack/capabilities.js.map +1 -1
  377. package/daemon/dist/domain/gateway/slack/channel-map.d.ts +11 -1
  378. package/daemon/dist/domain/gateway/slack/channel-map.d.ts.map +1 -1
  379. package/daemon/dist/domain/gateway/slack/channel-map.js +31 -6
  380. package/daemon/dist/domain/gateway/slack/channel-map.js.map +1 -1
  381. package/daemon/dist/domain/gateway/slack/channel-recovery.d.ts.map +1 -1
  382. package/daemon/dist/domain/gateway/slack/channel-recovery.js +4 -3
  383. package/daemon/dist/domain/gateway/slack/channel-recovery.js.map +1 -1
  384. package/daemon/dist/domain/gateway/slack/config.d.ts +14 -0
  385. package/daemon/dist/domain/gateway/slack/config.d.ts.map +1 -1
  386. package/daemon/dist/domain/gateway/slack/config.js +28 -0
  387. package/daemon/dist/domain/gateway/slack/config.js.map +1 -1
  388. package/daemon/dist/domain/gateway/slack/forwarding.d.ts +32 -0
  389. package/daemon/dist/domain/gateway/slack/forwarding.d.ts.map +1 -0
  390. package/daemon/dist/domain/gateway/slack/forwarding.js +76 -0
  391. package/daemon/dist/domain/gateway/slack/forwarding.js.map +1 -0
  392. package/daemon/dist/domain/gateway/slack/inbound.d.ts +99 -1
  393. package/daemon/dist/domain/gateway/slack/inbound.d.ts.map +1 -1
  394. package/daemon/dist/domain/gateway/slack/inbound.js +325 -7
  395. package/daemon/dist/domain/gateway/slack/inbound.js.map +1 -1
  396. package/daemon/dist/domain/gateway/slack/message.d.ts +28 -0
  397. package/daemon/dist/domain/gateway/slack/message.d.ts.map +1 -1
  398. package/daemon/dist/domain/gateway/slack/message.js +105 -4
  399. package/daemon/dist/domain/gateway/slack/message.js.map +1 -1
  400. package/daemon/dist/domain/gateway/slack/outbound-driver.d.ts +3 -0
  401. package/daemon/dist/domain/gateway/slack/outbound-driver.d.ts.map +1 -1
  402. package/daemon/dist/domain/gateway/slack/outbound-driver.js +1 -0
  403. package/daemon/dist/domain/gateway/slack/outbound-driver.js.map +1 -1
  404. package/daemon/dist/domain/gateway/slack/queue-access.d.ts +1 -1
  405. package/daemon/dist/domain/gateway/slack/queue-access.d.ts.map +1 -1
  406. package/daemon/dist/domain/gateway/slack/slack-api.d.ts +10 -0
  407. package/daemon/dist/domain/gateway/slack/slack-api.d.ts.map +1 -1
  408. package/daemon/dist/domain/gateway/slack/slack-api.js +13 -0
  409. package/daemon/dist/domain/gateway/slack/slack-api.js.map +1 -1
  410. package/daemon/dist/domain/gateway/slack/slack-delivery.d.ts +2 -0
  411. package/daemon/dist/domain/gateway/slack/slack-delivery.d.ts.map +1 -1
  412. package/daemon/dist/domain/gateway/slack/slack-delivery.js +12 -2
  413. package/daemon/dist/domain/gateway/slack/slack-delivery.js.map +1 -1
  414. package/daemon/dist/domain/gateway/slack/slack-subsystem.d.ts +3 -0
  415. package/daemon/dist/domain/gateway/slack/slack-subsystem.d.ts.map +1 -1
  416. package/daemon/dist/domain/gateway/slack/slack-subsystem.js +133 -20
  417. package/daemon/dist/domain/gateway/slack/slack-subsystem.js.map +1 -1
  418. package/daemon/dist/domain/gateway/slack/state-store.d.ts +9 -2
  419. package/daemon/dist/domain/gateway/slack/state-store.d.ts.map +1 -1
  420. package/daemon/dist/domain/gateway/slack/state-store.js +24 -2
  421. package/daemon/dist/domain/gateway/slack/state-store.js.map +1 -1
  422. package/daemon/dist/domain/gateway/slack/thread-routing.d.ts +3 -0
  423. package/daemon/dist/domain/gateway/slack/thread-routing.d.ts.map +1 -1
  424. package/daemon/dist/domain/gateway/slack/thread-routing.js +18 -7
  425. package/daemon/dist/domain/gateway/slack/thread-routing.js.map +1 -1
  426. package/daemon/dist/domain/gateway/slack/thread-seat-map.d.ts +17 -6
  427. package/daemon/dist/domain/gateway/slack/thread-seat-map.d.ts.map +1 -1
  428. package/daemon/dist/domain/gateway/slack/thread-seat-map.js +41 -23
  429. package/daemon/dist/domain/gateway/slack/thread-seat-map.js.map +1 -1
  430. package/daemon/dist/domain/history-query.d.ts.map +1 -1
  431. package/daemon/dist/domain/history-query.js +2 -1
  432. package/daemon/dist/domain/history-query.js.map +1 -1
  433. package/daemon/dist/domain/home-isolation.d.ts +18 -0
  434. package/daemon/dist/domain/home-isolation.d.ts.map +1 -0
  435. package/daemon/dist/domain/home-isolation.js +75 -0
  436. package/daemon/dist/domain/home-isolation.js.map +1 -0
  437. package/daemon/dist/domain/hosts/remote-daemon-http.d.ts +22 -0
  438. package/daemon/dist/domain/hosts/remote-daemon-http.d.ts.map +1 -1
  439. package/daemon/dist/domain/hosts/remote-daemon-http.js +43 -3
  440. package/daemon/dist/domain/hosts/remote-daemon-http.js.map +1 -1
  441. package/daemon/dist/domain/inbox-handler.d.ts.map +1 -1
  442. package/daemon/dist/domain/inbox-handler.js +34 -32
  443. package/daemon/dist/domain/inbox-handler.js.map +1 -1
  444. package/daemon/dist/domain/mission-control/notification-adapter-ntfy.js +12 -2
  445. package/daemon/dist/domain/mission-control/notification-adapter-ntfy.js.map +1 -1
  446. package/daemon/dist/domain/mission-control/notification-dispatcher.d.ts.map +1 -1
  447. package/daemon/dist/domain/mission-control/notification-dispatcher.js +2 -1
  448. package/daemon/dist/domain/mission-control/notification-dispatcher.js.map +1 -1
  449. package/daemon/dist/domain/model-divergence/current-generation-record.d.ts +63 -0
  450. package/daemon/dist/domain/model-divergence/current-generation-record.d.ts.map +1 -1
  451. package/daemon/dist/domain/model-divergence/current-generation-record.js +130 -1
  452. package/daemon/dist/domain/model-divergence/current-generation-record.js.map +1 -1
  453. package/daemon/dist/domain/model-divergence/effective-model-readers.d.ts +42 -0
  454. package/daemon/dist/domain/model-divergence/effective-model-readers.d.ts.map +1 -1
  455. package/daemon/dist/domain/model-divergence/effective-model-readers.js +160 -5
  456. package/daemon/dist/domain/model-divergence/effective-model-readers.js.map +1 -1
  457. package/daemon/dist/domain/model-divergence/model-divergence-monitor.d.ts +8 -2
  458. package/daemon/dist/domain/model-divergence/model-divergence-monitor.d.ts.map +1 -1
  459. package/daemon/dist/domain/model-divergence/model-divergence-monitor.js +10 -5
  460. package/daemon/dist/domain/model-divergence/model-divergence-monitor.js.map +1 -1
  461. package/daemon/dist/domain/native-process-lineage.d.ts +54 -0
  462. package/daemon/dist/domain/native-process-lineage.d.ts.map +1 -1
  463. package/daemon/dist/domain/native-process-lineage.js +181 -29
  464. package/daemon/dist/domain/native-process-lineage.js.map +1 -1
  465. package/daemon/dist/domain/native-resume-probe.d.ts +3 -1
  466. package/daemon/dist/domain/native-resume-probe.d.ts.map +1 -1
  467. package/daemon/dist/domain/native-resume-probe.js +43 -6
  468. package/daemon/dist/domain/native-resume-probe.js.map +1 -1
  469. package/daemon/dist/domain/node-inventory.d.ts.map +1 -1
  470. package/daemon/dist/domain/node-inventory.js +41 -16
  471. package/daemon/dist/domain/node-inventory.js.map +1 -1
  472. package/daemon/dist/domain/node-launcher.d.ts +18 -0
  473. package/daemon/dist/domain/node-launcher.d.ts.map +1 -1
  474. package/daemon/dist/domain/node-launcher.js +62 -3
  475. package/daemon/dist/domain/node-launcher.js.map +1 -1
  476. package/daemon/dist/domain/openrouter-charge.d.ts +42 -0
  477. package/daemon/dist/domain/openrouter-charge.d.ts.map +1 -0
  478. package/daemon/dist/domain/openrouter-charge.js +57 -0
  479. package/daemon/dist/domain/openrouter-charge.js.map +1 -0
  480. package/daemon/dist/domain/pane-cutover.d.ts +27 -0
  481. package/daemon/dist/domain/pane-cutover.d.ts.map +1 -0
  482. package/daemon/dist/domain/pane-cutover.js +37 -0
  483. package/daemon/dist/domain/pane-cutover.js.map +1 -0
  484. package/daemon/dist/domain/parent-session-env.d.ts +8 -0
  485. package/daemon/dist/domain/parent-session-env.d.ts.map +1 -0
  486. package/daemon/dist/domain/parent-session-env.js +37 -0
  487. package/daemon/dist/domain/parent-session-env.js.map +1 -0
  488. package/daemon/dist/domain/parked-query.d.ts +5 -0
  489. package/daemon/dist/domain/parked-query.d.ts.map +1 -1
  490. package/daemon/dist/domain/parked-query.js +9 -2
  491. package/daemon/dist/domain/parked-query.js.map +1 -1
  492. package/daemon/dist/domain/periodic-snapshot-scheduler.d.ts.map +1 -1
  493. package/daemon/dist/domain/periodic-snapshot-scheduler.js +27 -3
  494. package/daemon/dist/domain/periodic-snapshot-scheduler.js.map +1 -1
  495. package/daemon/dist/domain/permission-drift.d.ts +2 -0
  496. package/daemon/dist/domain/permission-drift.d.ts.map +1 -1
  497. package/daemon/dist/domain/permission-drift.js.map +1 -1
  498. package/daemon/dist/domain/permission-policy/policy-spec.d.ts.map +1 -1
  499. package/daemon/dist/domain/permission-policy/policy-spec.js +5 -7
  500. package/daemon/dist/domain/permission-policy/policy-spec.js.map +1 -1
  501. package/daemon/dist/domain/pi-usage-entries-store.d.ts +41 -0
  502. package/daemon/dist/domain/pi-usage-entries-store.d.ts.map +1 -0
  503. package/daemon/dist/domain/pi-usage-entries-store.js +173 -0
  504. package/daemon/dist/domain/pi-usage-entries-store.js.map +1 -0
  505. package/daemon/dist/domain/pi-usage-facts.d.ts +90 -0
  506. package/daemon/dist/domain/pi-usage-facts.d.ts.map +1 -0
  507. package/daemon/dist/domain/pi-usage-facts.js +165 -0
  508. package/daemon/dist/domain/pi-usage-facts.js.map +1 -0
  509. package/daemon/dist/domain/pod-bundle-assembler.d.ts +2 -0
  510. package/daemon/dist/domain/pod-bundle-assembler.d.ts.map +1 -1
  511. package/daemon/dist/domain/pod-bundle-assembler.js +4 -0
  512. package/daemon/dist/domain/pod-bundle-assembler.js.map +1 -1
  513. package/daemon/dist/domain/policies/artifact-pool-helpers.d.ts.map +1 -1
  514. package/daemon/dist/domain/policies/artifact-pool-helpers.js +6 -4
  515. package/daemon/dist/domain/policies/artifact-pool-helpers.js.map +1 -1
  516. package/daemon/dist/domain/policies/parked-owner-consumer.d.ts +11 -0
  517. package/daemon/dist/domain/policies/parked-owner-consumer.d.ts.map +1 -1
  518. package/daemon/dist/domain/policies/parked-owner-consumer.js +81 -11
  519. package/daemon/dist/domain/policies/parked-owner-consumer.js.map +1 -1
  520. package/daemon/dist/domain/profile-resolver.d.ts +2 -0
  521. package/daemon/dist/domain/profile-resolver.d.ts.map +1 -1
  522. package/daemon/dist/domain/profile-resolver.js +6 -3
  523. package/daemon/dist/domain/profile-resolver.js.map +1 -1
  524. package/daemon/dist/domain/progress/progress-indexer.js +1 -1
  525. package/daemon/dist/domain/progress/progress-indexer.js.map +1 -1
  526. package/daemon/dist/domain/project-lifecycle-compiler.d.ts +4 -0
  527. package/daemon/dist/domain/project-lifecycle-compiler.d.ts.map +1 -1
  528. package/daemon/dist/domain/project-lifecycle-compiler.js +63 -3
  529. package/daemon/dist/domain/project-lifecycle-compiler.js.map +1 -1
  530. package/daemon/dist/domain/proof/judgments.d.ts +10 -5
  531. package/daemon/dist/domain/proof/judgments.d.ts.map +1 -1
  532. package/daemon/dist/domain/proof/judgments.js +57 -20
  533. package/daemon/dist/domain/proof/judgments.js.map +1 -1
  534. package/daemon/dist/domain/provider/claude-auth-status-reader.d.ts +33 -0
  535. package/daemon/dist/domain/provider/claude-auth-status-reader.d.ts.map +1 -0
  536. package/daemon/dist/domain/provider/claude-auth-status-reader.js +195 -0
  537. package/daemon/dist/domain/provider/claude-auth-status-reader.js.map +1 -0
  538. package/daemon/dist/domain/provider/claude-usage-windows.d.ts +68 -0
  539. package/daemon/dist/domain/provider/claude-usage-windows.d.ts.map +1 -0
  540. package/daemon/dist/domain/provider/claude-usage-windows.js +260 -0
  541. package/daemon/dist/domain/provider/claude-usage-windows.js.map +1 -0
  542. package/daemon/dist/domain/provider/codex-auth-reader.d.ts +14 -0
  543. package/daemon/dist/domain/provider/codex-auth-reader.d.ts.map +1 -1
  544. package/daemon/dist/domain/provider/codex-auth-reader.js +41 -12
  545. package/daemon/dist/domain/provider/codex-auth-reader.js.map +1 -1
  546. package/daemon/dist/domain/provider/codex-limit-banner.d.ts +2 -2
  547. package/daemon/dist/domain/provider/codex-limit-banner.d.ts.map +1 -1
  548. package/daemon/dist/domain/provider/codex-limit-banner.js +56 -9
  549. package/daemon/dist/domain/provider/codex-limit-banner.js.map +1 -1
  550. package/daemon/dist/domain/provider/codex-usage-reader.d.ts +33 -0
  551. package/daemon/dist/domain/provider/codex-usage-reader.d.ts.map +1 -0
  552. package/daemon/dist/domain/provider/codex-usage-reader.js +117 -0
  553. package/daemon/dist/domain/provider/codex-usage-reader.js.map +1 -0
  554. package/daemon/dist/domain/provider/codex-usage-windows.d.ts +39 -0
  555. package/daemon/dist/domain/provider/codex-usage-windows.d.ts.map +1 -0
  556. package/daemon/dist/domain/provider/codex-usage-windows.js +101 -0
  557. package/daemon/dist/domain/provider/codex-usage-windows.js.map +1 -0
  558. package/daemon/dist/domain/provider/provider-seat-accounts.d.ts +18 -0
  559. package/daemon/dist/domain/provider/provider-seat-accounts.d.ts.map +1 -0
  560. package/daemon/dist/domain/provider/provider-seat-accounts.js +159 -0
  561. package/daemon/dist/domain/provider/provider-seat-accounts.js.map +1 -0
  562. package/daemon/dist/domain/provider/provider-service-impl.d.ts +15 -0
  563. package/daemon/dist/domain/provider/provider-service-impl.d.ts.map +1 -1
  564. package/daemon/dist/domain/provider/provider-service-impl.js +45 -1
  565. package/daemon/dist/domain/provider/provider-service-impl.js.map +1 -1
  566. package/daemon/dist/domain/provider/provider-service.d.ts +7 -0
  567. package/daemon/dist/domain/provider/provider-service.d.ts.map +1 -1
  568. package/daemon/dist/domain/provider/provider-types.d.ts +133 -0
  569. package/daemon/dist/domain/provider/provider-types.d.ts.map +1 -1
  570. package/daemon/dist/domain/provider/reactive-tap.js +2 -0
  571. package/daemon/dist/domain/provider/reactive-tap.js.map +1 -1
  572. package/daemon/dist/domain/provider/subscription-moves.d.ts +77 -0
  573. package/daemon/dist/domain/provider/subscription-moves.d.ts.map +1 -0
  574. package/daemon/dist/domain/provider/subscription-moves.js +140 -0
  575. package/daemon/dist/domain/provider/subscription-moves.js.map +1 -0
  576. package/daemon/dist/domain/provider/usage-across-hosts.d.ts +162 -0
  577. package/daemon/dist/domain/provider/usage-across-hosts.d.ts.map +1 -0
  578. package/daemon/dist/domain/provider/usage-across-hosts.js +563 -0
  579. package/daemon/dist/domain/provider/usage-across-hosts.js.map +1 -0
  580. package/daemon/dist/domain/provider/usage-windows.d.ts +88 -0
  581. package/daemon/dist/domain/provider/usage-windows.d.ts.map +1 -0
  582. package/daemon/dist/domain/provider/usage-windows.js +189 -0
  583. package/daemon/dist/domain/provider/usage-windows.js.map +1 -0
  584. package/daemon/dist/domain/ps-projection.d.ts +5 -2
  585. package/daemon/dist/domain/ps-projection.d.ts.map +1 -1
  586. package/daemon/dist/domain/ps-projection.js +27 -25
  587. package/daemon/dist/domain/ps-projection.js.map +1 -1
  588. package/daemon/dist/domain/queue-owner.d.ts +6 -0
  589. package/daemon/dist/domain/queue-owner.d.ts.map +1 -1
  590. package/daemon/dist/domain/queue-owner.js +22 -0
  591. package/daemon/dist/domain/queue-owner.js.map +1 -1
  592. package/daemon/dist/domain/queue-repository.d.ts +91 -14
  593. package/daemon/dist/domain/queue-repository.d.ts.map +1 -1
  594. package/daemon/dist/domain/queue-repository.js +347 -89
  595. package/daemon/dist/domain/queue-repository.js.map +1 -1
  596. package/daemon/dist/domain/queue-stuck-sweep.d.ts.map +1 -1
  597. package/daemon/dist/domain/queue-stuck-sweep.js +29 -27
  598. package/daemon/dist/domain/queue-stuck-sweep.js.map +1 -1
  599. package/daemon/dist/domain/queue-transition-log.d.ts +23 -2
  600. package/daemon/dist/domain/queue-transition-log.d.ts.map +1 -1
  601. package/daemon/dist/domain/queue-transition-log.js +98 -39
  602. package/daemon/dist/domain/queue-transition-log.js.map +1 -1
  603. package/daemon/dist/domain/queue-wake-ladder.d.ts +2 -2
  604. package/daemon/dist/domain/queue-wake-ladder.d.ts.map +1 -1
  605. package/daemon/dist/domain/queue-wake-ladder.js +27 -8
  606. package/daemon/dist/domain/queue-wake-ladder.js.map +1 -1
  607. package/daemon/dist/domain/queue-wake-repository.d.ts +7 -1
  608. package/daemon/dist/domain/queue-wake-repository.d.ts.map +1 -1
  609. package/daemon/dist/domain/queue-wake-repository.js +54 -5
  610. package/daemon/dist/domain/queue-wake-repository.js.map +1 -1
  611. package/daemon/dist/domain/reply-to-choice.d.ts +2 -0
  612. package/daemon/dist/domain/reply-to-choice.d.ts.map +1 -1
  613. package/daemon/dist/domain/reply-to-choice.js +2 -0
  614. package/daemon/dist/domain/reply-to-choice.js.map +1 -1
  615. package/daemon/dist/domain/restore-check-service.d.ts +8 -0
  616. package/daemon/dist/domain/restore-check-service.d.ts.map +1 -1
  617. package/daemon/dist/domain/restore-check-service.js +15 -5
  618. package/daemon/dist/domain/restore-check-service.js.map +1 -1
  619. package/daemon/dist/domain/restore-orchestrator.d.ts +4 -0
  620. package/daemon/dist/domain/restore-orchestrator.d.ts.map +1 -1
  621. package/daemon/dist/domain/restore-orchestrator.js +111 -18
  622. package/daemon/dist/domain/restore-orchestrator.js.map +1 -1
  623. package/daemon/dist/domain/restore-plan-preview.d.ts +4 -0
  624. package/daemon/dist/domain/restore-plan-preview.d.ts.map +1 -1
  625. package/daemon/dist/domain/restore-plan-preview.js +35 -20
  626. package/daemon/dist/domain/restore-plan-preview.js.map +1 -1
  627. package/daemon/dist/domain/restore-policy.d.ts +26 -0
  628. package/daemon/dist/domain/restore-policy.d.ts.map +1 -0
  629. package/daemon/dist/domain/restore-policy.js +22 -0
  630. package/daemon/dist/domain/restore-policy.js.map +1 -0
  631. package/daemon/dist/domain/restore-preconditions.d.ts +2 -0
  632. package/daemon/dist/domain/restore-preconditions.d.ts.map +1 -1
  633. package/daemon/dist/domain/restore-preconditions.js +6 -1
  634. package/daemon/dist/domain/restore-preconditions.js.map +1 -1
  635. package/daemon/dist/domain/restore-runtime-compatibility.d.ts +3 -0
  636. package/daemon/dist/domain/restore-runtime-compatibility.d.ts.map +1 -0
  637. package/daemon/dist/domain/restore-runtime-compatibility.js +7 -0
  638. package/daemon/dist/domain/restore-runtime-compatibility.js.map +1 -0
  639. package/daemon/dist/domain/resume-metadata-refresher.d.ts +10 -5
  640. package/daemon/dist/domain/resume-metadata-refresher.d.ts.map +1 -1
  641. package/daemon/dist/domain/resume-metadata-refresher.js +109 -21
  642. package/daemon/dist/domain/resume-metadata-refresher.js.map +1 -1
  643. package/daemon/dist/domain/resume-token-capture.d.ts +1 -1
  644. package/daemon/dist/domain/resume-token-capture.d.ts.map +1 -1
  645. package/daemon/dist/domain/resume-token-capture.js +7 -1
  646. package/daemon/dist/domain/resume-token-capture.js.map +1 -1
  647. package/daemon/dist/domain/review/compose.d.ts.map +1 -1
  648. package/daemon/dist/domain/review/compose.js +5 -4
  649. package/daemon/dist/domain/review/compose.js.map +1 -1
  650. package/daemon/dist/domain/review/gather.d.ts +2 -1
  651. package/daemon/dist/domain/review/gather.d.ts.map +1 -1
  652. package/daemon/dist/domain/review/gather.js +25 -17
  653. package/daemon/dist/domain/review/gather.js.map +1 -1
  654. package/daemon/dist/domain/rig-lifecycle-service.d.ts +31 -8
  655. package/daemon/dist/domain/rig-lifecycle-service.d.ts.map +1 -1
  656. package/daemon/dist/domain/rig-lifecycle-service.js +253 -98
  657. package/daemon/dist/domain/rig-lifecycle-service.js.map +1 -1
  658. package/daemon/dist/domain/rig-mode/rig-mode-store.d.ts +11 -1
  659. package/daemon/dist/domain/rig-mode/rig-mode-store.d.ts.map +1 -1
  660. package/daemon/dist/domain/rig-mode/rig-mode-store.js +69 -48
  661. package/daemon/dist/domain/rig-mode/rig-mode-store.js.map +1 -1
  662. package/daemon/dist/domain/rig-repository.d.ts +18 -0
  663. package/daemon/dist/domain/rig-repository.d.ts.map +1 -1
  664. package/daemon/dist/domain/rig-repository.js +52 -1
  665. package/daemon/dist/domain/rig-repository.js.map +1 -1
  666. package/daemon/dist/domain/rig-teardown.d.ts +4 -1
  667. package/daemon/dist/domain/rig-teardown.d.ts.map +1 -1
  668. package/daemon/dist/domain/rig-teardown.js +40 -2
  669. package/daemon/dist/domain/rig-teardown.js.map +1 -1
  670. package/daemon/dist/domain/rigspec-codec.d.ts.map +1 -1
  671. package/daemon/dist/domain/rigspec-codec.js +7 -0
  672. package/daemon/dist/domain/rigspec-codec.js.map +1 -1
  673. package/daemon/dist/domain/rigspec-exporter.d.ts.map +1 -1
  674. package/daemon/dist/domain/rigspec-exporter.js +4 -0
  675. package/daemon/dist/domain/rigspec-exporter.js.map +1 -1
  676. package/daemon/dist/domain/rigspec-instantiator.d.ts +3 -0
  677. package/daemon/dist/domain/rigspec-instantiator.d.ts.map +1 -1
  678. package/daemon/dist/domain/rigspec-instantiator.js +28 -5
  679. package/daemon/dist/domain/rigspec-instantiator.js.map +1 -1
  680. package/daemon/dist/domain/rigspec-preflight.d.ts.map +1 -1
  681. package/daemon/dist/domain/rigspec-preflight.js +25 -11
  682. package/daemon/dist/domain/rigspec-preflight.js.map +1 -1
  683. package/daemon/dist/domain/rigspec-schema.d.ts.map +1 -1
  684. package/daemon/dist/domain/rigspec-schema.js +24 -2
  685. package/daemon/dist/domain/rigspec-schema.js.map +1 -1
  686. package/daemon/dist/domain/runtime-adapter.d.ts +12 -0
  687. package/daemon/dist/domain/runtime-adapter.d.ts.map +1 -1
  688. package/daemon/dist/domain/runtime-adapter.js.map +1 -1
  689. package/daemon/dist/domain/runtime-verifier.d.ts +15 -1
  690. package/daemon/dist/domain/runtime-verifier.d.ts.map +1 -1
  691. package/daemon/dist/domain/runtime-verifier.js +28 -4
  692. package/daemon/dist/domain/runtime-verifier.js.map +1 -1
  693. package/daemon/dist/domain/scope/logical-checkbox.js +1 -1
  694. package/daemon/dist/domain/scope/logical-checkbox.js.map +1 -1
  695. package/daemon/dist/domain/scope/scope-view-projection.d.ts +4 -2
  696. package/daemon/dist/domain/scope/scope-view-projection.d.ts.map +1 -1
  697. package/daemon/dist/domain/scope/scope-view-projection.js +29 -14
  698. package/daemon/dist/domain/scope/scope-view-projection.js.map +1 -1
  699. package/daemon/dist/domain/seat-activity-service.d.ts +9 -2
  700. package/daemon/dist/domain/seat-activity-service.d.ts.map +1 -1
  701. package/daemon/dist/domain/seat-activity-service.js +100 -15
  702. package/daemon/dist/domain/seat-activity-service.js.map +1 -1
  703. package/daemon/dist/domain/seat-attention-reconciler.d.ts +3 -0
  704. package/daemon/dist/domain/seat-attention-reconciler.d.ts.map +1 -1
  705. package/daemon/dist/domain/seat-attention-reconciler.js +114 -7
  706. package/daemon/dist/domain/seat-attention-reconciler.js.map +1 -1
  707. package/daemon/dist/domain/seat-delivery-guard.d.ts +36 -1
  708. package/daemon/dist/domain/seat-delivery-guard.d.ts.map +1 -1
  709. package/daemon/dist/domain/seat-delivery-guard.js +255 -22
  710. package/daemon/dist/domain/seat-delivery-guard.js.map +1 -1
  711. package/daemon/dist/domain/seat-effort.d.ts +115 -0
  712. package/daemon/dist/domain/seat-effort.d.ts.map +1 -0
  713. package/daemon/dist/domain/seat-effort.js +233 -0
  714. package/daemon/dist/domain/seat-effort.js.map +1 -0
  715. package/daemon/dist/domain/seat-env.d.ts +37 -0
  716. package/daemon/dist/domain/seat-env.d.ts.map +1 -0
  717. package/daemon/dist/domain/seat-env.js +94 -0
  718. package/daemon/dist/domain/seat-env.js.map +1 -0
  719. package/daemon/dist/domain/seat-handover-service.d.ts +5 -0
  720. package/daemon/dist/domain/seat-handover-service.d.ts.map +1 -1
  721. package/daemon/dist/domain/seat-handover-service.js +22 -4
  722. package/daemon/dist/domain/seat-handover-service.js.map +1 -1
  723. package/daemon/dist/domain/seat-identity-reconciler.d.ts.map +1 -1
  724. package/daemon/dist/domain/seat-identity-reconciler.js +19 -2
  725. package/daemon/dist/domain/seat-identity-reconciler.js.map +1 -1
  726. package/daemon/dist/domain/seat-lifecycle-service.d.ts +234 -5
  727. package/daemon/dist/domain/seat-lifecycle-service.d.ts.map +1 -1
  728. package/daemon/dist/domain/seat-lifecycle-service.js +651 -95
  729. package/daemon/dist/domain/seat-lifecycle-service.js.map +1 -1
  730. package/daemon/dist/domain/seat-restart-in-place.d.ts +303 -0
  731. package/daemon/dist/domain/seat-restart-in-place.d.ts.map +1 -0
  732. package/daemon/dist/domain/seat-restart-in-place.js +914 -0
  733. package/daemon/dist/domain/seat-restart-in-place.js.map +1 -0
  734. package/daemon/dist/domain/seat-structural-activity-service.d.ts +7 -3
  735. package/daemon/dist/domain/seat-structural-activity-service.d.ts.map +1 -1
  736. package/daemon/dist/domain/seat-structural-activity-service.js +50 -15
  737. package/daemon/dist/domain/seat-structural-activity-service.js.map +1 -1
  738. package/daemon/dist/domain/session-fingerprinter.d.ts.map +1 -1
  739. package/daemon/dist/domain/session-fingerprinter.js +9 -5
  740. package/daemon/dist/domain/session-fingerprinter.js.map +1 -1
  741. package/daemon/dist/domain/session-registry.d.ts +16 -1
  742. package/daemon/dist/domain/session-registry.d.ts.map +1 -1
  743. package/daemon/dist/domain/session-registry.js +35 -3
  744. package/daemon/dist/domain/session-registry.js.map +1 -1
  745. package/daemon/dist/domain/session-transport.d.ts +141 -4
  746. package/daemon/dist/domain/session-transport.d.ts.map +1 -1
  747. package/daemon/dist/domain/session-transport.js +1496 -139
  748. package/daemon/dist/domain/session-transport.js.map +1 -1
  749. package/daemon/dist/domain/slices/slice-detail-projector.d.ts +2 -0
  750. package/daemon/dist/domain/slices/slice-detail-projector.d.ts.map +1 -1
  751. package/daemon/dist/domain/slices/slice-detail-projector.js +16 -4
  752. package/daemon/dist/domain/slices/slice-detail-projector.js.map +1 -1
  753. package/daemon/dist/domain/slices/slice-indexer.d.ts +10 -0
  754. package/daemon/dist/domain/slices/slice-indexer.d.ts.map +1 -1
  755. package/daemon/dist/domain/slices/slice-indexer.js +84 -4
  756. package/daemon/dist/domain/slices/slice-indexer.js.map +1 -1
  757. package/daemon/dist/domain/snapshot-repository.d.ts +3 -0
  758. package/daemon/dist/domain/snapshot-repository.d.ts.map +1 -1
  759. package/daemon/dist/domain/snapshot-repository.js +19 -2
  760. package/daemon/dist/domain/snapshot-repository.js.map +1 -1
  761. package/daemon/dist/domain/spec-library-workflow-scanner.d.ts +1 -1
  762. package/daemon/dist/domain/spec-library-workflow-scanner.d.ts.map +1 -1
  763. package/daemon/dist/domain/spec-library-workflow-scanner.js +24 -21
  764. package/daemon/dist/domain/spec-library-workflow-scanner.js.map +1 -1
  765. package/daemon/dist/domain/spec-validation-advisory.js +1 -1
  766. package/daemon/dist/domain/spec-validation-advisory.js.map +1 -1
  767. package/daemon/dist/domain/startup-orchestrator.d.ts +33 -3
  768. package/daemon/dist/domain/startup-orchestrator.d.ts.map +1 -1
  769. package/daemon/dist/domain/startup-orchestrator.js +220 -61
  770. package/daemon/dist/domain/startup-orchestrator.js.map +1 -1
  771. package/daemon/dist/domain/startup-submission-evidence.d.ts +13 -3
  772. package/daemon/dist/domain/startup-submission-evidence.d.ts.map +1 -1
  773. package/daemon/dist/domain/startup-submission-evidence.js +34 -25
  774. package/daemon/dist/domain/startup-submission-evidence.js.map +1 -1
  775. package/daemon/dist/domain/startup-validation.d.ts.map +1 -1
  776. package/daemon/dist/domain/startup-validation.js +3 -0
  777. package/daemon/dist/domain/startup-validation.js.map +1 -1
  778. package/daemon/dist/domain/successor-session-launcher.d.ts +10 -8
  779. package/daemon/dist/domain/successor-session-launcher.d.ts.map +1 -1
  780. package/daemon/dist/domain/successor-session-launcher.js +11 -33
  781. package/daemon/dist/domain/successor-session-launcher.js.map +1 -1
  782. package/daemon/dist/domain/text-cut.d.ts +4 -0
  783. package/daemon/dist/domain/text-cut.d.ts.map +1 -0
  784. package/daemon/dist/domain/text-cut.js +7 -0
  785. package/daemon/dist/domain/text-cut.js.map +1 -0
  786. package/daemon/dist/domain/time-bound-advisory.d.ts +5 -0
  787. package/daemon/dist/domain/time-bound-advisory.d.ts.map +1 -0
  788. package/daemon/dist/domain/time-bound-advisory.js +25 -0
  789. package/daemon/dist/domain/time-bound-advisory.js.map +1 -0
  790. package/daemon/dist/domain/transcript-capture.d.ts +7 -0
  791. package/daemon/dist/domain/transcript-capture.d.ts.map +1 -1
  792. package/daemon/dist/domain/transcript-capture.js +22 -1
  793. package/daemon/dist/domain/transcript-capture.js.map +1 -1
  794. package/daemon/dist/domain/transcript-rotation.d.ts +12 -4
  795. package/daemon/dist/domain/transcript-rotation.d.ts.map +1 -1
  796. package/daemon/dist/domain/transcript-rotation.js +185 -23
  797. package/daemon/dist/domain/transcript-rotation.js.map +1 -1
  798. package/daemon/dist/domain/transcript-store.d.ts +1 -1
  799. package/daemon/dist/domain/transcript-store.d.ts.map +1 -1
  800. package/daemon/dist/domain/transcript-store.js +8 -4
  801. package/daemon/dist/domain/transcript-store.js.map +1 -1
  802. package/daemon/dist/domain/types.d.ts +82 -3
  803. package/daemon/dist/domain/types.d.ts.map +1 -1
  804. package/daemon/dist/domain/types.js.map +1 -1
  805. package/daemon/dist/domain/usage-samples-store.d.ts +8 -0
  806. package/daemon/dist/domain/usage-samples-store.d.ts.map +1 -1
  807. package/daemon/dist/domain/usage-samples-store.js +24 -5
  808. package/daemon/dist/domain/usage-samples-store.js.map +1 -1
  809. package/daemon/dist/domain/usage-series.d.ts +154 -2
  810. package/daemon/dist/domain/usage-series.d.ts.map +1 -1
  811. package/daemon/dist/domain/usage-series.js +208 -9
  812. package/daemon/dist/domain/usage-series.js.map +1 -1
  813. package/daemon/dist/domain/user-settings/settings-store.d.ts +1 -1
  814. package/daemon/dist/domain/user-settings/settings-store.d.ts.map +1 -1
  815. package/daemon/dist/domain/user-settings/settings-store.js +67 -0
  816. package/daemon/dist/domain/user-settings/settings-store.js.map +1 -1
  817. package/daemon/dist/domain/wake-resolver.js +2 -2
  818. package/daemon/dist/domain/wake-resolver.js.map +1 -1
  819. package/daemon/dist/domain/watchdog-jobs-repository.d.ts +3 -1
  820. package/daemon/dist/domain/watchdog-jobs-repository.d.ts.map +1 -1
  821. package/daemon/dist/domain/watchdog-jobs-repository.js +32 -4
  822. package/daemon/dist/domain/watchdog-jobs-repository.js.map +1 -1
  823. package/daemon/dist/domain/watchdog-policy-engine.d.ts +1 -0
  824. package/daemon/dist/domain/watchdog-policy-engine.d.ts.map +1 -1
  825. package/daemon/dist/domain/watchdog-policy-engine.js +31 -2
  826. package/daemon/dist/domain/watchdog-policy-engine.js.map +1 -1
  827. package/daemon/dist/domain/whoami-service.d.ts +4 -1
  828. package/daemon/dist/domain/whoami-service.d.ts.map +1 -1
  829. package/daemon/dist/domain/whoami-service.js +25 -19
  830. package/daemon/dist/domain/whoami-service.js.map +1 -1
  831. package/daemon/dist/domain/workflow-exception-escalation.d.ts.map +1 -1
  832. package/daemon/dist/domain/workflow-exception-escalation.js +3 -1
  833. package/daemon/dist/domain/workflow-exception-escalation.js.map +1 -1
  834. package/daemon/dist/domain/workflow-exception-readiness.d.ts.map +1 -1
  835. package/daemon/dist/domain/workflow-exception-readiness.js +7 -3
  836. package/daemon/dist/domain/workflow-exception-readiness.js.map +1 -1
  837. package/daemon/dist/domain/workflow-instance-store.d.ts +11 -0
  838. package/daemon/dist/domain/workflow-instance-store.d.ts.map +1 -1
  839. package/daemon/dist/domain/workflow-instance-store.js +18 -4
  840. package/daemon/dist/domain/workflow-instance-store.js.map +1 -1
  841. package/daemon/dist/domain/workflow-projector.d.ts.map +1 -1
  842. package/daemon/dist/domain/workflow-projector.js +5 -3
  843. package/daemon/dist/domain/workflow-projector.js.map +1 -1
  844. package/daemon/dist/domain/workflow-rig-placeholder.d.ts +24 -0
  845. package/daemon/dist/domain/workflow-rig-placeholder.d.ts.map +1 -0
  846. package/daemon/dist/domain/workflow-rig-placeholder.js +43 -0
  847. package/daemon/dist/domain/workflow-rig-placeholder.js.map +1 -0
  848. package/daemon/dist/domain/workflow-runtime.d.ts +10 -1
  849. package/daemon/dist/domain/workflow-runtime.d.ts.map +1 -1
  850. package/daemon/dist/domain/workflow-runtime.js +26 -13
  851. package/daemon/dist/domain/workflow-runtime.js.map +1 -1
  852. package/daemon/dist/domain/workflow-scope.d.ts +50 -0
  853. package/daemon/dist/domain/workflow-scope.d.ts.map +1 -0
  854. package/daemon/dist/domain/workflow-scope.js +129 -0
  855. package/daemon/dist/domain/workflow-scope.js.map +1 -0
  856. package/daemon/dist/domain/workflow-spec-cache.d.ts +10 -0
  857. package/daemon/dist/domain/workflow-spec-cache.d.ts.map +1 -1
  858. package/daemon/dist/domain/workflow-spec-cache.js +13 -0
  859. package/daemon/dist/domain/workflow-spec-cache.js.map +1 -1
  860. package/daemon/dist/domain/workflow-types.d.ts +4 -0
  861. package/daemon/dist/domain/workflow-types.d.ts.map +1 -1
  862. package/daemon/dist/domain/workflow-types.js.map +1 -1
  863. package/daemon/dist/domain/workflow-validator.d.ts.map +1 -1
  864. package/daemon/dist/domain/workflow-validator.js +3 -1
  865. package/daemon/dist/domain/workflow-validator.js.map +1 -1
  866. package/daemon/dist/domain/workspace/project-read.d.ts +4 -0
  867. package/daemon/dist/domain/workspace/project-read.d.ts.map +1 -1
  868. package/daemon/dist/domain/workspace/project-read.js +23 -8
  869. package/daemon/dist/domain/workspace/project-read.js.map +1 -1
  870. package/daemon/dist/index.d.ts.map +1 -1
  871. package/daemon/dist/index.js +16 -0
  872. package/daemon/dist/index.js.map +1 -1
  873. package/daemon/dist/lib/pane-envelope.js +1 -1
  874. package/daemon/dist/lib/pane-envelope.js.map +1 -1
  875. package/daemon/dist/middleware/json-body-error.d.ts +4 -3
  876. package/daemon/dist/middleware/json-body-error.d.ts.map +1 -1
  877. package/daemon/dist/middleware/json-body-error.js +18 -4
  878. package/daemon/dist/middleware/json-body-error.js.map +1 -1
  879. package/daemon/dist/routes/activity.d.ts.map +1 -1
  880. package/daemon/dist/routes/activity.js +78 -0
  881. package/daemon/dist/routes/activity.js.map +1 -1
  882. package/daemon/dist/routes/bundles.d.ts.map +1 -1
  883. package/daemon/dist/routes/bundles.js +39 -16
  884. package/daemon/dist/routes/bundles.js.map +1 -1
  885. package/daemon/dist/routes/chat.d.ts.map +1 -1
  886. package/daemon/dist/routes/chat.js +23 -2
  887. package/daemon/dist/routes/chat.js.map +1 -1
  888. package/daemon/dist/routes/community-harnesses.d.ts +3 -0
  889. package/daemon/dist/routes/community-harnesses.d.ts.map +1 -0
  890. package/daemon/dist/routes/community-harnesses.js +18 -0
  891. package/daemon/dist/routes/community-harnesses.js.map +1 -0
  892. package/daemon/dist/routes/compaction.d.ts +3 -0
  893. package/daemon/dist/routes/compaction.d.ts.map +1 -1
  894. package/daemon/dist/routes/compaction.js +14 -2
  895. package/daemon/dist/routes/compaction.js.map +1 -1
  896. package/daemon/dist/routes/events.js +26 -12
  897. package/daemon/dist/routes/events.js.map +1 -1
  898. package/daemon/dist/routes/gateway.d.ts +3 -0
  899. package/daemon/dist/routes/gateway.d.ts.map +1 -1
  900. package/daemon/dist/routes/gateway.js +15 -0
  901. package/daemon/dist/routes/gateway.js.map +1 -1
  902. package/daemon/dist/routes/hosts.d.ts.map +1 -1
  903. package/daemon/dist/routes/hosts.js +2 -1
  904. package/daemon/dist/routes/hosts.js.map +1 -1
  905. package/daemon/dist/routes/proof.d.ts.map +1 -1
  906. package/daemon/dist/routes/proof.js +99 -10
  907. package/daemon/dist/routes/proof.js.map +1 -1
  908. package/daemon/dist/routes/provider.d.ts +11 -1
  909. package/daemon/dist/routes/provider.d.ts.map +1 -1
  910. package/daemon/dist/routes/provider.js +204 -1
  911. package/daemon/dist/routes/provider.js.map +1 -1
  912. package/daemon/dist/routes/query-limit.d.ts +1 -0
  913. package/daemon/dist/routes/query-limit.d.ts.map +1 -1
  914. package/daemon/dist/routes/query-limit.js +1 -1
  915. package/daemon/dist/routes/query-limit.js.map +1 -1
  916. package/daemon/dist/routes/queue.d.ts.map +1 -1
  917. package/daemon/dist/routes/queue.js +62 -9
  918. package/daemon/dist/routes/queue.js.map +1 -1
  919. package/daemon/dist/routes/rig-mode.d.ts.map +1 -1
  920. package/daemon/dist/routes/rig-mode.js +66 -11
  921. package/daemon/dist/routes/rig-mode.js.map +1 -1
  922. package/daemon/dist/routes/rigs.js +5 -3
  923. package/daemon/dist/routes/rigs.js.map +1 -1
  924. package/daemon/dist/routes/scope-audit.d.ts.map +1 -1
  925. package/daemon/dist/routes/scope-audit.js +10 -0
  926. package/daemon/dist/routes/scope-audit.js.map +1 -1
  927. package/daemon/dist/routes/scopes.d.ts.map +1 -1
  928. package/daemon/dist/routes/scopes.js +5 -3
  929. package/daemon/dist/routes/scopes.js.map +1 -1
  930. package/daemon/dist/routes/seat.d.ts.map +1 -1
  931. package/daemon/dist/routes/seat.js +95 -12
  932. package/daemon/dist/routes/seat.js.map +1 -1
  933. package/daemon/dist/routes/sessions.js +1 -1
  934. package/daemon/dist/routes/startup.d.ts.map +1 -1
  935. package/daemon/dist/routes/startup.js +9 -2
  936. package/daemon/dist/routes/startup.js.map +1 -1
  937. package/daemon/dist/routes/telemetry.d.ts.map +1 -1
  938. package/daemon/dist/routes/telemetry.js +13 -3
  939. package/daemon/dist/routes/telemetry.js.map +1 -1
  940. package/daemon/dist/routes/transcripts.d.ts.map +1 -1
  941. package/daemon/dist/routes/transcripts.js +14 -5
  942. package/daemon/dist/routes/transcripts.js.map +1 -1
  943. package/daemon/dist/routes/transport.d.ts.map +1 -1
  944. package/daemon/dist/routes/transport.js +13 -2
  945. package/daemon/dist/routes/transport.js.map +1 -1
  946. package/daemon/dist/routes/up.d.ts.map +1 -1
  947. package/daemon/dist/routes/up.js +4 -0
  948. package/daemon/dist/routes/up.js.map +1 -1
  949. package/daemon/dist/routes/workflow.d.ts.map +1 -1
  950. package/daemon/dist/routes/workflow.js +50 -1
  951. package/daemon/dist/routes/workflow.js.map +1 -1
  952. package/daemon/dist/server.d.ts.map +1 -1
  953. package/daemon/dist/server.js +4 -1
  954. package/daemon/dist/server.js.map +1 -1
  955. package/daemon/dist/spec-validation-surface.d.ts +1 -1
  956. package/daemon/dist/spec-validation-surface.d.ts.map +1 -1
  957. package/daemon/dist/spec-validation-surface.js +1 -1
  958. package/daemon/dist/spec-validation-surface.js.map +1 -1
  959. package/daemon/dist/startup.d.ts +3 -1
  960. package/daemon/dist/startup.d.ts.map +1 -1
  961. package/daemon/dist/startup.js +150 -54
  962. package/daemon/dist/startup.js.map +1 -1
  963. package/daemon/dist/workflow-scope-surface.d.ts +2 -0
  964. package/daemon/dist/workflow-scope-surface.d.ts.map +1 -0
  965. package/daemon/dist/workflow-scope-surface.js +2 -0
  966. package/daemon/dist/workflow-scope-surface.js.map +1 -0
  967. package/daemon/docs/reference/agent-spec.md +4 -2
  968. package/daemon/docs/reference/agent-startup-guide.md +114 -6
  969. package/daemon/docs/reference/claude-status-line.md +69 -0
  970. package/daemon/docs/reference/getting-started.md +39 -6
  971. package/daemon/docs/reference/keeping-a-watch.md +253 -0
  972. package/daemon/docs/reference/project-workspace.md +70 -1
  973. package/daemon/docs/reference/publishing-a-rig-bundle.md +6 -1
  974. package/daemon/docs/reference/refocus-channel.md +41 -5
  975. package/daemon/docs/reference/rig-bundle.md +26 -2
  976. package/daemon/docs/reference/rig-spec.md +66 -5
  977. package/daemon/docs/reference/schemas/bundle-behaviour.v1.schema.json +105 -2
  978. package/daemon/docs/reference/scoped-operating-posture.md +12 -2
  979. package/daemon/docs/reference/sdlc-conventions.md +50 -2
  980. package/daemon/docs/reference/seat-delivery.md +127 -0
  981. package/daemon/docs/reference/slack-app-setup.md +147 -17
  982. package/daemon/docs/reference/topology-naming.md +9 -0
  983. package/daemon/docs/reference/whats-new.md +169 -147
  984. package/daemon/specs/agents/shared/skills/core/openrig-skills/SKILL.md +2 -2
  985. package/daemon/specs/agents/shared/skills/core/openrig-software-factory/SKILL.md +0 -1
  986. package/daemon/specs/agents/shared/skills/core/openrig-software-factory/references/worked-example.md +7 -4
  987. package/daemon/specs/agents/shared/skills/core/topology-mutation-and-seat-management/SKILL.md +1 -1
  988. package/daemon/specs/agents/shared/skills/pods/orchestration-team/SKILL.md +1 -1
  989. package/daemon/specs/rigs/launch/demo/CULTURE.md +1 -1
  990. package/daemon/specs/rigs/launch/factory/CULTURE.md +1 -1
  991. package/daemon/specs/rigs/launch/factory/topology/rig/CRAFT.md +2 -2
  992. package/daemon/specs/rigs/launch/first-project/CULTURE.md +2 -1
  993. package/daemon/specs/rigs/launch/first-project-claude/CULTURE.md +2 -1
  994. package/daemon/specs/rigs/launch/first-project-mixed/CULTURE.md +2 -1
  995. package/daemon/specs/rigs/launch/kernel/agents/operator/agent/guidance/role.md +9 -2
  996. package/daemon/specs/rigs/launch/starter/CULTURE.md +2 -1
  997. package/daemon/specs/rigs/preview/product-team/CULTURE.md +1 -1
  998. package/dist/bin-wrapper.d.ts +2 -1
  999. package/dist/bin-wrapper.d.ts.map +1 -1
  1000. package/dist/bin-wrapper.js +61 -4
  1001. package/dist/bin-wrapper.js.map +1 -1
  1002. package/dist/build-info.js +3 -3
  1003. package/dist/bundle-behaviour.d.ts.map +1 -1
  1004. package/dist/bundle-behaviour.js +22 -0
  1005. package/dist/bundle-behaviour.js.map +1 -1
  1006. package/dist/client.d.ts.map +1 -1
  1007. package/dist/client.js +3 -3
  1008. package/dist/client.js.map +1 -1
  1009. package/dist/commands/add.d.ts.map +1 -1
  1010. package/dist/commands/add.js +1 -1
  1011. package/dist/commands/add.js.map +1 -1
  1012. package/dist/commands/ask.d.ts.map +1 -1
  1013. package/dist/commands/ask.js +16 -5
  1014. package/dist/commands/ask.js.map +1 -1
  1015. package/dist/commands/bootstrap.d.ts.map +1 -1
  1016. package/dist/commands/bootstrap.js +9 -5
  1017. package/dist/commands/bootstrap.js.map +1 -1
  1018. package/dist/commands/bundle.d.ts.map +1 -1
  1019. package/dist/commands/bundle.js +8 -0
  1020. package/dist/commands/bundle.js.map +1 -1
  1021. package/dist/commands/capture.d.ts +4 -0
  1022. package/dist/commands/capture.d.ts.map +1 -1
  1023. package/dist/commands/capture.js +15 -2
  1024. package/dist/commands/capture.js.map +1 -1
  1025. package/dist/commands/community-harness.d.ts +4 -0
  1026. package/dist/commands/community-harness.d.ts.map +1 -0
  1027. package/dist/commands/community-harness.js +48 -0
  1028. package/dist/commands/community-harness.js.map +1 -0
  1029. package/dist/commands/compact.d.ts.map +1 -1
  1030. package/dist/commands/compact.js +8 -4
  1031. package/dist/commands/compact.js.map +1 -1
  1032. package/dist/commands/config.d.ts.map +1 -1
  1033. package/dist/commands/config.js +8 -0
  1034. package/dist/commands/config.js.map +1 -1
  1035. package/dist/commands/crash-cart.d.ts.map +1 -1
  1036. package/dist/commands/crash-cart.js +3 -5
  1037. package/dist/commands/crash-cart.js.map +1 -1
  1038. package/dist/commands/create.d.ts.map +1 -1
  1039. package/dist/commands/create.js +7 -5
  1040. package/dist/commands/create.js.map +1 -1
  1041. package/dist/commands/daemon.d.ts.map +1 -1
  1042. package/dist/commands/daemon.js +7 -0
  1043. package/dist/commands/daemon.js.map +1 -1
  1044. package/dist/commands/down.d.ts.map +1 -1
  1045. package/dist/commands/down.js +2 -1
  1046. package/dist/commands/down.js.map +1 -1
  1047. package/dist/commands/launch.d.ts +1 -0
  1048. package/dist/commands/launch.d.ts.map +1 -1
  1049. package/dist/commands/launch.js +61 -10
  1050. package/dist/commands/launch.js.map +1 -1
  1051. package/dist/commands/policy.d.ts +2 -1
  1052. package/dist/commands/policy.d.ts.map +1 -1
  1053. package/dist/commands/policy.js +66 -7
  1054. package/dist/commands/policy.js.map +1 -1
  1055. package/dist/commands/proof.d.ts.map +1 -1
  1056. package/dist/commands/proof.js +11 -5
  1057. package/dist/commands/proof.js.map +1 -1
  1058. package/dist/commands/provider.d.ts +2 -0
  1059. package/dist/commands/provider.d.ts.map +1 -1
  1060. package/dist/commands/provider.js +360 -1
  1061. package/dist/commands/provider.js.map +1 -1
  1062. package/dist/commands/ps.d.ts +15 -0
  1063. package/dist/commands/ps.d.ts.map +1 -1
  1064. package/dist/commands/ps.js +53 -10
  1065. package/dist/commands/ps.js.map +1 -1
  1066. package/dist/commands/queue.d.ts +15 -5
  1067. package/dist/commands/queue.d.ts.map +1 -1
  1068. package/dist/commands/queue.js +172 -47
  1069. package/dist/commands/queue.js.map +1 -1
  1070. package/dist/commands/requirements.d.ts.map +1 -1
  1071. package/dist/commands/requirements.js +8 -5
  1072. package/dist/commands/requirements.js.map +1 -1
  1073. package/dist/commands/restore.js +1 -1
  1074. package/dist/commands/restore.js.map +1 -1
  1075. package/dist/commands/rig-mode.d.ts.map +1 -1
  1076. package/dist/commands/rig-mode.js +106 -18
  1077. package/dist/commands/rig-mode.js.map +1 -1
  1078. package/dist/commands/scope.d.ts.map +1 -1
  1079. package/dist/commands/scope.js +103 -44
  1080. package/dist/commands/scope.js.map +1 -1
  1081. package/dist/commands/seat.d.ts.map +1 -1
  1082. package/dist/commands/seat.js +290 -28
  1083. package/dist/commands/seat.js.map +1 -1
  1084. package/dist/commands/send.d.ts +2 -2
  1085. package/dist/commands/send.d.ts.map +1 -1
  1086. package/dist/commands/send.js +79 -12
  1087. package/dist/commands/send.js.map +1 -1
  1088. package/dist/commands/slack.d.ts.map +1 -1
  1089. package/dist/commands/slack.js +9 -4
  1090. package/dist/commands/slack.js.map +1 -1
  1091. package/dist/commands/start.d.ts +11 -0
  1092. package/dist/commands/start.d.ts.map +1 -1
  1093. package/dist/commands/start.js +81 -6
  1094. package/dist/commands/start.js.map +1 -1
  1095. package/dist/commands/status.d.ts.map +1 -1
  1096. package/dist/commands/status.js +60 -2
  1097. package/dist/commands/status.js.map +1 -1
  1098. package/dist/commands/up.d.ts.map +1 -1
  1099. package/dist/commands/up.js +68 -0
  1100. package/dist/commands/up.js.map +1 -1
  1101. package/dist/commands/usage.d.ts +12 -0
  1102. package/dist/commands/usage.d.ts.map +1 -1
  1103. package/dist/commands/usage.js +107 -0
  1104. package/dist/commands/usage.js.map +1 -1
  1105. package/dist/commands/watchdog.d.ts.map +1 -1
  1106. package/dist/commands/watchdog.js +20 -7
  1107. package/dist/commands/watchdog.js.map +1 -1
  1108. package/dist/commands/whoami.d.ts +36 -0
  1109. package/dist/commands/whoami.d.ts.map +1 -1
  1110. package/dist/commands/whoami.js +125 -7
  1111. package/dist/commands/whoami.js.map +1 -1
  1112. package/dist/commands/workflow.d.ts.map +1 -1
  1113. package/dist/commands/workflow.js +53 -5
  1114. package/dist/commands/workflow.js.map +1 -1
  1115. package/dist/config-store.d.ts +20 -1
  1116. package/dist/config-store.d.ts.map +1 -1
  1117. package/dist/config-store.js +92 -0
  1118. package/dist/config-store.js.map +1 -1
  1119. package/dist/daemon-lifecycle.d.ts +2 -2
  1120. package/dist/daemon-lifecycle.d.ts.map +1 -1
  1121. package/dist/daemon-lifecycle.js +5 -4
  1122. package/dist/daemon-lifecycle.js.map +1 -1
  1123. package/dist/host-registry.d.ts.map +1 -1
  1124. package/dist/host-registry.js +6 -3
  1125. package/dist/host-registry.js.map +1 -1
  1126. package/dist/index.d.ts +1 -0
  1127. package/dist/index.d.ts.map +1 -1
  1128. package/dist/index.js +4 -7
  1129. package/dist/index.js.map +1 -1
  1130. package/dist/lazy-program.d.ts +9 -0
  1131. package/dist/lazy-program.d.ts.map +1 -0
  1132. package/dist/lazy-program.js +111 -0
  1133. package/dist/lazy-program.js.map +1 -0
  1134. package/dist/lib/bundle-check.js +2 -2
  1135. package/dist/lib/bundle-check.js.map +1 -1
  1136. package/dist/lib/bundle-source.d.ts +1 -0
  1137. package/dist/lib/bundle-source.d.ts.map +1 -1
  1138. package/dist/lib/bundle-source.js +1 -0
  1139. package/dist/lib/bundle-source.js.map +1 -1
  1140. package/dist/lib/codex-auth.d.ts.map +1 -1
  1141. package/dist/lib/codex-auth.js +17 -1
  1142. package/dist/lib/codex-auth.js.map +1 -1
  1143. package/dist/lib/context-install.js +1 -1
  1144. package/dist/lib/context-install.js.map +1 -1
  1145. package/dist/lib/file-transfer.js +1 -1
  1146. package/dist/lib/file-transfer.js.map +1 -1
  1147. package/dist/lib/frontmatter.d.ts +11 -0
  1148. package/dist/lib/frontmatter.d.ts.map +1 -0
  1149. package/dist/lib/frontmatter.js +20 -0
  1150. package/dist/lib/frontmatter.js.map +1 -0
  1151. package/dist/lib/permission-policy/policy-spec.d.ts.map +1 -1
  1152. package/dist/lib/permission-policy/policy-spec.js +5 -7
  1153. package/dist/lib/permission-policy/policy-spec.js.map +1 -1
  1154. package/dist/lib/scope/logical-checkbox.js +1 -1
  1155. package/dist/lib/scope/logical-checkbox.js.map +1 -1
  1156. package/dist/lib/scope/mission-composition.d.ts +5 -1
  1157. package/dist/lib/scope/mission-composition.d.ts.map +1 -1
  1158. package/dist/lib/scope/mission-composition.js +61 -1
  1159. package/dist/lib/scope/mission-composition.js.map +1 -1
  1160. package/dist/lib/scope/scope-fs.d.ts +10 -1
  1161. package/dist/lib/scope/scope-fs.d.ts.map +1 -1
  1162. package/dist/lib/scope/scope-fs.js +54 -5
  1163. package/dist/lib/scope/scope-fs.js.map +1 -1
  1164. package/dist/lib/scope/slice-claims.d.ts +8 -0
  1165. package/dist/lib/scope/slice-claims.d.ts.map +1 -0
  1166. package/dist/lib/scope/slice-claims.js +88 -0
  1167. package/dist/lib/scope/slice-claims.js.map +1 -0
  1168. package/dist/lib/work-install.d.ts.map +1 -1
  1169. package/dist/lib/work-install.js +3 -13
  1170. package/dist/lib/work-install.js.map +1 -1
  1171. package/dist/mcp-server.d.ts.map +1 -1
  1172. package/dist/mcp-server.js +113 -0
  1173. package/dist/mcp-server.js.map +1 -1
  1174. package/dist/program-root.d.ts +5 -0
  1175. package/dist/program-root.d.ts.map +1 -0
  1176. package/dist/program-root.js +11 -0
  1177. package/dist/program-root.js.map +1 -0
  1178. package/dist/queue-read-compact.d.ts +29 -0
  1179. package/dist/queue-read-compact.d.ts.map +1 -0
  1180. package/dist/queue-read-compact.js +74 -0
  1181. package/dist/queue-read-compact.js.map +1 -0
  1182. package/dist/queue-receipt.d.ts +41 -0
  1183. package/dist/queue-receipt.d.ts.map +1 -0
  1184. package/dist/queue-receipt.js +272 -0
  1185. package/dist/queue-receipt.js.map +1 -0
  1186. package/dist/remote-host-ops.d.ts +21 -0
  1187. package/dist/remote-host-ops.d.ts.map +1 -1
  1188. package/dist/remote-host-ops.js +25 -12
  1189. package/dist/remote-host-ops.js.map +1 -1
  1190. package/package.json +1 -1
  1191. package/tui/dist/commands/registry.d.ts.map +1 -1
  1192. package/tui/dist/commands/registry.js +3 -0
  1193. package/tui/dist/commands/registry.js.map +1 -1
  1194. package/tui/dist/config/config-model.d.ts.map +1 -1
  1195. package/tui/dist/config/config-model.js +7 -0
  1196. package/tui/dist/config/config-model.js.map +1 -1
  1197. package/tui/dist/cost/cost-model.d.ts +12 -0
  1198. package/tui/dist/cost/cost-model.d.ts.map +1 -0
  1199. package/tui/dist/cost/cost-model.js +141 -0
  1200. package/tui/dist/cost/cost-model.js.map +1 -0
  1201. package/tui/dist/cost/cost-read.d.ts +19 -0
  1202. package/tui/dist/cost/cost-read.d.ts.map +1 -0
  1203. package/tui/dist/cost/cost-read.js +4 -0
  1204. package/tui/dist/cost/cost-read.js.map +1 -0
  1205. package/tui/dist/daemon-client.d.ts +13 -0
  1206. package/tui/dist/daemon-client.d.ts.map +1 -1
  1207. package/tui/dist/daemon-client.js +77 -1
  1208. package/tui/dist/daemon-client.js.map +1 -1
  1209. package/tui/dist/detail.d.ts +2 -0
  1210. package/tui/dist/detail.d.ts.map +1 -1
  1211. package/tui/dist/detail.js +5 -0
  1212. package/tui/dist/detail.js.map +1 -1
  1213. package/tui/dist/execution/execution-model.d.ts.map +1 -1
  1214. package/tui/dist/execution/execution-model.js +38 -18
  1215. package/tui/dist/execution/execution-model.js.map +1 -1
  1216. package/tui/dist/helper-cwd.d.ts +3 -0
  1217. package/tui/dist/helper-cwd.d.ts.map +1 -0
  1218. package/tui/dist/helper-cwd.js +16 -0
  1219. package/tui/dist/helper-cwd.js.map +1 -0
  1220. package/tui/dist/hydrate.d.ts.map +1 -1
  1221. package/tui/dist/hydrate.js +43 -5
  1222. package/tui/dist/hydrate.js.map +1 -1
  1223. package/tui/dist/input.d.ts.map +1 -1
  1224. package/tui/dist/input.js +2 -1
  1225. package/tui/dist/input.js.map +1 -1
  1226. package/tui/dist/local-reading.d.ts.map +1 -1
  1227. package/tui/dist/local-reading.js +2 -1
  1228. package/tui/dist/local-reading.js.map +1 -1
  1229. package/tui/dist/main.js +6 -5
  1230. package/tui/dist/main.js.map +1 -1
  1231. package/tui/dist/pulse/pulse-model.d.ts +1 -0
  1232. package/tui/dist/pulse/pulse-model.d.ts.map +1 -1
  1233. package/tui/dist/pulse/pulse-model.js +7 -3
  1234. package/tui/dist/pulse/pulse-model.js.map +1 -1
  1235. package/tui/dist/pulse/render-pulse.d.ts.map +1 -1
  1236. package/tui/dist/pulse/render-pulse.js +10 -2
  1237. package/tui/dist/pulse/render-pulse.js.map +1 -1
  1238. package/tui/dist/reading.js +1 -1
  1239. package/tui/dist/reading.js.map +1 -1
  1240. package/tui/dist/render.d.ts.map +1 -1
  1241. package/tui/dist/render.js +36 -12
  1242. package/tui/dist/render.js.map +1 -1
  1243. package/tui/dist/scopes/scopes-model.d.ts +6 -1
  1244. package/tui/dist/scopes/scopes-model.d.ts.map +1 -1
  1245. package/tui/dist/scopes/scopes-model.js +33 -13
  1246. package/tui/dist/scopes/scopes-model.js.map +1 -1
  1247. package/tui/dist/sections.d.ts.map +1 -1
  1248. package/tui/dist/sections.js +4 -1
  1249. package/tui/dist/sections.js.map +1 -1
  1250. package/tui/dist/socket-server.d.ts.map +1 -1
  1251. package/tui/dist/socket-server.js +2 -1
  1252. package/tui/dist/socket-server.js.map +1 -1
  1253. package/tui/dist/specs/launch.d.ts.map +1 -1
  1254. package/tui/dist/specs/launch.js +2 -1
  1255. package/tui/dist/specs/launch.js.map +1 -1
  1256. package/tui/dist/startup.js +1 -1
  1257. package/tui/dist/startup.js.map +1 -1
  1258. package/tui/dist/state.d.ts.map +1 -1
  1259. package/tui/dist/state.js +8 -4
  1260. package/tui/dist/state.js.map +1 -1
  1261. package/tui/dist/terminals/open-window.d.ts.map +1 -1
  1262. package/tui/dist/terminals/open-window.js +2 -1
  1263. package/tui/dist/terminals/open-window.js.map +1 -1
  1264. package/tui/dist/types.d.ts +12 -0
  1265. package/tui/dist/types.d.ts.map +1 -1
  1266. package/tui/dist/usage/usage-hosts-model.d.ts +17 -0
  1267. package/tui/dist/usage/usage-hosts-model.d.ts.map +1 -0
  1268. package/tui/dist/usage/usage-hosts-model.js +73 -0
  1269. package/tui/dist/usage/usage-hosts-model.js.map +1 -0
  1270. package/tui/dist/usage/usage-model.d.ts +39 -0
  1271. package/tui/dist/usage/usage-model.d.ts.map +1 -0
  1272. package/tui/dist/usage/usage-model.js +161 -0
  1273. package/tui/dist/usage/usage-model.js.map +1 -0
  1274. package/ui/dist/assets/{index-D5n8EwkG.js → index-Cfbtfd9G.js} +159 -159
  1275. package/ui/dist/index.html +1 -1
@@ -1,14 +1,20 @@
1
+ import stringWidth from "string-width";
2
+ import { CODEX_TURN_STATUS_PATTERN } from "./codex-pane-patterns.js";
1
3
  import { randomUUID } from "node:crypto";
2
- import { inspectStartupStagedText, startupSubmissionEvidence } from "./startup-submission-evidence.js";
4
+ import { COLLAPSED_PASTE, composerRegion, isComposerFooter, inspectStartupStagedText, QUEUED_PLACEHOLDER, startupSubmissionEvidence } from "./startup-submission-evidence.js";
5
+ 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";
3
6
  export { inspectStartupStagedText } from "./startup-submission-evidence.js";
4
7
  import { OutboxHandler } from "./outbox-handler.js";
8
+ import { DraftAwareDelivery } from "./draft-aware-delivery.js";
5
9
  import { claudeResumeRotation } from "./session-registry.js";
6
- import { latestHookWaitsOnPerson } from "./agent-activity-store.js";
10
+ import { latestHookPermissionPending, latestHookWaitsOnPerson } from "./agent-activity-store.js";
7
11
  import { wrapPaneEnvelope, appendDeliveredSegment } from "../lib/pane-envelope.js";
8
12
  import { SeatIdentityStore } from "./seat-identity-store.js";
9
13
  import { isShellForeground } from "./shell-classifier.js";
10
14
  import { observeClaudeDelivery, verifyCodexPaneProcess } from "./native-process-lineage.js";
11
15
  import { hashSentText } from "./capture-observer.js";
16
+ import { isAttentionRequiredReadinessCode } from "./runtime-adapter.js";
17
+ import { getLatestStartupFailedEvent, hasClaudeResumeIdentityWork, isRecoverableStartupFailure } from "./startup-orchestrator.js";
12
18
  // OPR.0.4.1.10 — send-readiness freshness. The runtime-hook store keeps a 5min freshness for activity
13
19
  // DISPLAY, but "safe to send NOW" needs a tight window: a stale "idle" read must not authorize a send
14
20
  // into what may since have become a prompt. Beyond this window the hook is ignored for send-readiness
@@ -19,13 +25,18 @@ import { hashSentText } from "./capture-observer.js";
19
25
  // window comfortably spans one inter-tool gap (so a mid-turn reading stays trusted) while refusing to
20
26
  // authorize a send from a reading tens of seconds old. (EXA: agtx#14, daintree#3938.)
21
27
  const SEND_READINESS_FRESHNESS_MS = 15_000;
28
+ // transport.verify_input_box: each extra input-box read gives up after this long and reads `unavailable`. A deadline
29
+ // budget, not a guarantee: the pre-Enter read comes after the existing 200 ms wait and can add about this much before
30
+ // Enter, and the post-Enter process read about this much before the reply. A read that misses it leaves the send
31
+ // unconfirmed, and Enter is still sent once.
32
+ const INPUT_BOX_READ_DEADLINE_MS = 500;
22
33
  // Mid-work detection patterns (cheap heuristics)
23
34
  const MID_WORK_PATTERNS = [
24
35
  /[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/, // spinner chars
25
36
  /Working/,
26
37
  /^[✶✢✳✻✽·]\s+\S.*(?:…|\.{3})\s+\([^)]*\bthinking\)$/m,
27
38
  /esc to interrupt/,
28
- /^[❯›]\s*\d+\.\s/m, // trust/consent prompt choices (e.g. '› 1. Yes, continue')
39
+ COMPOSER_SELECTION_SCAN_PATTERN, // trust/consent prompt choices (e.g. '› 1. Yes, continue')
29
40
  ];
30
41
  // Idle-prompt patterns: empty prompt line (no typed text after the char).
31
42
  // Lines like '❯ Working on a task.' have text after the prompt char and
@@ -36,23 +47,26 @@ const MID_WORK_PATTERNS = [
36
47
  // normally sits above it, but Codex hides that row while it streams assistant output. So the
37
48
  // placeholder counts as idle only through MID_WORK_PATTERNS here, and classifySendReadiness
38
49
  // never lets a placeholder-only verdict override a display-fresh running/needs_input hook.
39
- const CODEX_EMPTY_COMPOSER_PATTERN = /^›\s+Ask Codex to do anything\s*$/;
40
- // Codex's live turn-status row: a bullet, a header ("Working", or the reasoning summary Codex shows in its place),
41
- // then the elapsed time and "esc to interrupt" in parentheses, e.g. "• Working (1h 09m 39s • esc to interrupt)".
42
- // Completed output that merely says "Working directory: …" or "Working tree is clean." never matches.
43
- const CODEX_TURN_STATUS_PATTERN = /^[•◦]\s+\S.*\((?:\d+[hms]\s*)+•\s*esc to interrupt\)/;
44
50
  const IDLE_PROMPT_PATTERNS = [
45
- /^[❯›]\s*$/, // prompt char + optional whitespace + end-of-line only
51
+ COMPOSER_EMPTY_PATTERN, // prompt char + optional whitespace + end-of-line only
46
52
  CODEX_EMPTY_COMPOSER_PATTERN,
47
53
  ];
48
54
  const PROMPT_DRAFT_PATTERNS = [
49
- /^[❯›]\s+\S/,
55
+ COMPOSER_DRAFT_PATTERN,
50
56
  ];
57
+ // Claude's permission-mode footers: default, accept edits, bypass, auto and plan, optionally after a
58
+ // vim-mode marker such as "-- INSERT --" (#808). A suffix such as "· 1 shell" can follow the mode.
59
+ const CLAUDE_MODE_FOOTER = /^(?:-- [A-Z]+ -- )?(?:⏵⏵ (?:accept edits|bypass permissions|auto mode) on\b|⏸ plan mode on\b|\? for shortcuts\b)/;
60
+ // Sibling pattern identical to CLAUDE_MODE_FOOTER except that it ends in \? for shortcuts$,
61
+ // keeping unframed-composer and selectionLines checks anchored on the default footer (#808).
62
+ const CLAUDE_ANCHORED_MODE_FOOTER = /^(?:-- [A-Z]+ -- )?(?:⏵⏵ (?:accept edits|bypass permissions|auto mode) on\b|⏸ plan mode on\b|\? for shortcuts$)/;
63
+ // Permission mode status footers specifically (excluding shortcuts hint that appears in Codex footers).
64
+ const CLAUDE_PERMISSION_MODE_FOOTER = /(?:-- [A-Z]+ -- )?(?:⏵⏵ (?:accept edits|bypass permissions|auto mode) on\b|⏸ plan mode on\b)/;
51
65
  // Footer hints, not proof of inactivity: Claude also renders its mode bar
52
66
  // during a turn. Current live-status evidence must take precedence below.
53
67
  const IDLE_STATUS_BAR_PATTERNS = [
54
68
  /gpt-\d[\d.]* .+ · Context \[/, // Codex model/context footer
55
- /⏵⏵ accept edits/, // Claude Code edit-accept bar
69
+ CLAUDE_PERMISSION_MODE_FOOTER, // Claude Code mode footers (#808)
56
70
  ];
57
71
  const IDLE_TERMINAL_COMMANDS = new Set(["zsh", "bash", "sh", "fish", "nu", "tmux"]);
58
72
  // OPR.0.4.1.10 — permission / confirmation question signatures. The numbered-selector pattern
@@ -119,9 +133,6 @@ const CLAUDE_STATUS_WARNINGS = [
119
133
  /^tmux focus-events off · add 'set -g focus-events on' to ~\/\.tmux\.conf and re…$/,
120
134
  /^You've used (?:\d|[1-9]\d)% of your weekly limit · resets \d{1,2}(?::\d{2})?(?:am|pm) \(UTC\)$/,
121
135
  ];
122
- // Claude's permission-mode footers: default, accept edits, bypass, auto and plan, optionally after a
123
- // vim-mode marker such as "-- INSERT --" (#808). A suffix such as "· 1 shell" can follow the mode.
124
- const CLAUDE_MODE_FOOTER = /^(?:-- [A-Z]+ -- )?(?:⏵⏵ (?:accept edits|bypass permissions|auto mode) on\b|⏸ plan mode on\b|\? for shortcuts\b)/;
125
136
  // Current Claude status rows need not end in "thinking)" or show "esc to interrupt".
126
137
  // Completed summaries such as "✻ Crunched for 2s" lack the live ellipsis/timer shape.
127
138
  // While a hook runs, the timer follows its label: "(running PostToolUse hook · 3m 12s · …)".
@@ -131,15 +142,20 @@ const CLAUDE_LIVE_STATUS_PATTERN = /^[✶✢✳✻✽·*]\s+\S[^(]*(?:…|\.{3})
131
142
  // ("✻ Crunched for 2s") have no trailing ellipsis. Only callers that must not send into a busy
132
143
  // pane opt in (ClassifyPaneOptions.timerlessStatusIsLive); ordinary send readiness is unchanged.
133
144
  const CLAUDE_TIMERLESS_STATUS_PATTERN = /^[✶✢✳✻✽·*]\s+\S.*(?:…|\.{3})$/;
145
+ /** The current Claude footer follows the input frame; earlier examples are history or draft text. */
146
+ function findClaudeFooterIndex(lines) {
147
+ let bar = lines.length - 1;
148
+ while (bar >= 0 && (!lines[bar].trim() || CLAUDE_STATUS_WARNINGS.some(pattern => pattern.test(lines[bar].trim()))))
149
+ bar--;
150
+ return bar;
151
+ }
134
152
  function findClaudeComposer(paneContent, options = {}) {
135
153
  // Preserve columns: a multiline draft may contain indented border/prompt text.
136
154
  // This classifier scans at most 20 physical lines; captures can be taller.
137
155
  // Exhausting the scan without reaching the status head is unknown, not idle.
138
156
  const lines = paneContent.split("\n").slice(-20)
139
157
  .map((line) => line.trimEnd()).filter((line) => line.trim().length > 0);
140
- let bar = lines.length - 1;
141
- while (bar >= 0 && CLAUDE_STATUS_WARNINGS.some((pattern) => pattern.test(lines[bar].trim())))
142
- bar--;
158
+ const bar = findClaudeFooterIndex(lines);
143
159
  const supportedWarningFooter = lines[bar]?.trim() === "⏵⏵ accept edits on (shift+tab to cycle) · ← for agents";
144
160
  const modeFooter = CLAUDE_MODE_FOOTER.test(lines[bar]?.trim() ?? "");
145
161
  let indent = /^([ \t]*)─{3,}$/.exec(lines[bar - 1] ?? "")?.[1];
@@ -149,10 +165,11 @@ function findClaudeComposer(paneContent, options = {}) {
149
165
  if (indent === undefined) {
150
166
  // The unframed Claude prompt has the same current status/task block.
151
167
  // Keep Codex footers and a bare prompt with no visible block on their old path.
152
- if (bar < 2 || !/^(?:⏵⏵ (?:accept edits|bypass permissions) on\b|⏸ plan mode on\b|\? for shortcuts$)/.test(lines[bar].trim()))
168
+ if (bar < 2 || !CLAUDE_ANCHORED_MODE_FOOTER.test(lines[bar].trim()))
153
169
  return null;
154
170
  prompt = lines[bar - 1] ?? "";
155
- indent = /^([ \t]*)❯\s*$/.exec(prompt)?.[1];
171
+ const input = readComposerPromptLine(prompt, composerPromptClassForRuntime("claude-code"));
172
+ indent = input && !input.text.trim() ? input.indent : undefined;
156
173
  if (indent === undefined)
157
174
  return null;
158
175
  statusStart = bar - 2;
@@ -160,7 +177,7 @@ function findClaudeComposer(paneContent, options = {}) {
160
177
  else {
161
178
  const upper = lines[bar - 3] ?? "";
162
179
  if (!upper.startsWith(indent) || !/^─{3,}(?: .+ ─+)?$/.test(upper.slice(indent.length)) ||
163
- !prompt.startsWith(`${indent}❯`) || !/^❯(?:\s|$)/.test(prompt.slice(indent.length)))
180
+ !prompt.startsWith(`${indent}❯`) || !readComposerPromptLine(prompt, composerPromptClassForRuntime("claude-code")))
164
181
  return null;
165
182
  }
166
183
  let liveStatus = null;
@@ -186,6 +203,163 @@ function findClaudeComposer(paneContent, options = {}) {
186
203
  }
187
204
  return { text: prompt.slice(indent.length), bar: lines[bar].trim(), framed, hasWarnings: bar < lines.length - 1, supportedWarningFooter, modeFooter, headSeen, liveStatus };
188
205
  }
206
+ const unknown = () => ({ state: "unknown", rows: [], columns: 0, cursorAtEnd: false, collapsedPaste: null });
207
+ const rule = (line) => /^[─━═-]{6,}(?: .+ [─━═-]+)?$/.test(line.trim());
208
+ const codexFooter = (line) => isComposerFooter(line)
209
+ || /(?:\b\d+% context left\b|\bContext \[)/.test(line);
210
+ /** Preserve faint styling so autocomplete ghosts can be separated from authored
211
+ * text. Color channel values must never be interpreted as style codes. */
212
+ function styledTextLines(screen) {
213
+ let faint = false;
214
+ return screen.replace(/\r\n/g, "\n").split("\n").map(raw => {
215
+ let text = "", offset = 0;
216
+ const faintCells = [];
217
+ const append = (value) => { text += value; faintCells.push(...Array(value.length).fill(faint)); };
218
+ for (const match of raw.matchAll(/\x1b\[([0-9;:]*)m/g)) {
219
+ append(raw.slice(offset, match.index));
220
+ const codes = match[1].split(";");
221
+ for (let i = 0; i < codes.length; i++) {
222
+ const code = codes[i];
223
+ if (/^(?:38|48|58):/.test(code))
224
+ continue; // Colon-form color is one parameter.
225
+ if (code === "38" || code === "48" || code === "58") {
226
+ const skip = codes[i + 1] === "5" ? 2 : codes[i + 1] === "2" ? (codes[i + 2] === "" ? 5 : 4) : 0;
227
+ // Color channels are not style codes. Ambiguous compound sequences
228
+ // provide no faint-style proof, rather than interpreting an RGB 2 as dim.
229
+ if (!skip || i + skip !== codes.length - 1) {
230
+ faint = false;
231
+ break;
232
+ }
233
+ i += skip;
234
+ }
235
+ else if (code === "" || code === "0" || code === "22")
236
+ faint = false;
237
+ else if (code === "2")
238
+ faint = true;
239
+ }
240
+ offset = match.index + match[0].length;
241
+ }
242
+ append(raw.slice(offset));
243
+ return { text, faintCells };
244
+ });
245
+ }
246
+ /** Inspect only the visible input containing the current cursor. Activity, hook
247
+ * freshness and text elsewhere in the pane are not evidence that input is empty.
248
+ */
249
+ export function inspectComposerInput(snapshot, runtime = null, owned) {
250
+ const promptClass = composerPromptClassForRuntime(runtime);
251
+ if (!snapshot || snapshot.inMode)
252
+ return unknown();
253
+ const { x, y, width, height } = snapshot.cursor;
254
+ if (![x, y, width, height].every(Number.isInteger) || x < 0 || y < 0 || x >= width || y >= height || width < 3 || height < 1)
255
+ return unknown();
256
+ const styled = styledTextLines(snapshot.screen);
257
+ const lines = styled.map(line => line.text);
258
+ if (lines.at(-1) === "")
259
+ lines.pop();
260
+ if (lines.length > height || !lines[y])
261
+ return unknown();
262
+ const footerAt = findClaudeFooterIndex(lines);
263
+ const footer = lines[footerAt]?.trim() ?? "";
264
+ let claudeEnd = footerAt - 1;
265
+ while (claudeEnd >= 0 && !lines[claudeEnd].trim())
266
+ claudeEnd--;
267
+ const candidates = [];
268
+ for (let start = 0; start <= y; start++) {
269
+ const prompt = readComposerPromptLine(lines[start] ?? "", promptClass);
270
+ if (!prompt || !/^ *$/.test(prompt.indent))
271
+ continue;
272
+ const numbered = composerSelectionPrefixPattern(promptClass).test(lines[start].trimStart()) || /^\d+[.)]\s/.test(prompt.text);
273
+ const prefix = prompt.prefix;
274
+ let end = -1;
275
+ if (prompt.glyph === "❯") {
276
+ // Share the main reader's terminal footer anchor. A quoted frame within a
277
+ // multiline draft cannot supply either the closing rule or its indentation.
278
+ const alignedRule = (line) => rule(line) && line.length - line.trimStart().length === prompt.indent.length;
279
+ if ((!isComposerFooter(footer) && !CLAUDE_MODE_FOOTER.test(footer))
280
+ || !alignedRule(lines[start - 1] ?? "") || !alignedRule(lines[claudeEnd] ?? ""))
281
+ continue;
282
+ end = claudeEnd;
283
+ }
284
+ else {
285
+ for (let i = start + 1; i < lines.length; i++) {
286
+ if (lines[i].trim())
287
+ continue;
288
+ const tail = lines.slice(i + 1).filter(line => line.trim());
289
+ if (lines[i + 1]?.trim() && tail.length === 1 && codexFooter(tail[0].trim())) {
290
+ end = i;
291
+ break;
292
+ }
293
+ }
294
+ }
295
+ if (end <= y || x < prefix)
296
+ continue;
297
+ const rows = [prompt.text];
298
+ let valid = true;
299
+ for (let i = start + 1; i < end; i++) {
300
+ const line = lines[i];
301
+ if (line.trim() && !line.startsWith(" ".repeat(prefix))) {
302
+ valid = false;
303
+ break;
304
+ }
305
+ rows.push(line.slice(prefix));
306
+ }
307
+ if (!valid)
308
+ continue;
309
+ // A suggestion is a wholly dim suffix beginning at the current cursor.
310
+ // A literal placeholder or text before a moved cursor remains authored input.
311
+ const cursorRow = y - start;
312
+ let ghost;
313
+ const currentRow = rows[cursorRow];
314
+ let cursorOffset = x === prefix ? 0 : -1;
315
+ let offset = 0;
316
+ for (const character of currentRow) {
317
+ offset += character.length;
318
+ if (stringWidth(currentRow.slice(0, offset)) === x - prefix)
319
+ cursorOffset = offset;
320
+ }
321
+ if (cursorOffset >= 0) {
322
+ const suffix = rows.slice(cursorRow).map((row, i) => ({
323
+ text: row.slice(i === 0 ? cursorOffset : 0),
324
+ faint: styled[y + i].faintCells.slice(prefix + (i === 0 ? cursorOffset : 0), prefix + row.length),
325
+ }));
326
+ if (suffix.some(row => row.text.length > 0) && suffix.every(row => row.faint.every(Boolean))) {
327
+ ghost = { line: y, offset: prefix + cursorOffset, continuationRows: rows.length - cursorRow - 1 };
328
+ rows.splice(cursorRow + 1);
329
+ rows[cursorRow] = currentRow.slice(0, cursorOffset);
330
+ }
331
+ }
332
+ const visible = rows.map(line => line.trimEnd());
333
+ // The cursor bounds authored whitespace on the last input row. Captures may
334
+ // omit trailing blank cells or include screen padding; neither changes the bytes to submit.
335
+ if (cursorRow === rows.length - 1) {
336
+ const cellWidth = stringWidth(rows[cursorRow]);
337
+ if (x - prefix >= cellWidth)
338
+ visible[cursorRow] = rows[cursorRow] + " ".repeat(x - prefix - cellWidth);
339
+ else if (cursorOffset >= 0 && !rows[cursorRow].slice(cursorOffset).trim())
340
+ visible[cursorRow] = rows[cursorRow].slice(0, cursorOffset);
341
+ }
342
+ const atStart = y === start && x === prefix;
343
+ const empty = atStart && rows.length === 1 && rows[0] === "";
344
+ const last = visible.length - 1;
345
+ const cursorAtEnd = y === start + last && x === prefix + stringWidth(visible[last] ?? "");
346
+ const label = visible.length === 1 && COMPOSER_COLLAPSED_PASTE_PATTERN.test(visible[0]) ? visible[0] : null;
347
+ const frame = JSON.stringify([prompt.glyph, prefix, width, footer]);
348
+ const input = { state: empty ? "empty" : "text", rows: visible, columns: width - prefix, cursorAtEnd, collapsedPaste: label, frame,
349
+ ...(ghost ? { ghost } : {}) };
350
+ // A numbered body is allowed only as this operation's exact text in its
351
+ // previously empty composer. A newly observed menu gains no such authority.
352
+ if (numbered && !(owned?.frame === frame && composerContainsOwnedText(input, owned.text)))
353
+ continue;
354
+ if (owned?.frame === frame && cursorAtEnd && visible.join("\n").includes("[Pasted text #")
355
+ && hasExpectedStagedText(`${prompt.glyph} ${visible.join("\n")}`, owned.text, runtime)) {
356
+ input.collapsedPaste = visible.join("\n");
357
+ input.pasteMatchesExpected = true;
358
+ }
359
+ candidates.push(input);
360
+ }
361
+ return candidates.length === 1 ? candidates[0] : unknown();
362
+ }
189
363
  function findPromptDraftBeforeFooter(paneContent) {
190
364
  const rawLines = paneContent.split("\n").map((line) => line.trimEnd());
191
365
  let lastLineIndex = rawLines.length - 1;
@@ -202,8 +376,9 @@ function findPromptDraftBeforeFooter(paneContent) {
202
376
  if (priorLine.trim().length === 0)
203
377
  return null;
204
378
  const priorTrimmed = priorLine.trim();
205
- const looksLikeDraft = PROMPT_DRAFT_PATTERNS.some((pattern) => pattern.test(priorTrimmed));
206
- const looksLikeSelection = /^[❯›]\s*\d+\.\s/.test(priorTrimmed);
379
+ const prompt = readComposerPromptLine(priorLine);
380
+ const looksLikeDraft = prompt !== null && prompt.text.trim().length > 0;
381
+ const looksLikeSelection = COMPOSER_SELECTION_PATTERN.test(priorTrimmed);
207
382
  if (!looksLikeDraft || looksLikeSelection)
208
383
  return null;
209
384
  return truncateEvidence(priorTrimmed);
@@ -280,7 +455,25 @@ function draftComposerLine(lines, verdict) {
280
455
  return null;
281
456
  return lines[last].trim();
282
457
  }
458
+ /** A community harness's screen never proves it's idle: a quiet prompt can be an approval or a pause, so its idle
459
+ * reading becomes unknown. Positive work and attention are kept. `communityHarness` is the seat's recorded selection
460
+ * (`communityHarnessOf`); without it the classification is returned as it is. */
461
+ export function communityHarnessPaneActivity(classification, communityHarness) {
462
+ return communityHarness && classification.state === "agent_idle"
463
+ ? { state: "unknown", reason: "community_harness_no_idle_signal", evidence: classification.evidence }
464
+ : classification;
465
+ }
283
466
  export function classifyPaneActivity(paneContent, options = {}) {
467
+ if (options.composerSnapshot) {
468
+ const snapshot = options.composerSnapshot;
469
+ const { ghost } = inspectComposerInput(snapshot, options.runtime);
470
+ const lines = styledTextLines(snapshot.screen).map(line => line.text);
471
+ if (ghost) {
472
+ lines[ghost.line] = lines[ghost.line].slice(0, ghost.offset);
473
+ lines.splice(ghost.line + 1, ghost.continuationRows);
474
+ }
475
+ paneContent = lines.join("\n");
476
+ }
284
477
  const lastNonBlank = trimPaneLines(paneContent);
285
478
  if (lastNonBlank.length === 0) {
286
479
  return { state: "unknown", reason: "empty_capture", evidence: null };
@@ -300,12 +493,12 @@ export function classifyPaneActivity(paneContent, options = {}) {
300
493
  // A complete later empty composer with a recognized Claude bar makes the preceding question history.
301
494
  // Keep draft handling and selectors without this dialog boundary unchanged.
302
495
  const selectionLines = claudeComposer?.framed &&
303
- /^(?:⏵⏵ (?:accept edits|bypass permissions|auto mode) on\b|⏸ plan mode on\b|\? for shortcuts$)/.test(claudeComposer.bar) &&
496
+ CLAUDE_ANCHORED_MODE_FOOTER.test(claudeComposer.bar) &&
304
497
  IDLE_PROMPT_PATTERNS.some(pattern => pattern.test(claudeComposer.text)) &&
305
498
  oldQuestionEnd >= 0 && oldQuestionEnd < promptScanLines.length - 1
306
499
  ? promptScanLines.slice(oldQuestionEnd + 1) : promptScanLines;
307
500
  const selectionPromptEvidence = findCurrentClaudeQuestion(paneContent) ??
308
- findPatternEvidence(selectionLines, [/^[❯›]\s*\d+\.\s/m]);
501
+ findPatternEvidence(selectionLines, [COMPOSER_SELECTION_SCAN_PATTERN]);
309
502
  if (selectionPromptEvidence) {
310
503
  return {
311
504
  state: "attention",
@@ -354,7 +547,8 @@ export function classifyPaneActivity(paneContent, options = {}) {
354
547
  CLAUDE_LIVE_STATUS_PATTERN,
355
548
  ...(options.timerlessStatusIsLive ? [CLAUDE_TIMERLESS_STATUS_PATTERN] : []),
356
549
  ]);
357
- if (idleStatusBarLine && (!idleStatusBarLine.includes("⏵⏵ accept edits") || !midWorkEvidence)) {
550
+ const isClaudeStatusBar = CLAUDE_PERMISSION_MODE_FOOTER.test(idleStatusBarLine ?? "");
551
+ if (idleStatusBarLine && (!isClaudeStatusBar || !midWorkEvidence)) {
358
552
  return {
359
553
  state: "agent_idle",
360
554
  reason: "idle_status_bar",
@@ -511,10 +705,12 @@ export async function probeSessionActivity(input) {
511
705
  };
512
706
  const captureSeq = observed ? nextCaptureSeq++ : 0;
513
707
  try {
514
- const paneContent = await tmuxAdapter.capturePaneContent(sessionName, 20);
708
+ const snapshot = input.composerSnapshot ? await input.composerSnapshot().catch(() => null) : null;
709
+ const paneContent = snapshot?.screen ?? await tmuxAdapter.capturePaneContent(sessionName, 20);
515
710
  const capturedAt = new Date().toISOString();
516
711
  const classifyOptions = { timerlessStatusIsLive: input.timerlessStatusIsLive === true };
517
- let classification = classifyPaneActivity(paneContent ?? "", classifyOptions);
712
+ let classification = communityHarnessPaneActivity(classifyPaneActivity(paneContent ?? "", { ...classifyOptions,
713
+ composerSnapshot: snapshot ?? undefined, runtime }), input.communityHarness);
518
714
  // Claude renders its prompt suggestion faint, which the plain capture loses. When a Claude pane reads
519
715
  // as a draft, one capture with styling is classified on its own. Newer attention or work there is
520
716
  // kept. A faint suggestion counts only when it's the same composer text this capture read as a draft;
@@ -604,6 +800,101 @@ function countOccurrences(haystack, needle) {
604
800
  }
605
801
  return count;
606
802
  }
803
+ /**
804
+ * Claude indents continuation lines in submitted prompts. Compare the same
805
+ * line content in the sent text and both captures, retaining line breaks and
806
+ * within-line spacing rather than allowing unrelated words to match.
807
+ */
808
+ function normalizeVerificationText(text) {
809
+ return text.replace(/\r?\n[ \t]*/g, "\n");
810
+ }
811
+ /** Match the full envelope with whitespace collapsed, including body and recipient.
812
+ * Raw sends retain their normalized 40-character prefix and spacing. */
813
+ function verificationSnippet(text) {
814
+ const normalized = normalizeVerificationText(text);
815
+ const header = /^From: [^\n]+\nTo: [^\n]+\n(?:Sent: [^\n]+\n)?---\n/.exec(normalized);
816
+ return { snippet: header ? normalized.replace(/\s+/g, " ").trim() : normalized.slice(0, 40), envelope: !!header };
817
+ }
818
+ /** Count whole visible envelope blocks, not prefixes of longer messages.
819
+ * A new envelope, prompt or same-column output ends an indented user block.
820
+ * Whitespace is compared only after retaining boundaries and quote indentation. */
821
+ function countEnvelopeOccurrences(transcript, expected, promptClass) {
822
+ const promptPrefix = new RegExp(`^[${promptClass}]\\s+`);
823
+ const promptBoundary = new RegExp(`^[${promptClass}](?:\\s|$)`);
824
+ const lines = transcript.split("\n");
825
+ let count = 0;
826
+ let block = null;
827
+ let blockIndent = 0;
828
+ let blockPrompted = false;
829
+ let inAgentOutput = false;
830
+ const finish = () => {
831
+ if (block?.join("\n").replace(/\s+/g, " ").trim() === expected)
832
+ count++;
833
+ block = null;
834
+ };
835
+ for (let i = 0; i < lines.length; i++) {
836
+ const line = lines[i];
837
+ const indent = /^[ \t]*/.exec(line)[0].length;
838
+ const prompted = promptPrefix.test(line.slice(indent));
839
+ const header = line.slice(indent).replace(promptPrefix, "");
840
+ const atBoundary = (!block && !inAgentOutput) || indent <= blockIndent;
841
+ const candidate = normalizeVerificationText([header, ...lines.slice(i + 1, i + 5)].join("\n"));
842
+ const envelopeHeader = /^From: [^\n]+\nTo: [^\n]+\n(?:Sent: [^\n]+\n)?---\n/.test(candidate);
843
+ if (atBoundary && envelopeHeader && (!inAgentOutput || prompted)) {
844
+ finish();
845
+ block = [header];
846
+ blockIndent = indent;
847
+ // Flat historical captures have no role columns; retain whole-block
848
+ // equality there instead of treating their own header/body as output.
849
+ blockPrompted = prompted && /^[ \t]*/.exec(lines[i + 1] ?? "")[0].length > indent;
850
+ inAgentOutput = false;
851
+ }
852
+ else if (atBoundary && blockPrompted && line.trim().length > 0) {
853
+ // Any nonblank boundary line is outside an indented user block. Deeper
854
+ // quotes stay in the body, and blank separators do not change its text.
855
+ finish();
856
+ inAgentOutput = true;
857
+ }
858
+ else if (atBoundary && promptBoundary.test(line.slice(indent))) {
859
+ finish();
860
+ }
861
+ else if (block) {
862
+ block.push(line);
863
+ }
864
+ }
865
+ finish();
866
+ return count;
867
+ }
868
+ /** The last prompt marker owns the current input region. Never count it or its
869
+ * continuation lines as submitted transcript, in either capture. If a capture
870
+ * ends at a historical prompt with no later input marker, leave it unconfirmed. */
871
+ function verificationTranscript(content, promptClass) {
872
+ const indentedPrompt = new RegExp(`^([ \\t]*)[${promptClass}]`);
873
+ const lines = (content ?? "").replace(/\r\n/g, "\n").split("\n");
874
+ let inputAt = findComposerInputLineIndex(lines, promptClass);
875
+ if (inputAt >= 0) {
876
+ let indent = /^[ \t]*/.exec(lines[inputAt])[0].length;
877
+ // Claude indents draft continuation lines. A quoted prompt on one of them
878
+ // belongs to the enclosing input, not a new transcript/composer boundary.
879
+ for (let i = inputAt - 1; i >= 0; i--) {
880
+ const prompt = indentedPrompt.exec(lines[i]);
881
+ if (prompt && prompt[1].length < indent) {
882
+ inputAt = i;
883
+ indent = prompt[1].length;
884
+ }
885
+ }
886
+ }
887
+ // A visible framed composer starts at its upper rule, not its prompt.
888
+ // Keep quoted/indented rules in the message; require a matching lower frame.
889
+ if (inputAt > 0) {
890
+ const indent = /^[ \t]*/.exec(lines[inputAt])[0];
891
+ const frame = (line) => line.startsWith(indent) &&
892
+ /^─{3,}(?: .+ ─+)?$/.test(line.slice(indent.length).trimEnd());
893
+ if (frame(lines[inputAt - 1]) && lines.slice(inputAt + 1).some(frame))
894
+ inputAt--;
895
+ }
896
+ return (inputAt < 0 ? lines : lines.slice(0, inputAt)).join("\n");
897
+ }
607
898
  /** Map a resolved fan-out target + its recipient list → the EnvelopeScope (ruling 03c35295). The
608
899
  * transport knows the target shape + resolved seats, so it builds the honest scale daemon-side:
609
900
  * DM / multi (full list) / rig- or pod-scoped broadcast (with seat count) / topology. */
@@ -620,33 +911,36 @@ export function scopeForTarget(target, recipients) {
620
911
  }
621
912
  /** The existing submit-only identity check, also used to inspect startup's own paste.
622
913
  * A false result is no matching staged evidence, not proof of model consumption. */
623
- export function hasExpectedStagedText(pane, expected) {
914
+ export function hasExpectedStagedText(pane, expected, runtime = null) {
624
915
  const norm = (s) => s.replace(/\s+/g, "");
916
+ // The composer glyph set is scoped to the runtime when known: a Claude draft's
917
+ // continuation line can start with a Codex glyph, and taking that line as the
918
+ // input marker would misread the staged text. Unknown runtimes keep the union.
919
+ const promptClass = composerPromptClassForRuntime(runtime);
625
920
  // Round-2 (r2 HIGH-1): the evidence must be the CURRENT ACTIVE INPUT and must identify
626
921
  // THIS piece — stale scrollback can carry an old placeholder while a LATER interactive
627
922
  // prompt owns the input, and an Enter there approves the prompt. So:
628
923
  // 1. Only the pane's LAST input-marker line counts (the current input; everything above
629
924
  // is history).
630
- // 2. A numbered-option line (`❯ 1. …`) is a PROMPT SELECTION, never staged input: refuse.
925
+ // 2. A numbered-option line (`❯ 1. …`, `› 1.Yes`) is a PROMPT SELECTION, never staged
926
+ // input: refuse.
631
927
  // 3. A pasted-text placeholder is identity-qualified: "[Pasted text #N +X lines]" counts
632
928
  // only when X matches the expected piece's own line count (±1 for a trailing newline).
633
929
  // More than one placeholder is COALESCED staging (several pieces, one Enter): refuse.
634
930
  // 4. Otherwise the line must carry the content's own head (24 normalized chars — a short
635
931
  // paste renders inline, possibly truncated).
636
932
  const paneLines = (pane ?? "").split("\n");
637
- let currentInputAt = -1;
638
- for (let i = paneLines.length - 1; i >= 0; i--) {
639
- if (paneLines[i].trimStart().startsWith("❯")) {
640
- currentInputAt = i;
641
- break;
642
- }
643
- }
933
+ const currentInputAt = findComposerInputLineIndex(paneLines, promptClass);
644
934
  let stagedEvidence = false;
645
935
  if (currentInputAt >= 0) {
936
+ // Unknown runtime: a composer block that mixes glyph kinds has no runtime to
937
+ // say which line is live, so fail closed exactly as the pre-union check did.
938
+ if (composerPromptIsAmbiguous(paneLines, currentInputAt, promptClass))
939
+ return false;
646
940
  const inputLine = paneLines[currentInputAt].trimStart();
647
- // The region is the last ❯-line through the input box's closing separator (a box-drawing
648
- // line) or pane end — wrapped input continues below the marker; everything ABOVE the
649
- // marker is history and everything below the separator is hint-bar chrome.
941
+ // The region is the last composer-marker line through the input box's closing separator
942
+ // (a box-drawing line) or pane end — wrapped input continues below the marker; everything
943
+ // ABOVE the marker is history and everything below the separator is hint-bar chrome.
650
944
  let regionEnd = paneLines.length;
651
945
  for (let i = currentInputAt + 1; i < paneLines.length; i++) {
652
946
  const t = paneLines[i].trim();
@@ -656,7 +950,7 @@ export function hasExpectedStagedText(pane, expected) {
656
950
  }
657
951
  }
658
952
  const region = paneLines.slice(currentInputAt, regionEnd).join("\n");
659
- if (!/^❯\s*\d+\./.test(inputLine)) {
953
+ if (!composerSelectionPrefixPattern(promptClass).test(inputLine)) {
660
954
  // Round-3 (r2 R2 HIGH-1, specimen-pinned): Claude renders ONE staged piece as MANY
661
955
  // placeholders whose displayed counts are SEGMENT sizes (sum ≤ source lines), followed
662
956
  // by the piece's own literal tail wrapped across pane lines — and the placeholder
@@ -682,7 +976,7 @@ export function hasExpectedStagedText(pane, expected) {
682
976
  // or one that is not the piece's own suffix, FAIL CLOSED — the TUI did not expose
683
977
  // enough content to identify the staged state, and a bare Enter is never guessed.
684
978
  const chrome = /paste again to expand|ctrl\+g to edit( in Vim)?/gi;
685
- const residual = norm(regionFlat.replace(placeholderRe, "").replace(chrome, "")).replace(/^❯/, "");
979
+ const residual = stripComposerPromptGlyph(norm(regionFlat.replace(placeholderRe, "").replace(chrome, "")), promptClass);
686
980
  const pieceNorm = norm(expected);
687
981
  const sum = counts.reduce((a, b) => a + b, 0);
688
982
  // Round-5 (r2 R4 HIGH-1): the suffix anchor is JOINED to the opaque prefix. The
@@ -719,23 +1013,466 @@ export function hasExpectedStagedText(pane, expected) {
719
1013
  function promptAnswerStaged(pane, answer, runtime) {
720
1014
  if (!pane || !answer.trim() || /[\r\n\x1b]/.test(answer))
721
1015
  return false;
1016
+ // Fail closed for an unknown runtime: with no scoped glyph set there is no way to
1017
+ // say which prompt line is the live input (the pre-shared-matcher check did the same).
1018
+ if (runtime !== "codex" && runtime !== "claude-code")
1019
+ return false;
1020
+ const promptClass = composerPromptClassForRuntime(runtime);
722
1021
  const lines = pane.split("\n");
723
- for (let i = lines.length - 1; i >= 0; i--) {
724
- const input = lines[i].trimStart();
725
- if (!/^[❯›]/.test(input))
726
- continue;
727
- if (/^[❯›]\s*\d+[.)]/.test(input))
728
- return false;
729
- if (runtime === "codex" ? !input.startsWith("›") : runtime !== "claude-code" || !input.startsWith("❯"))
730
- return false;
731
- const end = lines.findIndex((line, index) => index > i && (runtime === "codex"
732
- ? line.trim() === "" : /^[─═-]{10,}$/.test(line.trim())));
733
- return end > i && lines.slice(i, end).join("\n").trimStart().slice(1).trim() === answer.trim();
1022
+ // Only the LAST prompt line of this runtime's glyph set can be the current input;
1023
+ // everything above it is transcript history, and another harness's glyph is not ours.
1024
+ const inputIndex = findComposerInputLineIndex(lines, promptClass);
1025
+ if (inputIndex < 0)
1026
+ return false;
1027
+ const input = lines[inputIndex].trimStart();
1028
+ // A numbered choice (`.` or `)`), e.g. `» 1)` or `❯ 1.`, is a menu selection, never the answer.
1029
+ if (composerNumberedChoicePattern(promptClass).test(input))
1030
+ return false;
1031
+ // Where the input ends: Codex closes the composer on a blank line, Claude Code on a rule line.
1032
+ const end = lines.findIndex((line, index) => index > inputIndex && (runtime === "codex"
1033
+ ? line.trim() === "" : /^[─═-]{10,}$/.test(line.trim())));
1034
+ return end > inputIndex && lines.slice(inputIndex, end).join("\n").trimStart().slice(1).trim() === answer.trim();
1035
+ }
1036
+ // Codex 0.160 input box: the last prompt line plus indented continuation rows, one blank row, then a footer
1037
+ // of at most two rows: the `<model> · <~ or /path>` status line, optionally followed by one known hint row.
1038
+ const CODEX_STATUS_LINE = /^ {2}[^\s·][^·]* · [~/]/;
1039
+ const CODEX_HINT_ROW = /(?:^|\s)\? for shortcuts(?:\s|$)|⚠ \d+ warnings? · f2 to view\s*$/;
1040
+ // Codex shows a long paste as this label until it is submitted. Matched on the normalized body.
1041
+ const CODEX_COLLAPSED_PASTE = /^\[PastedContent\d+chars\]$/;
1042
+ /** The current Codex input box: its normalized text ("" when empty), its rows as drawn (the prompt glyph removed)
1043
+ * and where its prompt row is, or null when the layout is not the recognized one (another footer, a selector, no
1044
+ * blank separator, an older TUI). */
1045
+ function codexInputBox(pane) {
1046
+ const lines = pane.split("\n");
1047
+ let last = lines.length - 1;
1048
+ while (last >= 0 && !lines[last].trim())
1049
+ last--;
1050
+ let footerStart = last;
1051
+ while (footerStart > 0 && lines[footerStart - 1].trim())
1052
+ footerStart--;
1053
+ const footer = lines.slice(footerStart, last + 1);
1054
+ if (last < 0 || footer.length > 2 || !CODEX_STATUS_LINE.test(footer[0])
1055
+ || !footer.slice(1).every((row) => CODEX_HINT_ROW.test(row)))
1056
+ return null;
1057
+ // promptAnswerStaged also ends a Codex input at a blank row.
1058
+ const separator = footerStart - 1;
1059
+ if (separator < 1 || lines[separator].trim())
1060
+ return null;
1061
+ const promptClass = composerPromptClassForRuntime("codex");
1062
+ const inputAt = findComposerInputLineIndex(lines.slice(0, separator), promptClass);
1063
+ if (inputAt < 0 || composerNumberedChoicePattern(promptClass).test(lines[inputAt].trimStart()))
1064
+ return null;
1065
+ const rows = lines.slice(inputAt, separator);
1066
+ // Only indented continuation rows belong to the input; anything else is not an input box.
1067
+ if (rows.slice(1).some((row) => row.trim() && !/^\s/.test(row)))
1068
+ return null;
1069
+ if (CODEX_EMPTY_COMPOSER_PATTERN.test(rows[0].trimStart()) && rows.slice(1).every((row) => !row.trim())) {
1070
+ return { text: "", rows: [], lines, inputAt };
734
1071
  }
735
- return false;
1072
+ return { text: rows.join("\n").trimStart().slice(1).replace(/\s+/g, ""), rows: stripBoxGlyph(rows), lines, inputAt };
736
1073
  }
1074
+ /** A box's rows with the prompt glyph taken off the first row (`❯`, `›`), as composerRegion does for its body. */
1075
+ function stripBoxGlyph(rows) {
1076
+ return rows.map((row, i) => (i === 0 ? row.trimStart().slice(1) : row));
1077
+ }
1078
+ // Bounds of a string's terminal width. Upper: a non-ASCII character may take two cells. Lower: a printable ASCII
1079
+ // character takes exactly one cell, and any other character is counted as none (a combining mark, a variation
1080
+ // selector or a joiner takes none, and an emoji sequence of several code points takes two cells in all).
1081
+ const widthAtMost = (text) => [...text].reduce((cells, ch) => cells + (ch.charCodeAt(0) < 0x80 ? 1 : 2), 0);
1082
+ const widthAtLeast = (text) => [...text].filter((ch) => ch >= " " && ch <= "~").length;
1083
+ // Pairs of characters that no wrapper separates across rows inside a word that fits in a row, under the conditions
1084
+ // rowBreakIsSpace adds. Claude Code 2.1.296 wraps its input with wrap-ansi, which breaks only at spaces. Codex 0.160.0
1085
+ // wraps it with textwrap's FirstFit, its UAX #14 word separator and its hyphen splitter, then re-passes the rows
1086
+ // (tui/src/bottom_pane/textarea/wrapping.rs at rust-v0.160.0), which also break with no space drawn after a hyphen
1087
+ // or a slash, between ideographs and at other UAX #14 break opportunities. UAX #14 has none between two ASCII
1088
+ // letters or digits (LB23, LB25, LB28), nor between a printable ASCII character and a following hyphen, slash,
1089
+ // closing bracket or `.,:;!?` (LB13, LB15d, LB21).
1090
+ const ASCII_LETTER_OR_DIGIT = /^[A-Za-z0-9]$/;
1091
+ const ASCII_VISIBLE = /^[!-~]$/;
1092
+ const ASCII_NO_BREAK_BEFORE = /^[-/.,:;!?)\]}]$/;
1093
+ // A word of ASCII letters, digits, `'` and `.,:;`, which has no UAX #14 break opportunity inside it (LB13, LB19, LB23,
1094
+ // LB25, LB28, LB29): Codex's re-pass keeps such a word whole when it starts on a row that already holds text.
1095
+ const ASCII_UNBROKEN_WORD = /^[A-Za-z0-9'.,:;]+$/;
1096
+ /** Whether the break between two drawn rows of a box must be whitespace of the box's text, given a lower bound of
1097
+ * the box's width. Rows are measured as drawn, the box's own indentation included (an upper bound of their width);
1098
+ * words are split at ASCII spaces only, which neither TUI breaks inside a word. `ownIndent` is how many cells of
1099
+ * `row` the box draws itself (1 after the prompt glyph, 2 on a continuation row). Either:
1100
+ * - the next row's first word would have fit on this row after a space: both wrappers fill a row first
1101
+ * (wrap-ansi and FirstFit are greedy), so where the rows follow the wrapper's own lines the box breaks its rows
1102
+ * there only at a line break of its text. Residual (review-safety-3 F1, from a model of the rust-v0.160.0 source,
1103
+ * not a live capture): on Codex, once a row holding only indentation has absorbed and split a word (below), the
1104
+ * re-pass can push a later row early at a hyphen, since its keep-whole check measures a hyphenated compound as one
1105
+ * word, so a short row can be followed by a fragment that would have fit with no whitespace between them, and this
1106
+ * rule counts that break as a space. Every such box in that model also holds the earlier absorbed split, which
1107
+ * only this rule reads and which it rejects (the row is full), so the box reads `ambiguous`, never `exact`. That
1108
+ * relies on the row-start check below, which the tests pin; or
1109
+ * - no wrapper breaks between the two characters (ASCII_NO_BREAK_BEFORE above), the two words they end and begin,
1110
+ * joined, fit in a row, the row's last word has no break opportunity inside it (ASCII_UNBROKEN_WORD), and the
1111
+ * row's text starts at its first cell with a printable ASCII character. Codex 0.160.0 keeps a word that fits in a
1112
+ * row whole only where the word starts on a row that already holds text, after whitespace or at one of textwrap's
1113
+ * breaks: a row holding only indentation absorbs the next word and splits it where the row fills ("Partial
1114
+ * whitespace rows absorb the next word"), and a word after a hyphen or a slash in a row that took the next
1115
+ * textwrap line is split the same way. So on Codex a split between two letters is no space at all on a row that
1116
+ * starts with indentation, or after a break opportunity inside the row's last word; only the first rule above
1117
+ * reads such a break. */
1118
+ function rowBreakIsSpace(row, next, width, ownIndent) {
1119
+ const firstWord = next.trim().split(" ")[0];
1120
+ if (widthAtMost(row.trimEnd()) + 1 + widthAtMost(firstWord) <= width)
1121
+ return true;
1122
+ if (row.slice(0, ownIndent).trim() || !ASCII_VISIBLE.test(row.slice(ownIndent, ownIndent + 1)))
1123
+ return false;
1124
+ const lastWord = row.trim().split(" ").at(-1);
1125
+ if (!ASCII_UNBROKEN_WORD.test(lastWord))
1126
+ return false;
1127
+ const before = lastWord.at(-1);
1128
+ const after = next.trimStart()[0] ?? "";
1129
+ const noBreak = (ASCII_LETTER_OR_DIGIT.test(before) && ASCII_LETTER_OR_DIGIT.test(after))
1130
+ || (ASCII_VISIBLE.test(before) && ASCII_NO_BREAK_BEFORE.test(after));
1131
+ return noBreak && widthAtMost(lastWord + firstWord) <= width;
1132
+ }
1133
+ /** Whether a Claude Code or Codex box, given as its rows, holds exactly `text` with its word boundaries kept.
1134
+ * Whitespace inside a row is compared as one space (runs and Unicode spaces count the same, so `now here` is not
1135
+ * `nowhere`), and letters are compared as they are (case included). A row break counts as the text's space only
1136
+ * where it must be whitespace of the box's text (rowBreakIsSpace), measured against the box's widest row in
1137
+ * printable ASCII cells (widthAtLeast), a lower bound of the box's width whatever else the rows hold. Any other
1138
+ * break may fall inside a word of the box's text, or be how the box draws different spacing, so the reading is
1139
+ * `ambiguous`, never `exact`, and so is a break that falls inside a word of the sent text. */
1140
+ function boxHoldsText(rows, text) {
1141
+ // The box draws its first row one cell after the prompt glyph, and its continuation rows two cells in.
1142
+ const drawn = rows.flatMap((row, index) => (row.trim() ? [{ row, ownIndent: index === 0 ? 1 : 2 }] : []));
1143
+ const shown = drawn.map(({ row }) => row.trim().replace(/\s+/g, " "));
1144
+ const sent = text.trim().replace(/\s+/g, " ");
1145
+ // Measured on the rows as drawn: whitespace compares as a space, but U+FEFF, which `\s` matches, takes no cell.
1146
+ const width = Math.max(0, ...drawn.map(({ row }) => widthAtLeast(row.trim())));
1147
+ let at = 0;
1148
+ let ambiguous = false;
1149
+ for (let i = 0; i < shown.length; i++) {
1150
+ const row = shown[i];
1151
+ if (!sent.startsWith(row, at))
1152
+ return "other";
1153
+ at += row.length;
1154
+ if (i === shown.length - 1)
1155
+ break;
1156
+ if (sent[at] === " ") {
1157
+ at += 1;
1158
+ if (!rowBreakIsSpace(drawn[i].row, drawn[i + 1].row, width, drawn[i].ownIndent))
1159
+ ambiguous = true;
1160
+ }
1161
+ else {
1162
+ ambiguous = true;
1163
+ }
1164
+ }
1165
+ if (at !== sent.length)
1166
+ return "other";
1167
+ return ambiguous ? "ambiguous" : "exact";
1168
+ }
1169
+ /** The row of Codex's running-turn status (`• Working (… esc to interrupt)`) above its input box, or -1. */
1170
+ function codexTurnStatusAt(box) {
1171
+ for (let i = box.inputAt - 1; i >= 0; i--)
1172
+ if (CODEX_TURN_STATUS_PATTERN.test(box.lines[i]))
1173
+ return i;
1174
+ return -1;
1175
+ }
1176
+ /** Whether a Claude Code frame shows a running turn: `esc to interrupt` in the footer under its input box, or a
1177
+ * live status row above the box (timer-less rows included): in the two rows above the box's opening rule, where
1178
+ * Claude Code 2.1.296 draws its status row over a one-row margin whatever the box's height, or as
1179
+ * `findClaudeComposer` reads it above a one-row box. Claude Code queues a message submitted during a turn, and it
1180
+ * labels the queue only while a per-user hint counter is low, so an unlabelled queued message leaves the same
1181
+ * empty box as a submit. With a statusLine configured (OpenRig installs one on every seat) and while the box holds
1182
+ * text, the footer never draws `esc to interrupt`, so the status row is the running signal, but only while it is
1183
+ * drawn: Claude Code hides it while streamed text is shown (SEAT_IDLE_SHAPES.claude.statusSlot). So this reading
1184
+ * can only say a turn ran; a turn it misses is ruled out, if at all, by the seat reading before the paste. */
1185
+ function claudeTurnRunning(pane) {
1186
+ const { markerLine, closingRuleLine } = composerRegion(pane, "claude-code");
1187
+ if (closingRuleLine === null || markerLine === null)
1188
+ return false;
1189
+ const lines = pane.split("\n");
1190
+ if (lines.slice(closingRuleLine).some((row) => /esc to interrupt/.test(row)))
1191
+ return true;
1192
+ // markerLine is 1-based: the box's first row is lines[markerLine - 1] and its opening rule the row above it.
1193
+ const statusSlot = lines.slice(Math.max(0, markerLine - 4), Math.max(0, markerLine - 2)).map((row) => row.trim());
1194
+ if (statusSlot.some((row) => CLAUDE_LIVE_STATUS_PATTERN.test(row) || CLAUDE_TIMERLESS_STATUS_PATTERN.test(row)))
1195
+ return true;
1196
+ return findClaudeComposer(pane, { timerlessStatusIsLive: true })?.liveStatus != null;
1197
+ }
1198
+ // Claude's faint `Try "…"` suggestion in an empty box. The capture is unstyled, so it is read as other text and
1199
+ // never as the sent text, even when the text is the suggestion itself.
1200
+ const CLAUDE_TRY_SUGGESTION = /^Try"[^"]*"$/;
1201
+ // Codex 0.160 lists messages waiting for a running turn above its input box under one of these headers, with
1202
+ // `↳ <text>` rows (tui/src/bottom_pane/pending_input_preview.rs). Like Claude's queued label, such a row is generic:
1203
+ // it says a message waits, not which one.
1204
+ const CODEX_PENDING_INPUT_HEADER = /^• (?:Messages to be submitted (?:after next tool call|at end of turn)|Queued follow-up inputs)\b/;
1205
+ const CODEX_PENDING_INPUT_ROW = new RegExp(`${CODEX_PENDING_INPUT_HEADER.source}|^↳ `);
1206
+ const BRAILLE_SPINNER = /[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/;
1207
+ // Rows that rule out an idle seat wherever they are in the capture (compared trimmed). A transcript row that
1208
+ // happens to match also reads not-idle, which only leaves the send unconfirmed.
1209
+ const SEAT_NOT_IDLE_ROWS = {
1210
+ "claude-code": [CLAUDE_LIVE_STATUS_PATTERN, CLAUDE_TIMERLESS_STATUS_PATTERN, /esc to interrupt/, /paste again to expand/,
1211
+ /Press up to edit queued messages/, BRAILLE_SPINNER],
1212
+ codex: [CODEX_TURN_STATUS_PATTERN, /esc to interrupt/, CODEX_PENDING_INPUT_ROW, BRAILLE_SPINNER],
1213
+ };
1214
+ // Codex 0.160's completion row (tui/src/history_cell/separators.rs, FinalMessageSeparator::label at rust-v0.160.0):
1215
+ // `Worked for <elapsed>`, then ` • ` and the completion time (an optional date for another day, then the host's
1216
+ // 12- or 24-hour clock, tui/src/clock_format.rs), then optional runtime metrics, each after ` • `, in source order.
1217
+ // A live completion always has both the elapsed time and the time (chatwidget/completion.rs), so both are required.
1218
+ // `<1s`, `{s}s`, `{m}m {s}s` or `{h}h {m}m {s}s`, with minutes and seconds below 60 and no leading zeros.
1219
+ const CODEX_SIXTY = "(?:[0-9]|[1-5][0-9])";
1220
+ const CODEX_NONZERO_SIXTY = "(?:[1-9]|[1-5][0-9])";
1221
+ 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)`;
1222
+ // `%b %-d at ` (this year) or `%b %-d, %Y at ` (another year); nothing for today.
1223
+ 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 )?";
1224
+ // `%-I:%M %p` (TwelveHour) or `%H:%M` (TwentyFourHour).
1225
+ const CODEX_CLOCK = "(?:(?:[1-9]|1[0-2]):[0-5][0-9] [AP]M|(?:[01][0-9]|2[0-3]):[0-5][0-9])";
1226
+ // runtime_metrics_label: format_duration_ms gives `<n>.<d>s` from one second, `<n>ms` below it.
1227
+ const CODEX_MS = "(?:[0-9]+\\.[0-9]s|[0-9]+ms)";
1228
+ const CODEX_TTFT_TBT = `(?:${CODEX_MS} \\(iapi\\)(?: ${CODEX_MS} \\(service\\))?|${CODEX_MS} \\(service\\))`;
1229
+ // A metric is shown only for a count above zero; pluralize() gives the singular for exactly one.
1230
+ const CODEX_MANY = "(?:[2-9]|[1-9][0-9]+)";
1231
+ const CODEX_COUNT = "[1-9][0-9]*";
1232
+ const CODEX_METRICS = [
1233
+ `Local tools: (?:1 call|${CODEX_MANY} calls) \\(${CODEX_MS}\\)`,
1234
+ `Inference: (?:1 call|${CODEX_MANY} calls) \\(${CODEX_MS}\\)`,
1235
+ `WebSocket: ${CODEX_COUNT} events send \\(${CODEX_MS}\\)`,
1236
+ `(?:Stream: 1 event|Streams: ${CODEX_MANY} events) \\(${CODEX_MS}\\)`,
1237
+ `${CODEX_COUNT} events received \\(${CODEX_MS}\\)`,
1238
+ `Responses API overhead: ${CODEX_MS}`,
1239
+ `Responses API inference: ${CODEX_MS}`,
1240
+ `TTFT: ${CODEX_TTFT_TBT}`,
1241
+ `TBT: ${CODEX_TTFT_TBT}`,
1242
+ ].map((metric) => `(?: • ${metric})?`).join("");
1243
+ const CODEX_COMPLETION_ROW = new RegExp(`^ {2}Worked for ${CODEX_ELAPSED} • ${CODEX_DATE}${CODEX_CLOCK}${CODEX_METRICS}$`);
1244
+ const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1245
+ // Claude Code 2.1.296's completion row, which ends a finished turn in the transcript (the bundle's turn-duration
1246
+ // row): the `✻` glyph in a two-cell column at column 0, then `<verb> for <duration> · done <time>`. The verb is one
1247
+ // of the bundle's past-tense list, picked from the turn's id. The duration is the bundle's duration format:
1248
+ // `<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
1249
+ // zeros. The time is the clock of a turn that finished today in the en-US forms of the bundle's time-format
1250
+ // presets: `auto` (en-US) and `12-hour` (`5:24 PM`), `24-hour` (`17:24`, `09:05`), `24-hour-utc` (`17:24Z`); real
1251
+ // frames of OpenRig-launched seats show the 12-hour form. Anything after the time
1252
+ // (a still-running note, hidden-message or budget notes), a row without `· done`, another locale, a custom
1253
+ // pattern or the weekday and date of an earlier day is not on this list.
1254
+ const CLAUDE_COMPLETION_VERBS = ["Baked", "Brewed", "Churned", "Cogitated", "Cooked", "Crunched", "Saut\u00e9ed", "Worked"];
1255
+ const CLAUDE_SIXTY = "(?:[0-9]|[1-5][0-9])";
1256
+ const CLAUDE_NONZERO_SIXTY = "(?:[1-9]|[1-5][0-9])";
1257
+ const CLAUDE_HOURS = "(?:[0-9]|1[0-9]|2[0-3])";
1258
+ const CLAUDE_NONZERO_HOURS = "(?:[1-9]|1[0-9]|2[0-3])";
1259
+ const CLAUDE_DURATION = `(?:0\\.0s|${CLAUDE_SIXTY}s|${CLAUDE_NONZERO_SIXTY}m ${CLAUDE_SIXTY}s`
1260
+ + `|${CLAUDE_NONZERO_HOURS}h ${CLAUDE_SIXTY}m ${CLAUDE_SIXTY}s|[1-9][0-9]*d ${CLAUDE_HOURS}h ${CLAUDE_SIXTY}m)`;
1261
+ 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?)";
1262
+ const CLAUDE_COMPLETION_ROW = new RegExp(`^✻ (?:${CLAUDE_COMPLETION_VERBS.join("|")}) for ${CLAUDE_DURATION} · done ${CLAUDE_CLOCK}$`);
1263
+ /** The idle allow-list. Each entry comes from real Claude Code 2.1.296 and Codex 0.160.0 frames
1264
+ * (test/fixtures/input-box, including idle seats OpenRig launched: openrig-seat-*), or, where no frame was captured,
1265
+ * 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
1266
+ * frame that matches none of them is not proven idle. */
1267
+ const SEAT_IDLE_SHAPES = {
1268
+ claude: {
1269
+ // The box's opening rule. Plain (the captured frames: a seat started without a name), or carrying this seat's
1270
+ // own session name: OpenRig launches every Claude seat with `--name <session>` (claude-code-adapter.ts), which
1271
+ // Claude Code draws on the box's top border as `─…─ <name> ─`, as wide as the closing rule (2.1.296 bundle:
1272
+ // the prompt-border banner draws the rule, the name padded by one space each side, then one `─`). Real frames
1273
+ // of OpenRig seats from earlier Claude Code versions end it with `──` (session-transport.test.ts,
1274
+ // fixtures/walk-staged-specimen), so one or two are accepted; real 2.1.296 OpenRig seats (openrig-seat-*) draw
1275
+ // one. Any other name, or a truncated one, is unknown.
1276
+ openingRules: {
1277
+ plain: /^─{10,}$/,
1278
+ named: (sessionName) => new RegExp(`^─+ ${escapeRegExp(sessionName)} ─{1,2}$`),
1279
+ },
1280
+ // The footer under an empty box when no turn runs, one per permission posture claudePostureFlag can launch
1281
+ // (yolo-mode.ts: --dangerously-skip-permissions, or --permission-mode with any mode Claude Code 2.1.296 offers).
1282
+ // Bypass and accept edits are real (openrig-seat-*; bypass also after-enter-15s.J, idle-fresh-suggestion). The
1283
+ // others are drawn by the 2.1.296 bundle's footer (the mode's symbol and indicator plus ` on`,
1284
+ // ` (shift+tab to cycle)` for every mode but default, then ` · ← for agents` while the box is empty); accept edits
1285
+ // also matches a real 2.1.220 capture (claude-warning-footer-readiness.test.ts). OpenRig's statusLine makes the footer drop its hints
1286
+ // (`? for shortcuts`, `esc to interrupt`), so these rows are the same whether or not a turn runs: the status
1287
+ // slot above the box (`statusSlot` below) is what tells a running turn apart.
1288
+ footers: {
1289
+ bypassPermissions: "⏵⏵ bypass permissions on (shift+tab to cycle) · ← for agents",
1290
+ acceptEdits: "⏵⏵ accept edits on (shift+tab to cycle) · ← for agents",
1291
+ auto: "⏵⏵ auto mode on (shift+tab to cycle) · ← for agents",
1292
+ dontAsk: "⏵⏵ don't ask on (shift+tab to cycle) · ← for agents",
1293
+ plan: "⏸ plan mode on (shift+tab to cycle) · ← for agents",
1294
+ default: "⏸ manual mode on · ← for agents",
1295
+ },
1296
+ // The rows OpenRig's statusLine collector (provisionContextCollector, assets/claude-statusline-context.cjs) draws
1297
+ // between the closing rule and the footer: none. It prints nothing to stdout, and Claude Code 2.1.296 draws no
1298
+ // row for an empty statusLine in the classic renderer OpenRig launches with (CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN=1).
1299
+ // A statusLine that prints anything (a person's own command) is not on this list.
1300
+ statusLineRows: [],
1301
+ // The one row between the box's two rules: empty (after-enter-15s.J), or the Try suggestion (idle-fresh-suggestion).
1302
+ boxes: [/^❯$/, /^❯\s+Try "[^"]*"$/],
1303
+ // The row just above the opening rule: blank (after-enter-15s.J), the effort row (idle-fresh-suggestion), or a
1304
+ // tmux notice (cleared-without-enter.J, staged-short).
1305
+ notices: ["", "◐ medium · /effort",
1306
+ "tmux detected · scroll with PgUp/PgDn · or add 'set -g mouse on' to ~/.tmux.conf for wheel scroll",
1307
+ "tmux focus-events off · add 'set -g focus-events on' to ~/.tmux.conf and reattach for focus tracking"],
1308
+ // The status slot, the row above that, read together with the rows above it when it is blank: the first
1309
+ // non-blank row at or above the slot must be one of these. With OpenRig's statusLine the footer shows no running
1310
+ // signal, so this slot is what tells a running turn apart, and the 2.1.296 bundle says what a running turn leaves
1311
+ // there. Its REPL draws the live status row (the spinner) only while
1312
+ // `isLoading && (!hasDisplayedStreamingText || tailBlank || …)`: once streamed text with a complete line is on
1313
+ // screen it hides the spinner, and draws nothing in its place (the slot component renders only a retry banner,
1314
+ // or null), so the last streamed row moves down into the slot. That row is never the completion row: Claude Code
1315
+ // draws a message behind a two-cell `●` column (continuation rows two spaces in), the user's echo after `❯ `,
1316
+ // tool output indented. But the bundle does not show that the last streamed row is never blank (the streamed
1317
+ // markdown is laid out in a column with a one-row gap, and its unfinished last line is hidden), so a blank slot
1318
+ // under transcript text is not proof of an idle seat and reads unknown. A blank slot counts only where nothing
1319
+ // of a newer turn can be under the row it sits below:
1320
+ // - finishedTurn: the completion row at column 0 (CLAUDE_COMPLETION_ROW). In the classic renderer OpenRig
1321
+ // launches Claude with, the box follows the transcript, so an idle seat that has finished a turn shows that
1322
+ // turn's completion row in the slot itself (openrig-seat-*: real OpenRig-launched seats); in the fullscreen
1323
+ // renderer the box is pinned to the bottom and blank rows sit between them (after-enter-15s.J). A newer turn
1324
+ // draws its echo, its status row or its `●` rows under the completion row.
1325
+ // - freshSession: the last row of the welcome banner of Claude Code v2.1.296, before any turn (idle-fresh-
1326
+ // suggestion, cleared-without-enter.J).
1327
+ // Any other row reads unknown.
1328
+ statusSlot: {
1329
+ finishedTurn: (lines, at) => CLAUDE_COMPLETION_ROW.test(lines[at] ?? ""),
1330
+ freshSession: (lines, at) => at >= 2 && /^ ▝▝ {3}▝▝ {3}[~/]/.test(lines[at])
1331
+ && /^▝▜██████▀ {2}\S/.test(lines[at - 1]) && /^ ▐▛███▛█ {3}Claude Code v2\.1\.296$/.test(lines[at - 2]),
1332
+ },
1333
+ },
1334
+ codex: {
1335
+ // The hint row under the status line of an empty box (product-path-idle.J, idle).
1336
+ hints: [/^\? for shortcuts$/, /^\? for shortcuts\s+⚠ \d+ warnings? · f2 to view$/],
1337
+ // The last non-blank row above the empty box: the completion row Codex adds when a turn completes
1338
+ // (after-enter-15s.J; its grammar is CODEX_COMPLETION_ROW above), or the greeting under the session header
1339
+ // before any turn (idle, product-path-idle.J, cleared-without-enter.J). A running or streaming turn draws rows
1340
+ // below either.
1341
+ above: {
1342
+ finishedTurn: (lines, at) => CODEX_COMPLETION_ROW.test(lines[at] ?? ""),
1343
+ freshSession: (lines, at) => {
1344
+ if (!/^ {2}\S/.test(lines[at] ?? ""))
1345
+ return false;
1346
+ let i = at - 1;
1347
+ while (i >= 0 && !lines[i].trim())
1348
+ i--;
1349
+ return i >= 2 && i < at - 1 && /^ {2}permissions: \S/.test(lines[i]) && /^ {5}[~/]/.test(lines[i - 1])
1350
+ && /^ {2}>_ OpenAI Codex \(v0\.160\.0\)$/.test(lines[i - 2]);
1351
+ },
1352
+ },
1353
+ },
1354
+ };
1355
+ function claudeSeatIdle(lines, sessionName) {
1356
+ const idle = SEAT_IDLE_SHAPES.claude;
1357
+ let footer = lines.length - 1;
1358
+ while (footer >= 0 && !lines[footer].trim())
1359
+ footer--;
1360
+ const closing = footer - 1 - idle.statusLineRows.length;
1361
+ if (closing < 4)
1362
+ return false;
1363
+ const width = (row) => [...row].length;
1364
+ const plainRule = (row) => idle.openingRules.plain.test(row);
1365
+ const openingRule = (row) => plainRule(row)
1366
+ || (!!sessionName && idle.openingRules.named(sessionName).test(row) && width(row) === width(lines[closing]));
1367
+ return Object.values(idle.footers).includes(lines[footer].trim())
1368
+ && idle.statusLineRows.every((row, i) => lines[closing + 1 + i].trim() === row) && plainRule(lines[closing])
1369
+ && idle.boxes.some((box) => box.test(lines[closing - 1])) && openingRule(lines[closing - 2])
1370
+ && idle.notices.includes(lines[closing - 3].trim()) && claudeStatusSlotIdle(lines, closing - 4);
1371
+ }
1372
+ /** The status slot (see SEAT_IDLE_SHAPES.claude.statusSlot): its own row, or, when it is blank, the first non-blank
1373
+ * row above it. */
1374
+ function claudeStatusSlotIdle(lines, slot) {
1375
+ let at = slot;
1376
+ while (at >= 0 && !lines[at].trim())
1377
+ at--;
1378
+ return at >= 0 && Object.values(SEAT_IDLE_SHAPES.claude.statusSlot).some((shape) => shape(lines, at));
1379
+ }
1380
+ function codexSeatIdle(pane) {
1381
+ const box = codexInputBox(pane);
1382
+ if (box === null || box.text)
1383
+ return false;
1384
+ const lines = box.lines.map((row) => row.trimEnd());
1385
+ let hint = lines.length - 1;
1386
+ while (hint >= 0 && !lines[hint].trim())
1387
+ hint--;
1388
+ if (!SEAT_IDLE_SHAPES.codex.hints.some((row) => row.test(lines[hint].trim())))
1389
+ return false;
1390
+ let above = box.inputAt - 1;
1391
+ while (above >= 0 && !lines[above].trim())
1392
+ above--;
1393
+ return Object.values(SEAT_IDLE_SHAPES.codex.above).some((shape) => shape(lines, above));
1394
+ }
1395
+ // The client versions the idle layouts and box readers were recorded from, and the rows in which each client names
1396
+ // the version it runs: Claude Code's welcome banner (`Claude Code v2.1.296`), Codex's session header
1397
+ // (`>_ OpenAI Codex (v0.160.0)`) and its update notice, whose first version is the running one
1398
+ // (`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
1399
+ // layouts. Most frames name no version (the banner and header scroll away), and then a matching layout is evidence
1400
+ // about the layout only, not proof of the client version: another version that draws the same rows reads the same.
1401
+ const RECORDED_CLIENT_VERSION = { "claude-code": "2.1.296", codex: "0.160.0" };
1402
+ const VISIBLE_CLIENT_VERSION = {
1403
+ "claude-code": [/Claude Code v(\d[\w.+-]*)/g],
1404
+ codex: [/OpenAI Codex \(v(\d[\w.+-]*)\)/g, /Update available! (\d[\w.+-]*) -> /g],
1405
+ };
1406
+ /** Whether a frame visibly names a client version other than the recorded one (see RECORDED_CLIENT_VERSION). */
1407
+ function namesAnotherClientVersion(pane, runtime) {
1408
+ return VISIBLE_CLIENT_VERSION[runtime].some((pattern) => [...pane.matchAll(pattern)].some((match) => match[1] !== RECORDED_CLIENT_VERSION[runtime]));
1409
+ }
1410
+ /** The seat before the paste (see SeatReading), read from the capture the transport already takes before it.
1411
+ * `sessionName` is the seat's own session name, which a Claude Code seat launched by OpenRig draws on its box.
1412
+ * A frame that names another client version is `unknown`. */
1413
+ export function readSeatBeforePaste(pane, runtime, sessionName) {
1414
+ if (!pane?.trim())
1415
+ return "unknown";
1416
+ const lines = pane.split("\n").map((row) => row.trimEnd());
1417
+ if (lines.some((row) => SEAT_NOT_IDLE_ROWS[runtime].some((pattern) => pattern.test(row.trim()))))
1418
+ return "not-idle";
1419
+ if (namesAnotherClientVersion(pane, runtime))
1420
+ return "unknown";
1421
+ return (runtime === "codex" ? codexSeatIdle(pane) : claudeSeatIdle(lines, sessionName)) ? "idle" : "unknown";
1422
+ }
1423
+ /** What a Claude Code or Codex input box shows, read from the input box only (never the transcript). Labels and
1424
+ * placeholders (Claude's queued label and Try suggestion, collapsed pastes, Codex's empty-box placeholder) are
1425
+ * checked before the sent text, so none of them can read as `exact`. The sent text is `exact` only with its word
1426
+ * boundaries kept, and `ambiguous` where the box's row breaks leave them uncertain (boxHoldsText). A frame that
1427
+ * names another client version reads `unrecognized` (RECORDED_CLIENT_VERSION).
1428
+ *
1429
+ * An empty box also reads `queued` where the frames say the message may wait behind a turn: Codex's
1430
+ * pending-input rows above the box, rows between Codex's running-turn status row and the box, or
1431
+ * `beforeEnter` (the frame read just before Enter) showing a running turn (Codex's status row, or
1432
+ * `claudeTurnRunning`). These readings decide the verdict: verified needs `empty` after Enter. For a seat that
1433
+ * was not idle before the paste they only say why the send is unconfirmed (readSeatBeforePaste already rules it
1434
+ * out), but for a seat that was idle they are the only check that rejects a message queued behind a turn that
1435
+ * started after the pre-paste capture and shows in the frame read before Enter.
1436
+ * Used only to report a send with transport.verify_input_box on. */
1437
+ export function readInputBox(pane, runtime, text, beforeEnter) {
1438
+ if (!pane?.trim())
1439
+ return "unavailable";
1440
+ if (namesAnotherClientVersion(pane, runtime))
1441
+ return "unrecognized";
1442
+ if (runtime === "codex") {
1443
+ const box = codexInputBox(pane);
1444
+ if (box === null)
1445
+ return "unrecognized";
1446
+ if (!box.text) {
1447
+ const before = beforeEnter ? codexInputBox(beforeEnter) : null;
1448
+ const status = codexTurnStatusAt(box);
1449
+ const rowsBehindTurn = status >= 0 && box.lines.slice(status + 1, box.inputAt).some((row) => row.trim());
1450
+ const pendingInput = box.lines.slice(0, box.inputAt).some((row) => CODEX_PENDING_INPUT_ROW.test(row.trim()));
1451
+ return pendingInput || rowsBehindTurn || (before !== null && codexTurnStatusAt(before) >= 0) ? "queued" : "empty";
1452
+ }
1453
+ if (CODEX_COLLAPSED_PASTE.test(box.text))
1454
+ return "hidden";
1455
+ return boxHoldsText(box.rows, text);
1456
+ }
1457
+ const { body, markerLine, closingRuleLine } = composerRegion(pane, runtime);
1458
+ if (body === null)
1459
+ return "unrecognized";
1460
+ if (!body)
1461
+ return beforeEnter && claudeTurnRunning(beforeEnter) ? "queued" : "empty";
1462
+ if (body === QUEUED_PLACEHOLDER)
1463
+ return "queued";
1464
+ if (COLLAPSED_PASTE.test(body))
1465
+ return "hidden";
1466
+ if (CLAUDE_TRY_SUGGESTION.test(body))
1467
+ return "other";
1468
+ // composerRegion's body has every space removed (the startup guard's matcher, unchanged); identity reads the rows.
1469
+ return boxHoldsText(stripBoxGlyph(pane.split("\n").slice(markerLine - 1, closingRuleLine - 1)), text);
1470
+ }
1471
+ /** The evidence of a verified send whose path does not read the input box (an audited prompt answer). */
1472
+ const INPUT_BOX_UNCHECKED = { seat: "unchecked", before: "unchecked", after: "unchecked", sameProcess: false };
737
1473
  export class SessionTransport {
738
1474
  db;
1475
+ guardedDelivery;
739
1476
  rigRepo;
740
1477
  sessionRegistry;
741
1478
  tmuxAdapter;
@@ -749,6 +1486,9 @@ export class SessionTransport {
749
1486
  activityEndpointFile;
750
1487
  captureObserver;
751
1488
  listProcesses;
1489
+ runtimeAdapters;
1490
+ inFlightReconciliations = new Map();
1491
+ verifyInputBox;
752
1492
  constructor(deps) {
753
1493
  this.db = deps.db;
754
1494
  this.rigRepo = deps.rigRepo;
@@ -764,6 +1504,17 @@ export class SessionTransport {
764
1504
  this.activityEndpointFile = deps.activityEndpointFile ?? (() => null);
765
1505
  this.captureObserver = deps.captureObserver;
766
1506
  this.listProcesses = deps.listProcesses;
1507
+ this.runtimeAdapters = deps.runtimeAdapters ?? {};
1508
+ this.verifyInputBox = deps.verifyInputBox;
1509
+ const guard = this.tmuxAdapter.deliveryGuard;
1510
+ if (guard && this.db.prepare("PRAGMA table_info(outbox_entries)").all().some(c => c.name === "guard_delivery"))
1511
+ this.guardedDelivery = new DraftAwareDelivery(this.db, guard, (session, text, opts) => this.sendUnguarded(session, text, opts));
1512
+ }
1513
+ configureGuardedWakes(applicable) {
1514
+ this.guardedDelivery?.configureWakes(applicable);
1515
+ }
1516
+ attachRuntimeAdapters(adapters) {
1517
+ this.runtimeAdapters = { ...this.runtimeAdapters, ...adapters };
767
1518
  }
768
1519
  /** Freeze the registration and binding before asking tmux about this name. */
769
1520
  absenceProbeTarget(sessionName) {
@@ -1037,14 +1788,30 @@ export class SessionTransport {
1037
1788
  const pref = guard.preference(target.nodeId);
1038
1789
  return pref.desired || pref.effective ? target : null;
1039
1790
  }
1791
+ draftAwareTarget(sessionName) {
1792
+ const guard = this.tmuxAdapter.deliveryGuard;
1793
+ const target = guard?.maybeTarget(sessionName);
1794
+ if (!guard || !target)
1795
+ return null;
1796
+ const policy = guard.configuration(target.nodeId);
1797
+ return policy.desired.mode === "draft-aware" || policy.effective.mode === "draft-aware" ? target : null;
1798
+ }
1799
+ /** Deliver text and Enter to the resolved recipient under the send guards.
1800
+ * With verification requested, compare rendered message counts, excluding
1801
+ * agent input; a confirmed render does not prove agent consumption. */
1040
1802
  async send(sessionName, text, opts) {
1041
1803
  const guard = this.tmuxAdapter.deliveryGuard;
1042
- if (!guard)
1043
- return this.sendUnguarded(sessionName, text, opts);
1804
+ if (!guard) {
1805
+ const result = await this.sendUnguarded(sessionName, text, opts);
1806
+ if (result.ok && result.sent !== false) {
1807
+ await this.reconcileStartupReadiness(sessionName).catch(() => { });
1808
+ }
1809
+ return result;
1810
+ }
1044
1811
  const outbox = new OutboxHandler(this.db);
1045
1812
  const ids = opts?.committedOutboxIds ?? [opts?.deliveryId ?? `guard-send-${randomUUID()}`];
1046
- const retainedResult = () => ({ ok: true, sessionName, outcome: "retained", sent: false, verified: false,
1047
- outboxIds: ids, reason: "typing_guard_enabled", warning: `Retained, not delivered. Inspect with rig seat held-messages ${sessionName}; disabling does not replay held messages.` });
1813
+ const retainedResult = (reason = "typing_guard_enabled") => ({ ok: true, sessionName, outcome: "retained", sent: false, verified: false,
1814
+ outboxIds: ids, reason, warning: `Retained, not delivered. Inspect with rig seat held-messages ${sessionName}; changing protection does not replay held messages.` });
1048
1815
  try {
1049
1816
  if (opts?.committedOutboxIds) {
1050
1817
  const target = guard.target(sessionName);
@@ -1057,6 +1824,9 @@ export class SessionTransport {
1057
1824
  }
1058
1825
  }
1059
1826
  }
1827
+ const existing = this.guardedDelivery?.existing(sessionName, text, opts ?? {}, ids);
1828
+ if (existing)
1829
+ return existing;
1060
1830
  // Idempotent readback also after disabling: an old retained ID never becomes a new send.
1061
1831
  // (P2: this readback is NOT a new retention and is outside the retained_no_write seam.)
1062
1832
  if (!opts?.committedOutboxIds && opts?.deliveryId) {
@@ -1069,7 +1839,53 @@ export class SessionTransport {
1069
1839
  return retainedResult();
1070
1840
  }
1071
1841
  }
1072
- return await guard.operation(sessionName, () => this.sendUnguarded(sessionName, text, opts), async (target) => {
1842
+ // #1084 — the idle wait must not occupy the seat's serial lease: a long
1843
+ // --wait-for-idle inside guard.operation holds up other sends and queue
1844
+ // creates to this seat for the whole wait. The lease-free wait, main's
1845
+ // quick refusals ahead of it, and the occupant binding all live in
1846
+ // preWaitForIdle; the lease is entered only for the idle re-check +
1847
+ // write. A null return means go through guard.operation exactly as on
1848
+ // main (guard held, target unresolvable, or a quick refusal).
1849
+ let sendOpts = opts;
1850
+ if (opts?.waitForIdleMs !== undefined && !opts.submitOnly && !opts.preWaitedIdle) {
1851
+ const pre = await this.preWaitForIdle(sessionName, opts, guard);
1852
+ if (pre === null) {
1853
+ // Fall through with today's path (in-lease wait / retain / throw).
1854
+ }
1855
+ else if (!pre.ok) {
1856
+ return pre.failure;
1857
+ }
1858
+ else {
1859
+ // Occupant check (review round 3): the lease starts only after the
1860
+ // pre-wait, so refuse here when the seat changed hands meanwhile —
1861
+ // exactly as main refuses with guard_target_changed. Retention must
1862
+ // not adopt the new occupant either, so this runs before operation.
1863
+ const current = guard.target(sessionName);
1864
+ if (JSON.stringify(pre.bound) !== JSON.stringify(current)) {
1865
+ return {
1866
+ ok: false,
1867
+ sessionName,
1868
+ sent: false,
1869
+ reason: "guard_target_changed",
1870
+ error: "Input target/occupant changed; no input written.",
1871
+ activity: pre.evidence.activity,
1872
+ waitedMs: pre.evidence.waitedMs,
1873
+ attempts: pre.evidence.attempts,
1874
+ };
1875
+ }
1876
+ sendOpts = { ...opts, preWaitedIdle: pre.evidence, preWaitBound: pre.bound };
1877
+ }
1878
+ }
1879
+ const result = await guard.operation(sessionName, async () => {
1880
+ if (this.guardedDelivery && !sendOpts?.submitOnly && guard.needsInputCheck(sessionName)) {
1881
+ return this.guardedDelivery.send(sessionName, text, sendOpts ?? {}, ids);
1882
+ }
1883
+ const prior = this.guardedDelivery?.existing(sessionName, text, sendOpts ?? {}, ids);
1884
+ return prior ?? this.sendUnguarded(sessionName, text, sendOpts);
1885
+ }, async (target) => {
1886
+ const prior = this.guardedDelivery?.existing(sessionName, text, sendOpts ?? {}, ids);
1887
+ if (prior)
1888
+ return prior;
1073
1889
  if (opts?.submitOnly)
1074
1890
  return { ok: false, sessionName, sent: false, reason: "typing_guard_enabled", error: "Typing guard prevents submit-only; no Enter was sent." };
1075
1891
  this.db.transaction(() => {
@@ -1079,6 +1895,7 @@ export class SessionTransport {
1079
1895
  throw new Error("Committed wake target/ID mismatch");
1080
1896
  outbox.retain(prior ? { ...prior, outboxId: id, tags: prior.tags ?? undefined, auditPointer: prior.auditPointer ?? undefined } : {
1081
1897
  outboxId: id, senderSession: opts?.actorSession ?? "unknown", destinationSession: sessionName, body: text, auditPointer: opts?.auditPointer,
1898
+ identityProvenance: opts?.identityProvenance,
1082
1899
  }, target, !!opts?.committedOutboxIds);
1083
1900
  }
1084
1901
  })();
@@ -1099,11 +1916,156 @@ export class SessionTransport {
1099
1916
  }
1100
1917
  return retainedResult();
1101
1918
  });
1919
+ if (result.ok && result.sent !== false) {
1920
+ await this.reconcileStartupReadiness(sessionName).catch(() => { });
1921
+ }
1922
+ return result;
1102
1923
  }
1103
1924
  catch (error) {
1925
+ // A refusal thrown after the text was written (for example a guard check before Enter) is not "nothing sent".
1926
+ if (error.inputWritten === true) {
1927
+ // Only a throw before the Enter key reached the adapter positively means Enter was never sent.
1928
+ const enter = error.enterAttempted === true ? "the submission (Enter) outcome is unconfirmed" : "Enter was not sent";
1929
+ return { ok: false, sessionName, sent: true, outcome: "failed", inputPhase: "written", reason: error.code ?? "submit_refused",
1930
+ error: `Text was written to '${sessionName}' but ${enter}: ${error.message}` };
1931
+ }
1932
+ // A throw after the text write was issued: whether it landed is unknown, so it is not "nothing sent" either.
1933
+ if (error.inputAttempted === true) {
1934
+ return { ok: false, sessionName, outcome: "failed", inputPhase: "attempted", reason: error.code ?? "delivery_indeterminate",
1935
+ error: `A text write to '${sessionName}' was issued, but its result was lost: ${error.message}` };
1936
+ }
1104
1937
  return { ok: false, sessionName, sent: false, reason: error.code ?? "guard_unavailable", error: error.message };
1105
1938
  }
1106
1939
  }
1940
+ /**
1941
+ * #1084/#1103: lease-free idle wait before the serial lease, so a long
1942
+ * --wait-for-idle holds up no other send or queue create to this seat.
1943
+ * Returns the wait evidence plus the occupant bound before the wait (both
1944
+ * threaded into the leased send), a failure result using main's exact
1945
+ * reasons, or null when the send must go through guard.operation exactly
1946
+ * as on main. Order matters: typing-guard decision first, then main's
1947
+ * quick refusals (runtime identity, wait validations, session probe),
1948
+ * then the wait. Only the lookups sit inside try: a late throw surfaces
1949
+ * instead of silently running a second full wait inside the lease.
1950
+ */
1951
+ async preWaitForIdle(sessionName, opts, guard) {
1952
+ const timeoutMs = opts.waitForIdleMs;
1953
+ if (timeoutMs === undefined)
1954
+ return null;
1955
+ let meta;
1956
+ let bound;
1957
+ try {
1958
+ const preTarget = guard.maybeTarget(sessionName);
1959
+ if (!preTarget)
1960
+ return null;
1961
+ const prePref = guard.preference(preTarget.nodeId);
1962
+ // Hold mode retains in the lease, and draft-aware mode reads the input there first and returns its
1963
+ // immediate result (held, or refused with its reason): neither takes the lease-free wait. A
1964
+ // draft-aware send whose input is empty waits for idle inside the lease, as before this wait existed.
1965
+ if (prePref.effective || prePref.desired || prePref.desiredMode === "draft-aware" || prePref.effectiveMode === "draft-aware")
1966
+ return null;
1967
+ meta = this.getSessionMeta(sessionName);
1968
+ bound = guard.target(sessionName);
1969
+ }
1970
+ catch {
1971
+ return null;
1972
+ }
1973
+ // External CLI seats never wait: main refuses them before anything else,
1974
+ // and a probe or wait would only delay that refusal (or mistake it for
1975
+ // an absent session).
1976
+ if (meta.attachmentType === "external_cli") {
1977
+ return { ok: false, failure: {
1978
+ ok: false,
1979
+ sessionName,
1980
+ reason: "transport_unavailable",
1981
+ error: `Session '${sessionName}' is attached as an external CLI node. Inbound tmux transport is unavailable for this target.`,
1982
+ } };
1983
+ }
1984
+ const runtime = meta.runtime;
1985
+ if (runtime && runtime !== "terminal" && runtime !== "claude-code") {
1986
+ const unverified = await this.unverifiedShellForeground(sessionName, runtime, meta.pane, meta.resumeToken);
1987
+ if (unverified) {
1988
+ return { ok: false, failure: {
1989
+ ok: false,
1990
+ sessionName,
1991
+ sent: false,
1992
+ reason: "target_runtime_unverified",
1993
+ error: `Refused: '${sessionName}' reports ${unverified} as the foreground command, but OpenRig could not verify its expected ${runtime} agent in the bound pane. The agent may still be running behind a wrapper. No text was sent.`,
1994
+ } };
1995
+ }
1996
+ }
1997
+ if (opts.force) {
1998
+ return { ok: false, failure: {
1999
+ ok: false,
2000
+ sessionName,
2001
+ reason: "invalid_wait_for_idle",
2002
+ error: "--wait-for-idle cannot be combined with force. No text was sent.",
2003
+ sent: false,
2004
+ } };
2005
+ }
2006
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
2007
+ return { ok: false, failure: {
2008
+ ok: false,
2009
+ sessionName,
2010
+ reason: "invalid_wait_for_idle",
2011
+ error: "waitForIdleMs must be a positive number. No text was sent.",
2012
+ sent: false,
2013
+ } };
2014
+ }
2015
+ try {
2016
+ const target = this.absenceProbeTarget(sessionName);
2017
+ const observedAt = this.now().toISOString();
2018
+ const probe = await this.tmuxAdapter.probeSession(sessionName);
2019
+ if (probe.state === "absent") {
2020
+ this.recordSessionMissingVerdict(target, observedAt);
2021
+ return { ok: false, failure: {
2022
+ ok: false,
2023
+ sessionName,
2024
+ reason: "session_missing",
2025
+ error: `Session '${sessionName}' not found: tmux reports no session with this name. No text was sent. Check available sessions with: rig ps --nodes`,
2026
+ } };
2027
+ }
2028
+ if (probe.state === "transport_unavailable") {
2029
+ return { ok: false, failure: {
2030
+ ok: false,
2031
+ sessionName,
2032
+ reason: "tmux_unavailable",
2033
+ error: `The tmux server could not be reached (${probe.cause}). Whether session '${sessionName}' exists was not determined. No text was sent.`,
2034
+ } };
2035
+ }
2036
+ }
2037
+ catch (err) {
2038
+ return { ok: false, failure: {
2039
+ ok: false,
2040
+ sessionName,
2041
+ reason: "tmux_unavailable",
2042
+ error: `The tmux session probe failed unexpectedly (${err instanceof Error ? err.message : String(err)}). Whether session '${sessionName}' exists was not determined. No text was sent.`,
2043
+ } };
2044
+ }
2045
+ const deadlineAt = Date.now() + timeoutMs;
2046
+ const preWait = await this.waitForIdle({
2047
+ sessionName,
2048
+ runtime: meta.runtime,
2049
+ attachmentType: meta.attachmentType,
2050
+ timeoutMs,
2051
+ binding: { sessionName, nodeId: meta.nodeId, occupant: meta.occupant, pane: meta.pane },
2052
+ readinessFromPaneOnly: opts.readinessFromPaneOnly === true,
2053
+ });
2054
+ if (!preWait.ok) {
2055
+ return { ok: false, failure: {
2056
+ ok: false,
2057
+ sessionName,
2058
+ reason: preWait.reason,
2059
+ error: preWait.error,
2060
+ sent: false,
2061
+ inputPhase: "not_reached",
2062
+ activity: preWait.activity,
2063
+ waitedMs: preWait.waitedMs,
2064
+ attempts: preWait.attempts,
2065
+ } };
2066
+ }
2067
+ return { ok: true, evidence: { activity: preWait.activity, waitedMs: preWait.waitedMs, attempts: preWait.attempts, deadlineAt }, bound };
2068
+ }
1107
2069
  async sendUnguarded(sessionName, text, opts) {
1108
2070
  let preVerifyContent = null;
1109
2071
  const sessionMeta = this.getSessionMeta(sessionName);
@@ -1128,6 +2090,31 @@ export class SessionTransport {
1128
2090
  runtimeAdvisory = `runtime: ${observation.detail}; delivery proceeds without verified native identity.`;
1129
2091
  return null;
1130
2092
  };
2093
+ // #331: with mouse on, scrolling a seat pane puts it in tmux copy mode. A paste still reaches
2094
+ // the input there, but copy mode swallows the Enter, so the send would report ok while the
2095
+ // text sits unsubmitted. Leave the mode once (send-keys -X cancel) and deliver; fail before
2096
+ // typing anything only if the pane still reads as in a mode. An unknown answer keeps today's path.
2097
+ const readPaneInMode = async () => {
2098
+ try {
2099
+ return typeof this.tmuxAdapter.isPaneInMode === "function" ? await this.tmuxAdapter.isPaneInMode(sessionName) : null;
2100
+ }
2101
+ catch {
2102
+ return null;
2103
+ }
2104
+ };
2105
+ const leavePaneMode = async (outcome) => {
2106
+ if (await readPaneInMode() !== true)
2107
+ return null;
2108
+ try {
2109
+ if (typeof this.tmuxAdapter.cancelPaneMode === "function")
2110
+ await this.tmuxAdapter.cancelPaneMode(sessionName);
2111
+ }
2112
+ catch { /* the re-read below decides */ }
2113
+ if (await readPaneInMode() !== true)
2114
+ return null;
2115
+ return { ok: false, sessionName, sent: false, reason: "pane_in_mode",
2116
+ error: `'${sessionName}' is in a tmux mode (such as copy mode) and could not leave it, so Enter would not reach the agent. ${outcome}` };
2117
+ };
1131
2118
  // S01/S02 P2 observation context, frozen at attempt entry before any await.
1132
2119
  const observed = this.captureObserver ? {
1133
2120
  attemptId: randomUUID(),
@@ -1247,7 +2234,7 @@ export class SessionTransport {
1247
2234
  if (!opts.requireFullStagedText || !opts.onStartupMismatch)
1248
2235
  return;
1249
2236
  try {
1250
- const evidence = startupSubmissionEvidence(pane, expected, opts.submitOnlyCaptureLines ?? 50);
2237
+ const evidence = startupSubmissionEvidence(pane, expected, opts.submitOnlyCaptureLines ?? 50, runtime);
1251
2238
  if (evidence)
1252
2239
  opts.onStartupMismatch(evidence);
1253
2240
  }
@@ -1262,8 +2249,8 @@ export class SessionTransport {
1262
2249
  throw error; // Preserve the existing guarded/unguarded error handling.
1263
2250
  }
1264
2251
  const staged = opts.requireFullStagedText
1265
- ? inspectStartupStagedText(pane, expected) === "staged"
1266
- : hasExpectedStagedText(pane, expected);
2252
+ ? inspectStartupStagedText(pane, expected, runtime) === "staged"
2253
+ : hasExpectedStagedText(pane, expected, runtime);
1267
2254
  if (!staged) {
1268
2255
  recordMismatch(pane);
1269
2256
  return {
@@ -1273,16 +2260,27 @@ export class SessionTransport {
1273
2260
  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.`,
1274
2261
  };
1275
2262
  }
2263
+ this.tmuxAdapter.deliveryGuard?.expectStagedInput(sessionName, expected);
1276
2264
  const targetFailure = await checkClaudeTarget();
1277
2265
  if (targetFailure)
1278
2266
  return targetFailure;
2267
+ const copyModeFailure = await leavePaneMode("Nothing was submitted.");
2268
+ if (copyModeFailure)
2269
+ return copyModeFailure;
1279
2270
  const submitResult = await this.runStage("session_transport.submit", () => this.tmuxAdapter.sendKeys(sessionName, ["Enter"]), (result) => result.ok ? "ok" : "failed");
1280
2271
  if (!submitResult.ok) {
1281
2272
  return { ok: false, sessionName, reason: "submit_failed", outcome: "failed", error: `submitOnly: Enter did not land on '${sessionName}': ${submitResult.message}` };
1282
2273
  }
2274
+ try {
2275
+ opts.onInputEffect?.("submitted");
2276
+ }
2277
+ catch { /* Observation cannot change an input result. */ }
1283
2278
  return observe({ ok: true, sessionName, outcome: "rendered-unconfirmed", submitOnly: true });
1284
2279
  }
1285
- if (waitForIdleMs !== undefined) {
2280
+ // #1084: when send() already waited lease-free (preWaitedIdle), the wait
2281
+ // below is skipped; otherwise wait here, exactly as before
2282
+ // (typing-guard flip and unresolvable-target races keep today's behavior).
2283
+ if (waitForIdleMs !== undefined && !opts?.preWaitedIdle) {
1286
2284
  const waitResult = await this.waitForIdle({
1287
2285
  sessionName,
1288
2286
  runtime,
@@ -1303,10 +2301,111 @@ export class SessionTransport {
1303
2301
  reason: waitResult.reason,
1304
2302
  error: waitResult.error,
1305
2303
  sent: false,
2304
+ inputPhase: "not_reached",
1306
2305
  ...waitEvidence,
1307
2306
  };
1308
2307
  }
1309
2308
  }
2309
+ else if (opts?.preWaitedIdle && waitForIdleMs !== undefined) {
2310
+ // Occupant check (review round 3): the lease started only after the
2311
+ // pre-wait, so refuse when the seat changed hands meanwhile, exactly
2312
+ // as main refuses with guard_target_changed. No new refusal.
2313
+ const preGuard = this.tmuxAdapter.deliveryGuard;
2314
+ if (preGuard && opts.preWaitBound) {
2315
+ const current = preGuard.target(sessionName);
2316
+ if (JSON.stringify(opts.preWaitBound) !== JSON.stringify(current)) {
2317
+ return {
2318
+ ok: false,
2319
+ sessionName,
2320
+ sent: false,
2321
+ reason: "guard_target_changed",
2322
+ error: "Input target/occupant changed; no input written.",
2323
+ activity: opts.preWaitedIdle.activity,
2324
+ waitedMs: opts.preWaitedIdle.waitedMs,
2325
+ attempts: opts.preWaitedIdle.attempts,
2326
+ };
2327
+ }
2328
+ }
2329
+ // Idle re-check (review round 1 + bot round 2): the wait-mode contract
2330
+ // holds at type time too — only idle types. The budget is measured
2331
+ // from the ORIGINAL deadline (lease-queue time counts too), so the
2332
+ // total wait never exceeds what the caller asked for. Below one poll
2333
+ // interval, waiting buys nothing: one unraced observation decides, so
2334
+ // an idle seat at a dead budget still sends instead of losing a race
2335
+ // against a zero timer.
2336
+ const preEvidence = opts.preWaitedIdle;
2337
+ const deadlineRemainingMs = preEvidence.deadlineAt - Date.now();
2338
+ if (deadlineRemainingMs > this.waitForIdlePollMs) {
2339
+ const rewait = await this.waitForIdle({
2340
+ sessionName,
2341
+ runtime,
2342
+ attachmentType: sessionMeta.attachmentType,
2343
+ timeoutMs: deadlineRemainingMs,
2344
+ // Message-only: the re-wait runs on the leftover budget, but the
2345
+ // timeout names the configured --wait-for-idle value.
2346
+ timeoutLabelMs: waitForIdleMs,
2347
+ binding: observed?.binding,
2348
+ readinessFromPaneOnly: opts.readinessFromPaneOnly === true,
2349
+ });
2350
+ waitEvidence = {
2351
+ activity: rewait.activity,
2352
+ waitedMs: preEvidence.waitedMs + rewait.waitedMs,
2353
+ attempts: preEvidence.attempts + rewait.attempts,
2354
+ };
2355
+ if (!rewait.ok) {
2356
+ return {
2357
+ ok: false,
2358
+ sessionName,
2359
+ reason: rewait.reason,
2360
+ error: rewait.error,
2361
+ sent: false,
2362
+ inputPhase: "not_reached",
2363
+ ...waitEvidence,
2364
+ };
2365
+ }
2366
+ }
2367
+ else {
2368
+ const recheck = await this.classifySendReadiness({
2369
+ sessionName,
2370
+ runtime,
2371
+ attachmentType: sessionMeta.attachmentType,
2372
+ binding: observed?.binding,
2373
+ readinessFromPaneOnly: opts.readinessFromPaneOnly === true,
2374
+ });
2375
+ if (recheck.state !== "idle") {
2376
+ const late = recheck.state === "needs_input"
2377
+ ? {
2378
+ reason: "target_needs_input",
2379
+ error: `Refused: '${sessionName}' is at an interactive prompt (${recheck.reason}). A message must not select or approve it. To deliberately drive the prompt: rig send ${sessionName} "<text>" --dangerously-interact --reason "<why>". No text was sent.`,
2380
+ }
2381
+ : recheck.state === "unknown"
2382
+ ? {
2383
+ reason: "target_activity_unknown",
2384
+ error: `Target activity could not be determined (${recheck.reason}) when the ${waitForIdleMs}ms wait ended. No text was sent.`,
2385
+ }
2386
+ : {
2387
+ reason: "wait_for_idle_timeout",
2388
+ error: `Target was still busy (${recheck.reason}) when the ${waitForIdleMs}ms wait ended. No text was sent.`,
2389
+ };
2390
+ return {
2391
+ ok: false,
2392
+ sessionName,
2393
+ sent: false,
2394
+ inputPhase: "not_reached",
2395
+ reason: late.reason,
2396
+ error: late.error,
2397
+ activity: recheck,
2398
+ waitedMs: preEvidence.waitedMs,
2399
+ attempts: preEvidence.attempts + 1,
2400
+ };
2401
+ }
2402
+ waitEvidence = {
2403
+ activity: recheck,
2404
+ waitedMs: preEvidence.waitedMs,
2405
+ attempts: preEvidence.attempts + 1,
2406
+ };
2407
+ }
2408
+ }
1310
2409
  // 2. OPR.0.4.1.10 — robust prompt/permission + mid-work guard on the DEFAULT path.
1311
2410
  // Runs the same detector previously reachable only via --wait-for-idle: fresh runtime-hook primary
1312
2411
  // (within the send-readiness window) + hardened capture-pane fallback. This closes the rig-send
@@ -1319,7 +2418,7 @@ export class SessionTransport {
1319
2418
  // the positive-picker guard (FR-4 — the footgun separation). The advisory is carried on the
1320
2419
  // success result via `warning` so the honest telemetry is surfaced.
1321
2420
  let sendAdvisory;
1322
- let promptOverride = false;
2421
+ const promptOverride = waitForIdleMs === undefined && Boolean(opts?.dangerouslyInteract);
1323
2422
  if (waitForIdleMs === undefined) {
1324
2423
  const readiness = await this.classifySendReadiness({
1325
2424
  sessionName,
@@ -1327,42 +2426,37 @@ export class SessionTransport {
1327
2426
  attachmentType: sessionMeta.attachmentType,
1328
2427
  binding: observed?.binding,
1329
2428
  });
1330
- // Single state dispatch (B1 code-review fix): flattened so `unknown` ALWAYS attaches the advisory
1331
- // regardless of whether --dangerously-interact was passed — the deliberate-override branch no
1332
- // longer bypasses unknown handling.
1333
- if (readiness.state === "needs_input") {
1334
- // The POSITIVE picker/approval footgun. --dangerously-interact is the deliberate audited
1335
- // override (reason required + an auditable record persisted BEFORE the send; fail closed if it
1336
- // cannot be audited so an unauditable override never sends). Otherwise refuse with the
1337
- // proceed-path. This is the ONLY state --dangerously-interact bypasses.
1338
- if (opts?.dangerouslyInteract) {
1339
- if (!opts.reason || opts.reason.trim().length === 0) {
1340
- return {
1341
- ok: false,
1342
- sessionName,
1343
- reason: "dangerously_interact_requires_reason",
1344
- error: "--dangerously-interact requires --reason explaining why the prompt is being driven. No text was sent.",
1345
- };
1346
- }
1347
- const audit = this.recordPromptOverride({
2429
+ // Explicit interaction selects key input even when readiness cannot recognize the prompt.
2430
+ // Only bypassing a positive picker requires a successful audit; other states already sent.
2431
+ if (opts?.dangerouslyInteract) {
2432
+ if (readiness.state === "needs_input" && (!opts.reason || opts.reason.trim().length === 0)) {
2433
+ return {
2434
+ ok: false,
1348
2435
  sessionName,
1349
- readiness,
1350
- actorSession: opts.actorSession ?? null,
1351
- overrideReason: opts.reason,
1352
- });
1353
- if (!audit.ok) {
1354
- return {
1355
- ok: false,
1356
- sessionName,
1357
- reason: "prompt_override_audit_unavailable",
1358
- activity: readiness,
1359
- error: `Refused: --dangerously-interact requires an auditable override record, which could not be persisted (${audit.reason}). No text was sent.`,
1360
- };
1361
- }
1362
- // Audited answers use unbracketed input; a choice may submit itself.
1363
- promptOverride = true;
2436
+ reason: "dangerously_interact_requires_reason",
2437
+ error: "--dangerously-interact requires --reason explaining why the prompt is being driven. No text was sent.",
2438
+ };
1364
2439
  }
1365
- else {
2440
+ const audit = this.recordPromptOverride({
2441
+ sessionName,
2442
+ readiness,
2443
+ actorSession: opts.actorSession ?? null,
2444
+ overrideReason: opts.reason ?? null,
2445
+ });
2446
+ if (!audit.ok && readiness.state === "needs_input") {
2447
+ return {
2448
+ ok: false,
2449
+ sessionName,
2450
+ reason: "prompt_override_audit_unavailable",
2451
+ activity: readiness,
2452
+ error: `Refused: --dangerously-interact requires an auditable override record, which could not be persisted (${audit.reason}). No text was sent.`,
2453
+ };
2454
+ }
2455
+ if (!audit.ok)
2456
+ sendAdvisory = "prompt-override: audit unavailable (" + audit.reason + "); input sent anyway.";
2457
+ }
2458
+ if (readiness.state === "needs_input") {
2459
+ if (!promptOverride) {
1366
2460
  return {
1367
2461
  ok: false,
1368
2462
  sessionName,
@@ -1380,7 +2474,7 @@ export class SessionTransport {
1380
2474
  // surfaced without ever blocking communication. Hooks are advisory telemetry, not authority
1381
2475
  // over whether agents can talk.
1382
2476
  const linkDiagnosis = await this.diagnoseProducerLink(sessionName);
1383
- sendAdvisory = `producer-link: ${linkDiagnosis} — activity could not be determined (${readiness.reason}); sent anyway (telemetry is advisory).`;
2477
+ sendAdvisory = [sendAdvisory, `producer-link: ${linkDiagnosis} — activity could not be determined (${readiness.reason}); sent anyway (telemetry is advisory).`].filter(Boolean).join(" ");
1384
2478
  // fall through to the send below.
1385
2479
  }
1386
2480
  else if (readiness.state === "running") {
@@ -1389,15 +2483,20 @@ export class SessionTransport {
1389
2483
  // attach it on the success result + PROCEED (mirrors the unknown inversion). --force is now a
1390
2484
  // no-op here (the option is kept for back-compat). needs_input (positive picker) remains the
1391
2485
  // ONLY hard refuse; unknown/stale/missing already proceed-with-advisory above.
1392
- sendAdvisory = `target pane appears mid-task; sent anyway (busy is advisory, not a block).`;
2486
+ sendAdvisory = [sendAdvisory, "target pane appears mid-task; sent anyway (busy is advisory, not a block)."].filter(Boolean).join(" ");
1393
2487
  // fall through to the send below.
1394
2488
  }
1395
2489
  // idle (or running/unknown — now advisory-and-proceed) → proceed to send.
1396
2490
  }
2491
+ // transport.verify_input_box: a verified Claude Code or Codex send reports on input-box evidence. The
2492
+ // setting is read only for those sends; everything else keeps today's path.
2493
+ const inputBoxRuntime = opts?.verify && (runtime === "claude-code" || runtime === "codex") && this.inputBoxSettingOn()
2494
+ ? runtime : null;
1397
2495
  if (opts?.verify) {
1398
2496
  const captureSeq = observed ? nextCaptureSeq++ : 0;
1399
2497
  try {
1400
- preVerifyContent = await this.runStage("session_transport.pre_capture", () => this.tmuxAdapter.capturePaneContent(sessionName, 30));
2498
+ preVerifyContent = await this.runStage("session_transport.pre_capture", () => runtime === "terminal" || !verificationSnippet(text).envelope ? this.tmuxAdapter.capturePaneContent(sessionName, 30)
2499
+ : this.tmuxAdapter.capturePaneContent(sessionName, 30, { joinWrappedLines: true }));
1401
2500
  if (observed)
1402
2501
  observed.pre = captureSlot(preVerifyContent, this.now().toISOString(), captureSeq);
1403
2502
  }
@@ -1418,51 +2517,99 @@ export class SessionTransport {
1418
2517
  const targetFailure = await checkClaudeTarget();
1419
2518
  if (targetFailure)
1420
2519
  return observe(targetFailure);
2520
+ const copyModeFailure = await leavePaneMode("No text was sent.");
2521
+ if (copyModeFailure)
2522
+ return observe({ ...copyModeFailure, ...(waitMode ? waitEvidence : {}) });
1421
2523
  // 3. Deliver ordinary messages as paste, audited prompt answers as key input.
1422
2524
  if (observed)
1423
2525
  observed.sentHash = hashSentText(text);
2526
+ // Set once the text write has been handed to the adapter: from then on a throw is not "nothing sent".
2527
+ let writeIssued = false;
2528
+ const tagIssued = (error) => {
2529
+ if (writeIssued && error && typeof error === "object")
2530
+ error.inputAttempted = true;
2531
+ throw error;
2532
+ };
1424
2533
  const textResult = await this.runStage("session_transport.send_text", () => {
1425
2534
  opts?.beforeWrite?.();
2535
+ writeIssued = true;
1426
2536
  if (promptOverride)
1427
2537
  return this.tmuxAdapter.sendText(sessionName, text, opts?.beforeWrite, { bracketed: false });
1428
2538
  return opts?.beforeWrite ? this.tmuxAdapter.sendText(sessionName, text, opts.beforeWrite) : this.tmuxAdapter.sendText(sessionName, text);
1429
- }, (result) => result.ok ? "ok" : "failed");
2539
+ }, (result) => result.ok ? "ok" : "failed").catch(tagIssued);
1430
2540
  if (!textResult.ok) {
2541
+ // A draft refusal is the guard holding the write before any input; it is not an attempted write.
2542
+ const policyRefusal = typeof textResult.code === "string" && textResult.code.startsWith("draft_");
1431
2543
  return observe({
1432
2544
  ok: false,
1433
2545
  sessionName,
1434
- reason: "send_failed",
2546
+ reason: policyRefusal ? textResult.code : "send_failed",
1435
2547
  outcome: "failed",
2548
+ ...(policyRefusal ? {} : { inputPhase: "attempted" }),
1436
2549
  error: `Failed to send text to '${sessionName}': ${textResult.message}`,
1437
- ...(waitMode ? { sent: false, ...waitEvidence } : {}),
2550
+ ...(waitMode || policyRefusal ? { sent: false, ...waitEvidence } : {}),
1438
2551
  });
1439
2552
  }
1440
- // 4. Wait 200ms (spike-proven delay)
1441
- await this.sleep(200);
1442
- if (runtime === "claude-code" && bindingChanged())
1443
- return observe(changedRecipient(true));
1444
- if (promptOverride) {
1445
- const pane = await this.runStage("session_transport.prompt_override_pre_submit_capture", () => this.tmuxAdapter.capturePaneContent(sessionName, 50)).catch(() => null);
2553
+ try {
2554
+ opts?.onInputEffect?.("pasted");
2555
+ }
2556
+ catch { /* Observation cannot change a completed paste. */ }
2557
+ // From the successful paste to Enter the text may sit in the seat's input. Any throw in this span is
2558
+ // tagged inputWritten, so no caller reports it as unsent; a step added here later stays covered.
2559
+ let boxBefore = null;
2560
+ let submitResult;
2561
+ let enterIssued = false; // set as the Enter key is handed to the adapter
2562
+ try {
2563
+ // 4. Wait 200ms (spike-proven delay)
2564
+ await this.sleep(200);
2565
+ // With transport.verify_input_box on, one bounded read of the input box and its pane process before
2566
+ // Enter (a 500 ms budget after the 200 ms wait). It only reports: nothing here decides whether Enter is sent.
2567
+ // The seat is read from the existing --verify capture taken before the paste (the whole screen); no new read.
2568
+ if (inputBoxRuntime && !promptOverride) {
2569
+ const read = await this.readWithinDeadline("session_transport.input_box_capture", () => Promise.all([
2570
+ this.tmuxAdapter.getPanePid(sessionName).catch(() => null),
2571
+ this.tmuxAdapter.capturePaneContent(sessionName, 30).catch(() => null),
2572
+ ]));
2573
+ boxBefore = { seat: readSeatBeforePaste(preVerifyContent, inputBoxRuntime, sessionName),
2574
+ reading: readInputBox(read?.[1] ?? null, inputBoxRuntime, text), pid: read?.[0] ?? null, pane: read?.[1] ?? null };
2575
+ }
1446
2576
  if (runtime === "claude-code" && bindingChanged())
1447
2577
  return observe(changedRecipient(true));
1448
- if (!promptAnswerStaged(pane, text, runtime)) {
1449
- return observe({ ok: true, sessionName, sent: true, verified: false, outcome: "rendered-unconfirmed", promptInteraction: "unverified",
1450
- 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)." });
2578
+ if (promptOverride) {
2579
+ const pane = await this.runStage("session_transport.prompt_override_pre_submit_capture", () => this.tmuxAdapter.capturePaneContent(sessionName, 50)).catch(() => null);
2580
+ if (runtime === "claude-code" && bindingChanged())
2581
+ return observe(changedRecipient(true));
2582
+ if (!promptAnswerStaged(pane, text, runtime)) {
2583
+ return observe({ ok: true, sessionName, sent: true, verified: false, outcome: "rendered-unconfirmed", promptInteraction: "unverified",
2584
+ ...(inputBoxRuntime ? { inputBox: INPUT_BOX_UNCHECKED } : {}),
2585
+ 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(" ") });
2586
+ }
2587
+ sendAdvisory = [sendAdvisory, "prompt-override: answer sent as unbracketed input; Enter submitted the complete still-staged answer."].filter(Boolean).join(" ");
2588
+ }
2589
+ // 5. Submit (Enter)
2590
+ 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");
2591
+ if (!submitResult.ok) {
2592
+ const policyRefusal = typeof submitResult.code === "string" && submitResult.code.startsWith("draft_");
2593
+ return observe({
2594
+ ok: false,
2595
+ sessionName,
2596
+ reason: policyRefusal ? submitResult.code : "submit_failed",
2597
+ outcome: "failed",
2598
+ inputPhase: "written",
2599
+ error: `Text is in '${sessionName}', but the Enter reported a failure, so the submission outcome is unconfirmed. The agent may need manual attention.`,
2600
+ ...(waitMode || policyRefusal ? { sent: true, ...waitEvidence } : {}),
2601
+ });
1451
2602
  }
1452
- sendAdvisory = "prompt-override: answer sent as unbracketed input; Enter submitted the complete still-staged answer.";
1453
2603
  }
1454
- // 5. Submit (Enter)
1455
- 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");
1456
- if (!submitResult.ok) {
1457
- return observe({
1458
- ok: false,
1459
- sessionName,
1460
- reason: "submit_failed",
1461
- outcome: "failed",
1462
- error: `Text is visible in '${sessionName}' but was not submitted (Enter failed). The agent may need manual attention.`,
1463
- ...(waitMode ? { sent: true, ...waitEvidence } : {}),
1464
- });
2604
+ catch (error) {
2605
+ if (error && typeof error === "object")
2606
+ Object.assign(error, { inputWritten: true, enterAttempted: enterIssued });
2607
+ throw error;
1465
2608
  }
2609
+ try {
2610
+ opts?.onInputEffect?.("submitted");
2611
+ }
2612
+ catch { /* Observation cannot change a completed submission. */ }
1466
2613
  const interaction = promptOverride ? { promptInteraction: "enter-sent" } : {};
1467
2614
  // 6. Verify if requested. At this point text + Enter BOTH succeeded, so the
1468
2615
  // message LANDED; the capture only re-confirms the render. Not re-confirming
@@ -1472,24 +2619,85 @@ export class SessionTransport {
1472
2619
  await this.sleep(500);
1473
2620
  const captureSeq = observed ? nextCaptureSeq++ : 0;
1474
2621
  try {
1475
- const content = await this.runStage("session_transport.post_capture", () => this.tmuxAdapter.capturePaneContent(sessionName, 30));
2622
+ const content = await this.runStage("session_transport.post_capture", () => runtime === "terminal" || !verificationSnippet(text).envelope ? this.tmuxAdapter.capturePaneContent(sessionName, 30)
2623
+ : this.tmuxAdapter.capturePaneContent(sessionName, 30, { joinWrappedLines: true }));
1476
2624
  if (observed)
1477
2625
  observed.post = captureSlot(content, this.now().toISOString(), captureSeq);
1478
- const snippet = text.substring(0, Math.min(text.length, 40));
1479
- const preCount = countOccurrences(preVerifyContent ?? "", snippet);
1480
- const postCount = countOccurrences(content ?? "", snippet);
1481
- const verified = postCount > preCount;
1482
- return observe({ ok: true, sessionName, ...interaction, verified, outcome: verified ? "delivered" : "rendered-unconfirmed", ...(sendAdvisory ? { warning: sendAdvisory } : {}), ...(waitMode ? { sent: true, ...waitEvidence } : {}) });
2626
+ // A terminal prompt is command history, not an agent composer. Preserve
2627
+ // its original prefix-count verification even while a command is running.
2628
+ const terminal = runtime === "terminal";
2629
+ const { snippet, envelope } = terminal ? { snippet: text.slice(0, 40), envelope: false } : verificationSnippet(text);
2630
+ // Only Codex gains »; all other runtimes keep the existing boundaries.
2631
+ const promptClass = runtime === "codex" ? COMPOSER_PROMPT_CLASS : "❯›";
2632
+ const transcript = terminal ? (pane) => pane ?? ""
2633
+ : (pane) => envelope ? verificationTranscript(pane, promptClass)
2634
+ : normalizeVerificationText(verificationTranscript(pane, promptClass));
2635
+ const count = envelope ? (pane, expected) => countEnvelopeOccurrences(pane, expected, promptClass) : countOccurrences;
2636
+ const preCount = count(transcript(preVerifyContent), snippet);
2637
+ const postCount = count(transcript(content), snippet);
2638
+ // An audited prompt answer reads no box (no extra read is added for it), so it says so: unchecked, and
2639
+ // unconfirmed; it never falls back to the count below.
2640
+ const inputBox = boxBefore && inputBoxRuntime
2641
+ ? await this.inputBoxEvidence(sessionName, sessionMeta, boxBefore, readInputBox(content, inputBoxRuntime, text, boxBefore.pane))
2642
+ : inputBoxRuntime && promptOverride ? INPUT_BOX_UNCHECKED : undefined;
2643
+ // With the setting on, only a seat proven idle before the paste and input-box evidence verify: exact before
2644
+ // Enter, empty after, same process. A not-idle or unknown seat always reads unconfirmed.
2645
+ const verified = inputBoxRuntime
2646
+ ? inputBox?.seat === "idle" && inputBox.before === "exact" && inputBox.after === "empty" && inputBox.sameProcess
2647
+ : postCount > preCount;
2648
+ return observe({ ok: true, sessionName, ...interaction, verified, outcome: verified ? "delivered" : "rendered-unconfirmed", ...(inputBox ? { inputBox } : {}), ...(sendAdvisory ? { warning: sendAdvisory } : {}), ...(waitMode ? { sent: true, ...waitEvidence } : {}) });
1483
2649
  }
1484
2650
  catch {
1485
2651
  if (observed && observed.post.state === "not_reached") {
1486
2652
  observed.post = { state: "unavailable", cause: "capture_error", capturedAt: this.now().toISOString(), captureSeq };
1487
2653
  }
1488
- return observe({ ok: true, sessionName, ...interaction, verified: false, outcome: "rendered-unconfirmed", ...(sendAdvisory ? { warning: sendAdvisory } : {}), ...(waitMode ? { sent: true, ...waitEvidence } : {}) });
2654
+ const inputBox = boxBefore
2655
+ ? { seat: boxBefore.seat, before: boxBefore.reading, after: "unavailable", sameProcess: false }
2656
+ : inputBoxRuntime && promptOverride ? INPUT_BOX_UNCHECKED : undefined;
2657
+ return observe({ ok: true, sessionName, ...interaction, verified: false, outcome: "rendered-unconfirmed", ...(inputBox ? { inputBox } : {}), ...(sendAdvisory ? { warning: sendAdvisory } : {}), ...(waitMode ? { sent: true, ...waitEvidence } : {}) });
1489
2658
  }
1490
2659
  }
1491
2660
  return observe({ ok: true, sessionName, ...interaction, ...(sendAdvisory ? { warning: sendAdvisory } : {}), ...(waitMode ? { sent: true, ...waitEvidence } : {}) });
1492
2661
  }
2662
+ inputBoxSettingOn() {
2663
+ try {
2664
+ return this.verifyInputBox?.() === true;
2665
+ }
2666
+ catch {
2667
+ return false;
2668
+ }
2669
+ }
2670
+ /** One input-box read, timed by the slow-op recorder and raced against the deadline. Null on timeout or
2671
+ * error; an abandoned read is ignored. A read never decides whether Enter is sent. */
2672
+ async readWithinDeadline(site, read) {
2673
+ let timer;
2674
+ try {
2675
+ const pending = this.runStage(site, read);
2676
+ pending.catch(() => undefined);
2677
+ return await Promise.race([
2678
+ pending,
2679
+ new Promise((resolve) => { timer = setTimeout(() => resolve(null), INPUT_BOX_READ_DEADLINE_MS); }),
2680
+ ]);
2681
+ }
2682
+ catch {
2683
+ return null;
2684
+ }
2685
+ finally {
2686
+ if (timer !== undefined)
2687
+ clearTimeout(timer);
2688
+ }
2689
+ }
2690
+ /** After the post-Enter capture: the same pane process (pid read before Enter and now) and the same binding. */
2691
+ async inputBoxEvidence(sessionName, meta, before, after) {
2692
+ const pid = await this.readWithinDeadline("session_transport.input_box_pane_pid", () => this.tmuxAdapter.getPanePid(sessionName));
2693
+ let sameBinding = false;
2694
+ try {
2695
+ const now = this.getSessionMeta(sessionName);
2696
+ sameBinding = now.nodeId === meta.nodeId && now.pane === meta.pane && now.occupant === meta.occupant;
2697
+ }
2698
+ catch { /* unknown binding is not the same binding */ }
2699
+ return { seat: before.seat, before: before.reading, after, sameProcess: before.pid !== null && pid !== null && pid === before.pid && sameBinding };
2700
+ }
1493
2701
  runStage(site, fn, classify) {
1494
2702
  return this.slowOpRecorder?.runStage
1495
2703
  ? this.slowOpRecorder.runStage(site, fn, classify)
@@ -1510,6 +2718,7 @@ export class SessionTransport {
1510
2718
  const deadline = Date.now() + input.timeoutMs;
1511
2719
  let attempts = 0;
1512
2720
  let last = null;
2721
+ const labelMs = input.timeoutLabelMs ?? input.timeoutMs;
1513
2722
  const expire = () => {
1514
2723
  const count = `${attempts} observation${attempts === 1 ? "" : "s"}`;
1515
2724
  const activity = last ?? {
@@ -1521,14 +2730,14 @@ export class SessionTransport {
1521
2730
  ok: false,
1522
2731
  reason: "target_activity_unknown",
1523
2732
  error: last
1524
- ? `Target activity could not be determined (${activity.reason}) when the ${input.timeoutMs}ms wait ended (${count}). No text was sent.`
1525
- : `Target activity could not be determined: no observation completed within the ${input.timeoutMs}ms wait. No text was sent.`,
2733
+ ? `Target activity could not be determined (${activity.reason}) when the ${labelMs}ms wait ended (${count}). No text was sent.`
2734
+ : `Target activity could not be determined: no observation completed within the ${labelMs}ms wait. No text was sent.`,
1526
2735
  activity, waitedMs: input.timeoutMs, attempts,
1527
2736
  }
1528
2737
  : {
1529
2738
  ok: false,
1530
2739
  reason: "wait_for_idle_timeout",
1531
- error: `Target was still busy (${activity.reason}) when the ${input.timeoutMs}ms wait ended (${count}). No text was sent.`,
2740
+ error: `Target was still busy (${activity.reason}) when the ${labelMs}ms wait ended (${count}). No text was sent.`,
1532
2741
  activity, waitedMs: input.timeoutMs, attempts,
1533
2742
  };
1534
2743
  };
@@ -1645,10 +2854,16 @@ export class SessionTransport {
1645
2854
  }
1646
2855
  async classifySendReadiness(input) {
1647
2856
  const now = this.now();
2857
+ const composerSnapshot = this.tmuxAdapter.deliveryGuard?.needsInputCheck(input.sessionName)
2858
+ ? () => this.tmuxAdapter.captureComposerSnapshot(input.binding?.pane ?? input.sessionName) : undefined;
1648
2859
  const hookActivity = this.agentActivityStore?.getLatestForNode({
1649
2860
  sessionName: input.sessionName,
1650
2861
  now,
1651
2862
  });
2863
+ const permissionPending = latestHookPermissionPending(hookActivity, input.runtime);
2864
+ if (hookActivity && permissionPending && this.hookFreshForSend(hookActivity, now)) {
2865
+ return { ...hookActivity, state: "needs_input" };
2866
+ }
1652
2867
  // Pane-only readiness never lets a hook authorise a send, but a fresh hook showing work or a
1653
2868
  // waiting person still refuses one, as it does for every other send.
1654
2869
  if (input.readinessFromPaneOnly &&
@@ -1665,7 +2880,9 @@ export class SessionTransport {
1665
2880
  if (!input.readinessFromPaneOnly &&
1666
2881
  hookActivity &&
1667
2882
  hookActivity.evidenceSource === "runtime_hook" &&
1668
- hookActivity.stale !== true) {
2883
+ hookActivity.stale !== true &&
2884
+ hookActivity.reason !== "permission_request_pending" &&
2885
+ hookActivity.reason !== "permission_prompt_cleared") {
1669
2886
  // Fresh hook (within the 15s send window): authoritative for any state.
1670
2887
  if (this.hookFreshForSend(hookActivity, now)) {
1671
2888
  return hookActivity;
@@ -1685,6 +2902,7 @@ export class SessionTransport {
1685
2902
  // vetoes (→ refuse); an unknown pane (the flaky-Codex case this trust
1686
2903
  // exists for) or a clean idle pane does NOT veto → trust the stale hook.
1687
2904
  const paneVeto = await probeSessionActivity({
2905
+ composerSnapshot,
1688
2906
  sessionName: input.sessionName,
1689
2907
  runtime: input.runtime,
1690
2908
  attachmentType: input.attachmentType,
@@ -1700,6 +2918,7 @@ export class SessionTransport {
1700
2918
  }
1701
2919
  }
1702
2920
  const probe = await probeSessionActivity({
2921
+ composerSnapshot,
1703
2922
  sessionName: input.sessionName,
1704
2923
  runtime: input.runtime,
1705
2924
  attachmentType: input.attachmentType,
@@ -1723,6 +2942,9 @@ export class SessionTransport {
1723
2942
  (hookActivity.state === "running" || hookActivity.state === "needs_input")) {
1724
2943
  return hookActivity;
1725
2944
  }
2945
+ if (probe.state === "unknown" && permissionPending) {
2946
+ return { ...probe, state: "needs_input", reason: "permission_request_unverified" };
2947
+ }
1726
2948
  // A latest hook saying the seat waits on a person (approval, picker or elicitation) stays positive
1727
2949
  // picker evidence past the send window, and past the store's freshness window: nothing newer was
1728
2950
  // recorded, so nothing answered it. The pane alone can miss a picker it has no signature for (a
@@ -1849,10 +3071,9 @@ export class SessionTransport {
1849
3071
  }
1850
3072
  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)`;
1851
3073
  }
1852
- // OPR.0.4.1.10 — persist the audit record for a --dangerously-interact prompt override. Audit-all-
1853
- // or-nothing: if there is no eventBus, the target rig/node can't be resolved, or the event cannot be
1854
- // persisted, return !ok so the caller fails closed and does NOT send. Payload keeps the caller's
1855
- // overrideReason distinct from the classifier's detectedReason/evidenceSource (not overloaded).
3074
+ // Record actor/reason with the actual readiness, never a fabricated prompt classification.
3075
+ // Report an unavailable sink to the caller: a positive-picker override still fails closed,
3076
+ // while other readiness states keep their existing accepted-send behavior with an advisory.
1856
3077
  recordPromptOverride(input) {
1857
3078
  if (!this.eventBus)
1858
3079
  return { ok: false, reason: "audit_unconfigured" };
@@ -1929,6 +3150,7 @@ export class SessionTransport {
1929
3150
  error: `Could not capture pane content for '${sessionName}'.`,
1930
3151
  };
1931
3152
  }
3153
+ await this.reconcileStartupReadiness(sessionName).catch(() => { });
1932
3154
  return { ok: true, sessionName, content, lines };
1933
3155
  }
1934
3156
  async broadcast(target, text, opts) {
@@ -1982,5 +3204,140 @@ export class SessionTransport {
1982
3204
  results,
1983
3205
  };
1984
3206
  }
3207
+ getReconciliationSnapshot(sessionName) {
3208
+ return this.db.prepare(`
3209
+ SELECT n.id as node_id, r.id as rig_id, s.id as session_id, s.session_name, n.runtime, n.cwd,
3210
+ s.startup_status, s.status as session_status, s.startup_completed_at,
3211
+ s.resume_type, s.resume_token,
3212
+ b.tmux_session as binding_session, b.tmux_window as binding_window, b.tmux_pane as binding_pane,
3213
+ b.updated_at as binding_updated_at,
3214
+ (SELECT generation_uuid FROM occupant_tenures WHERE node_id = n.id ORDER BY generation_ordinal DESC LIMIT 1) as occupant_generation,
3215
+ (SELECT id FROM sessions WHERE node_id = n.id ORDER BY id DESC LIMIT 1) as latest_session_id,
3216
+ (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
3217
+ FROM sessions s
3218
+ JOIN nodes n ON n.id = s.node_id
3219
+ JOIN rigs r ON r.id = n.rig_id
3220
+ LEFT JOIN bindings b ON b.node_id = n.id
3221
+ WHERE s.session_name = ?
3222
+ ORDER BY s.id DESC LIMIT 1
3223
+ `).get(sessionName);
3224
+ }
3225
+ /**
3226
+ * Reconcile non-ready startup state after successful session interaction (Refs #7).
3227
+ *
3228
+ * If an active session is in a non-ready startup state (`failed` or `attention_required`),
3229
+ * re-check readiness via the runtime adapter with currentness verification and atomic commit.
3230
+ */
3231
+ async reconcileStartupReadiness(sessionName) {
3232
+ const existing = this.inFlightReconciliations.get(sessionName);
3233
+ if (existing)
3234
+ return existing;
3235
+ const promise = this.doReconcileStartupReadiness(sessionName);
3236
+ this.inFlightReconciliations.set(sessionName, promise);
3237
+ try {
3238
+ return await promise;
3239
+ }
3240
+ finally {
3241
+ this.inFlightReconciliations.delete(sessionName);
3242
+ }
3243
+ }
3244
+ async doReconcileStartupReadiness(sessionName) {
3245
+ const snapshot = this.getReconciliationSnapshot(sessionName);
3246
+ if (!snapshot)
3247
+ return false;
3248
+ if (snapshot.startup_status !== "failed" && snapshot.startup_status !== "attention_required")
3249
+ return false;
3250
+ if (snapshot.session_status !== "running")
3251
+ return false;
3252
+ if (snapshot.session_id !== snapshot.latest_session_id)
3253
+ return false;
3254
+ if (!isRecoverableStartupFailure(this.db, snapshot.node_id, snapshot.session_id))
3255
+ return false;
3256
+ const checker = this.runtimeAdapters[snapshot.runtime];
3257
+ if (!checker?.checkReady)
3258
+ return false;
3259
+ const hasClaudeIdentityWork = (nodeId, sessionId, runtime) => {
3260
+ if (runtime !== "claude-code")
3261
+ return false;
3262
+ const failedEvent = getLatestStartupFailedEvent(this.db, nodeId, sessionId);
3263
+ if (!failedEvent || failedEvent.requestedResumeToken === undefined || failedEvent.requestedResumeType === undefined) {
3264
+ return true;
3265
+ }
3266
+ return hasClaudeResumeIdentityWork(this.sessionRegistry, sessionId, runtime, failedEvent.requestedResumeToken, failedEvent.requestedResumeType);
3267
+ };
3268
+ const probeBinding = {
3269
+ id: `session-transport:${snapshot.session_id}`,
3270
+ nodeId: snapshot.node_id,
3271
+ attachmentType: "tmux",
3272
+ tmuxSession: sessionName,
3273
+ tmuxWindow: null,
3274
+ tmuxPane: null,
3275
+ cmuxWorkspace: null,
3276
+ cmuxSurface: null,
3277
+ updatedAt: "",
3278
+ cwd: snapshot.cwd ?? "",
3279
+ };
3280
+ let timeoutTimer;
3281
+ const timeoutPromise = new Promise((_, reject) => {
3282
+ timeoutTimer = setTimeout(() => reject(new Error("readiness probe timeout")), 5000);
3283
+ });
3284
+ let readiness;
3285
+ try {
3286
+ readiness = await Promise.race([checker.checkReady(probeBinding), timeoutPromise]);
3287
+ }
3288
+ catch {
3289
+ return false;
3290
+ }
3291
+ finally {
3292
+ if (timeoutTimer)
3293
+ clearTimeout(timeoutTimer);
3294
+ }
3295
+ let emittedReady = false;
3296
+ this.db.transaction(() => {
3297
+ const current = this.getReconciliationSnapshot(sessionName);
3298
+ if (!current || JSON.stringify(current) !== JSON.stringify(snapshot))
3299
+ return;
3300
+ if (!isRecoverableStartupFailure(this.db, snapshot.node_id, snapshot.session_id))
3301
+ return;
3302
+ const currentIdentityWork = hasClaudeIdentityWork(current.node_id, current.session_id, current.runtime);
3303
+ if (readiness.ready) {
3304
+ if (currentIdentityWork) {
3305
+ this.db.prepare(`
3306
+ UPDATE sessions
3307
+ SET startup_status = 'attention_required'
3308
+ WHERE id = ? AND startup_status = 'failed'
3309
+ `).run(snapshot.session_id);
3310
+ return;
3311
+ }
3312
+ const result = this.db.prepare(`
3313
+ UPDATE sessions
3314
+ SET startup_status = 'ready',
3315
+ startup_completed_at = ?
3316
+ WHERE id = ? AND startup_status IN ('failed', 'attention_required')
3317
+ `).run(new Date().toISOString(), snapshot.session_id);
3318
+ if (result.changes > 0) {
3319
+ emittedReady = true;
3320
+ }
3321
+ return;
3322
+ }
3323
+ if (isAttentionRequiredReadinessCode(readiness.code)) {
3324
+ this.db.prepare(`
3325
+ UPDATE sessions
3326
+ SET startup_status = 'attention_required'
3327
+ WHERE id = ? AND startup_status = 'failed'
3328
+ `).run(snapshot.session_id);
3329
+ }
3330
+ }).immediate();
3331
+ if (emittedReady && this.eventBus) {
3332
+ this.eventBus.emit({
3333
+ type: "node.startup_ready",
3334
+ rigId: snapshot.rig_id,
3335
+ nodeId: snapshot.node_id,
3336
+ previousStatus: snapshot.startup_status,
3337
+ });
3338
+ return true;
3339
+ }
3340
+ return false;
3341
+ }
1985
3342
  }
1986
3343
  //# sourceMappingURL=session-transport.js.map