@openrig/cli 0.6.10 → 0.6.11

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 (352) hide show
  1. package/daemon/assets/plugins/openrig-core/skills/openrig-skills/SKILL.md +1 -1
  2. package/daemon/assets/plugins/openrig-core/skills/refocusing/scripts/trace-to-root.py +6 -1
  3. package/daemon/assets/skills/rigs/SKILL.md +1 -1
  4. package/daemon/context-packs/help/manifest.yaml +1 -1
  5. package/daemon/context-packs/onboarding-width/manifest.yaml +1 -1
  6. package/daemon/context-packs/onboarding-width/public-what-you-can-do.md +36 -11
  7. package/daemon/context-packs/reference/manifest.yaml +1 -1
  8. package/daemon/context-packs/reference/rig-spec.md +2 -2
  9. package/daemon/context-packs/reference/whats-new.md +135 -134
  10. package/daemon/context-packs/skills/agent-operated-software/manifest.yaml +1 -1
  11. package/daemon/context-packs/skills/agent-operated-workflows/manifest.yaml +1 -1
  12. package/daemon/context-packs/skills/applying-a-permission-policy/manifest.yaml +1 -1
  13. package/daemon/context-packs/skills/claude-compaction-restore/manifest.yaml +1 -1
  14. package/daemon/context-packs/skills/core/agent-starters/manifest.yaml +1 -1
  15. package/daemon/context-packs/skills/core/agent-startup-and-context-ingestion/manifest.yaml +1 -1
  16. package/daemon/context-packs/skills/core/cross-host-rig-commands/manifest.yaml +1 -1
  17. package/daemon/context-packs/skills/core/human-in-the-loop/manifest.yaml +1 -1
  18. package/daemon/context-packs/skills/core/messaging-the-human/manifest.yaml +1 -1
  19. package/daemon/context-packs/skills/core/openrig-architect/manifest.yaml +1 -1
  20. package/daemon/context-packs/skills/core/openrig-cmux/manifest.yaml +1 -1
  21. package/daemon/context-packs/skills/core/openrig-herdr/manifest.yaml +1 -1
  22. package/daemon/context-packs/skills/core/openrig-skills/SKILL.md +1 -1
  23. package/daemon/context-packs/skills/core/openrig-skills/manifest.yaml +3 -3
  24. package/daemon/context-packs/skills/core/openrig-software-factory/manifest.yaml +1 -1
  25. package/daemon/context-packs/skills/core/openrig-upgrade/manifest.yaml +1 -1
  26. package/daemon/context-packs/skills/core/rig-bundles-and-shareable-artifacts/manifest.yaml +1 -1
  27. package/daemon/context-packs/skills/core/rig-lifecycle/manifest.yaml +1 -1
  28. package/daemon/context-packs/skills/core/session-source-fork/manifest.yaml +1 -1
  29. package/daemon/context-packs/skills/core/specification-system/manifest.yaml +1 -1
  30. package/daemon/context-packs/skills/core/topology-mutation-and-seat-management/manifest.yaml +1 -1
  31. package/daemon/context-packs/skills/core/watchdog/manifest.yaml +1 -1
  32. package/daemon/context-packs/skills/delegating-work/manifest.yaml +1 -1
  33. package/daemon/context-packs/skills/forming-an-openrig-mental-model/manifest.yaml +1 -1
  34. package/daemon/context-packs/skills/loading-addressable-markdown/manifest.yaml +1 -1
  35. package/daemon/context-packs/skills/mission-slice-sop/manifest.yaml +1 -1
  36. package/daemon/context-packs/skills/openrig-operating-model/manifest.yaml +1 -1
  37. package/daemon/context-packs/skills/openrig-user/manifest.yaml +1 -1
  38. package/daemon/context-packs/skills/orienting-to-an-inherited-seat/manifest.yaml +1 -1
  39. package/daemon/context-packs/skills/pm/backlog-capture/manifest.yaml +1 -1
  40. package/daemon/context-packs/skills/pm/context-builder/manifest.yaml +1 -1
  41. package/daemon/context-packs/skills/pm/exec-summary/manifest.yaml +1 -1
  42. package/daemon/context-packs/skills/pm/office-hours/manifest.yaml +1 -1
  43. package/daemon/context-packs/skills/pm/plan-review/manifest.yaml +1 -1
  44. package/daemon/context-packs/skills/pm/requirements-writer/manifest.yaml +1 -1
  45. package/daemon/context-packs/skills/pm/ui-mockup/manifest.yaml +1 -1
  46. package/daemon/context-packs/skills/pods/development-team/manifest.yaml +1 -1
  47. package/daemon/context-packs/skills/pods/orchestration-team/manifest.yaml +1 -1
  48. package/daemon/context-packs/skills/pods/oversight-team/manifest.yaml +1 -1
  49. package/daemon/context-packs/skills/pods/review-team/manifest.yaml +1 -1
  50. package/daemon/context-packs/skills/process/agent-browser/manifest.yaml +1 -1
  51. package/daemon/context-packs/skills/process/context-engineering/manifest.yaml +1 -1
  52. package/daemon/context-packs/skills/process/dogfood/manifest.yaml +1 -1
  53. package/daemon/context-packs/skills/process/frontend-design/manifest.yaml +1 -1
  54. package/daemon/context-packs/skills/process/systematic-debugging/manifest.yaml +1 -1
  55. package/daemon/context-packs/skills/process/test-driven-development/manifest.yaml +1 -1
  56. package/daemon/context-packs/skills/process/verification-before-completion/manifest.yaml +1 -1
  57. package/daemon/context-packs/skills/queue-handoff/manifest.yaml +1 -1
  58. package/daemon/context-packs/skills/refocusing/manifest.yaml +1 -1
  59. package/daemon/context-packs/skills/refocusing/scripts/trace-to-root.py +6 -1
  60. package/daemon/context-packs/skills/retiring-and-inheriting-a-seat/manifest.yaml +1 -1
  61. package/daemon/context-packs/skills/seat-continuity-and-handover/manifest.yaml +1 -1
  62. package/daemon/context-packs/skills/session-compaction-and-restore/manifest.yaml +1 -1
  63. package/daemon/context-packs/skills/software-for-agents/manifest.yaml +1 -1
  64. package/daemon/context-packs/world-example/manifest.yaml +1 -1
  65. package/daemon/context-packs/world-public/manifest.yaml +1 -1
  66. package/daemon/dist/adapters/claude-code-adapter.d.ts +0 -14
  67. package/daemon/dist/adapters/claude-code-adapter.d.ts.map +1 -1
  68. package/daemon/dist/adapters/claude-code-adapter.js +27 -73
  69. package/daemon/dist/adapters/claude-code-adapter.js.map +1 -1
  70. package/daemon/dist/adapters/codex-runtime-adapter.d.ts +35 -0
  71. package/daemon/dist/adapters/codex-runtime-adapter.d.ts.map +1 -1
  72. package/daemon/dist/adapters/codex-runtime-adapter.js +94 -44
  73. package/daemon/dist/adapters/codex-runtime-adapter.js.map +1 -1
  74. package/daemon/dist/adapters/pi-runner.d.ts.map +1 -1
  75. package/daemon/dist/adapters/pi-runner.js +6 -3
  76. package/daemon/dist/adapters/pi-runner.js.map +1 -1
  77. package/daemon/dist/adapters/pi-runtime-adapter.js +1 -1
  78. package/daemon/dist/adapters/pi-runtime-adapter.js.map +1 -1
  79. package/daemon/dist/adapters/tmux.d.ts +23 -0
  80. package/daemon/dist/adapters/tmux.d.ts.map +1 -1
  81. package/daemon/dist/adapters/tmux.js +127 -6
  82. package/daemon/dist/adapters/tmux.js.map +1 -1
  83. package/daemon/dist/build-info.js +3 -3
  84. package/daemon/dist/db/all-migrations.d.ts.map +1 -1
  85. package/daemon/dist/db/all-migrations.js +2 -0
  86. package/daemon/dist/db/all-migrations.js.map +1 -1
  87. package/daemon/dist/db/migrations/103_session_stop_intent.d.ts +4 -0
  88. package/daemon/dist/db/migrations/103_session_stop_intent.d.ts.map +1 -0
  89. package/daemon/dist/db/migrations/103_session_stop_intent.js +9 -0
  90. package/daemon/dist/db/migrations/103_session_stop_intent.js.map +1 -0
  91. package/daemon/dist/domain/active-occupant.d.ts +14 -0
  92. package/daemon/dist/domain/active-occupant.d.ts.map +1 -1
  93. package/daemon/dist/domain/active-occupant.js +17 -0
  94. package/daemon/dist/domain/active-occupant.js.map +1 -1
  95. package/daemon/dist/domain/codex-network-default.d.ts +3 -0
  96. package/daemon/dist/domain/codex-network-default.d.ts.map +1 -1
  97. package/daemon/dist/domain/codex-network-default.js +1 -1
  98. package/daemon/dist/domain/codex-network-default.js.map +1 -1
  99. package/daemon/dist/domain/codex-pane-patterns.d.ts +2 -0
  100. package/daemon/dist/domain/codex-pane-patterns.d.ts.map +1 -0
  101. package/daemon/dist/domain/codex-pane-patterns.js +5 -0
  102. package/daemon/dist/domain/codex-pane-patterns.js.map +1 -0
  103. package/daemon/dist/domain/context-monitor.d.ts.map +1 -1
  104. package/daemon/dist/domain/context-monitor.js +46 -22
  105. package/daemon/dist/domain/context-monitor.js.map +1 -1
  106. package/daemon/dist/domain/cwd-resolution.d.ts +8 -0
  107. package/daemon/dist/domain/cwd-resolution.d.ts.map +1 -1
  108. package/daemon/dist/domain/cwd-resolution.js +24 -3
  109. package/daemon/dist/domain/cwd-resolution.js.map +1 -1
  110. package/daemon/dist/domain/gateway/slack/channel-recovery.d.ts.map +1 -1
  111. package/daemon/dist/domain/gateway/slack/channel-recovery.js +4 -3
  112. package/daemon/dist/domain/gateway/slack/channel-recovery.js.map +1 -1
  113. package/daemon/dist/domain/native-process-lineage.d.ts +5 -0
  114. package/daemon/dist/domain/native-process-lineage.d.ts.map +1 -1
  115. package/daemon/dist/domain/native-process-lineage.js +53 -15
  116. package/daemon/dist/domain/native-process-lineage.js.map +1 -1
  117. package/daemon/dist/domain/node-inventory.d.ts.map +1 -1
  118. package/daemon/dist/domain/node-inventory.js +15 -7
  119. package/daemon/dist/domain/node-inventory.js.map +1 -1
  120. package/daemon/dist/domain/node-launcher.d.ts.map +1 -1
  121. package/daemon/dist/domain/node-launcher.js +2 -1
  122. package/daemon/dist/domain/node-launcher.js.map +1 -1
  123. package/daemon/dist/domain/pane-cutover.d.ts +27 -0
  124. package/daemon/dist/domain/pane-cutover.d.ts.map +1 -0
  125. package/daemon/dist/domain/pane-cutover.js +37 -0
  126. package/daemon/dist/domain/pane-cutover.js.map +1 -0
  127. package/daemon/dist/domain/parent-session-env.d.ts +8 -0
  128. package/daemon/dist/domain/parent-session-env.d.ts.map +1 -0
  129. package/daemon/dist/domain/parent-session-env.js +37 -0
  130. package/daemon/dist/domain/parent-session-env.js.map +1 -0
  131. package/daemon/dist/domain/policies/parked-owner-consumer.d.ts +11 -0
  132. package/daemon/dist/domain/policies/parked-owner-consumer.d.ts.map +1 -1
  133. package/daemon/dist/domain/policies/parked-owner-consumer.js +81 -11
  134. package/daemon/dist/domain/policies/parked-owner-consumer.js.map +1 -1
  135. package/daemon/dist/domain/profile-resolver.d.ts.map +1 -1
  136. package/daemon/dist/domain/profile-resolver.js +2 -3
  137. package/daemon/dist/domain/profile-resolver.js.map +1 -1
  138. package/daemon/dist/domain/proof/judgments.d.ts +10 -5
  139. package/daemon/dist/domain/proof/judgments.d.ts.map +1 -1
  140. package/daemon/dist/domain/proof/judgments.js +27 -19
  141. package/daemon/dist/domain/proof/judgments.js.map +1 -1
  142. package/daemon/dist/domain/provider/codex-limit-banner.d.ts +2 -2
  143. package/daemon/dist/domain/provider/codex-limit-banner.d.ts.map +1 -1
  144. package/daemon/dist/domain/provider/codex-limit-banner.js +56 -9
  145. package/daemon/dist/domain/provider/codex-limit-banner.js.map +1 -1
  146. package/daemon/dist/domain/provider/codex-usage-reader.d.ts +31 -0
  147. package/daemon/dist/domain/provider/codex-usage-reader.d.ts.map +1 -0
  148. package/daemon/dist/domain/provider/codex-usage-reader.js +115 -0
  149. package/daemon/dist/domain/provider/codex-usage-reader.js.map +1 -0
  150. package/daemon/dist/domain/provider/codex-usage-windows.d.ts +78 -0
  151. package/daemon/dist/domain/provider/codex-usage-windows.d.ts.map +1 -0
  152. package/daemon/dist/domain/provider/codex-usage-windows.js +259 -0
  153. package/daemon/dist/domain/provider/codex-usage-windows.js.map +1 -0
  154. package/daemon/dist/domain/provider/provider-service-impl.d.ts +10 -0
  155. package/daemon/dist/domain/provider/provider-service-impl.d.ts.map +1 -1
  156. package/daemon/dist/domain/provider/provider-service-impl.js +27 -1
  157. package/daemon/dist/domain/provider/provider-service-impl.js.map +1 -1
  158. package/daemon/dist/domain/provider/provider-service.d.ts +5 -0
  159. package/daemon/dist/domain/provider/provider-service.d.ts.map +1 -1
  160. package/daemon/dist/domain/provider/provider-types.d.ts +65 -0
  161. package/daemon/dist/domain/provider/provider-types.d.ts.map +1 -1
  162. package/daemon/dist/domain/ps-projection.d.ts +5 -2
  163. package/daemon/dist/domain/ps-projection.d.ts.map +1 -1
  164. package/daemon/dist/domain/ps-projection.js +27 -25
  165. package/daemon/dist/domain/ps-projection.js.map +1 -1
  166. package/daemon/dist/domain/queue-repository.d.ts +2 -0
  167. package/daemon/dist/domain/queue-repository.d.ts.map +1 -1
  168. package/daemon/dist/domain/queue-repository.js +16 -3
  169. package/daemon/dist/domain/queue-repository.js.map +1 -1
  170. package/daemon/dist/domain/queue-stuck-sweep.d.ts.map +1 -1
  171. package/daemon/dist/domain/queue-stuck-sweep.js +21 -7
  172. package/daemon/dist/domain/queue-stuck-sweep.js.map +1 -1
  173. package/daemon/dist/domain/restore-check-service.d.ts +8 -0
  174. package/daemon/dist/domain/restore-check-service.d.ts.map +1 -1
  175. package/daemon/dist/domain/restore-check-service.js +12 -3
  176. package/daemon/dist/domain/restore-check-service.js.map +1 -1
  177. package/daemon/dist/domain/restore-orchestrator.d.ts +4 -0
  178. package/daemon/dist/domain/restore-orchestrator.d.ts.map +1 -1
  179. package/daemon/dist/domain/restore-orchestrator.js +34 -5
  180. package/daemon/dist/domain/restore-orchestrator.js.map +1 -1
  181. package/daemon/dist/domain/restore-plan-preview.d.ts +4 -0
  182. package/daemon/dist/domain/restore-plan-preview.d.ts.map +1 -1
  183. package/daemon/dist/domain/restore-plan-preview.js +14 -3
  184. package/daemon/dist/domain/restore-plan-preview.js.map +1 -1
  185. package/daemon/dist/domain/rigspec-preflight.d.ts.map +1 -1
  186. package/daemon/dist/domain/rigspec-preflight.js +7 -6
  187. package/daemon/dist/domain/rigspec-preflight.js.map +1 -1
  188. package/daemon/dist/domain/runtime-adapter.d.ts +1 -0
  189. package/daemon/dist/domain/runtime-adapter.d.ts.map +1 -1
  190. package/daemon/dist/domain/runtime-adapter.js.map +1 -1
  191. package/daemon/dist/domain/scope/scope-view-projection.d.ts +4 -2
  192. package/daemon/dist/domain/scope/scope-view-projection.d.ts.map +1 -1
  193. package/daemon/dist/domain/scope/scope-view-projection.js +6 -4
  194. package/daemon/dist/domain/scope/scope-view-projection.js.map +1 -1
  195. package/daemon/dist/domain/seat-lifecycle-service.d.ts +117 -2
  196. package/daemon/dist/domain/seat-lifecycle-service.d.ts.map +1 -1
  197. package/daemon/dist/domain/seat-lifecycle-service.js +335 -74
  198. package/daemon/dist/domain/seat-lifecycle-service.js.map +1 -1
  199. package/daemon/dist/domain/seat-restart-in-place.d.ts +292 -0
  200. package/daemon/dist/domain/seat-restart-in-place.d.ts.map +1 -0
  201. package/daemon/dist/domain/seat-restart-in-place.js +895 -0
  202. package/daemon/dist/domain/seat-restart-in-place.js.map +1 -0
  203. package/daemon/dist/domain/seat-structural-activity-service.d.ts +1 -1
  204. package/daemon/dist/domain/seat-structural-activity-service.d.ts.map +1 -1
  205. package/daemon/dist/domain/seat-structural-activity-service.js +5 -6
  206. package/daemon/dist/domain/seat-structural-activity-service.js.map +1 -1
  207. package/daemon/dist/domain/session-fingerprinter.d.ts.map +1 -1
  208. package/daemon/dist/domain/session-fingerprinter.js +9 -5
  209. package/daemon/dist/domain/session-fingerprinter.js.map +1 -1
  210. package/daemon/dist/domain/session-registry.d.ts +3 -0
  211. package/daemon/dist/domain/session-registry.d.ts.map +1 -1
  212. package/daemon/dist/domain/session-registry.js +6 -1
  213. package/daemon/dist/domain/session-registry.js.map +1 -1
  214. package/daemon/dist/domain/session-transport.d.ts +33 -3
  215. package/daemon/dist/domain/session-transport.d.ts.map +1 -1
  216. package/daemon/dist/domain/session-transport.js +391 -21
  217. package/daemon/dist/domain/session-transport.js.map +1 -1
  218. package/daemon/dist/domain/startup-orchestrator.d.ts +2 -0
  219. package/daemon/dist/domain/startup-orchestrator.d.ts.map +1 -1
  220. package/daemon/dist/domain/startup-orchestrator.js +4 -3
  221. package/daemon/dist/domain/startup-orchestrator.js.map +1 -1
  222. package/daemon/dist/domain/successor-session-launcher.d.ts +3 -7
  223. package/daemon/dist/domain/successor-session-launcher.d.ts.map +1 -1
  224. package/daemon/dist/domain/successor-session-launcher.js +6 -32
  225. package/daemon/dist/domain/successor-session-launcher.js.map +1 -1
  226. package/daemon/dist/domain/types.d.ts +29 -0
  227. package/daemon/dist/domain/types.d.ts.map +1 -1
  228. package/daemon/dist/domain/types.js.map +1 -1
  229. package/daemon/dist/domain/user-settings/settings-store.d.ts +1 -1
  230. package/daemon/dist/domain/user-settings/settings-store.d.ts.map +1 -1
  231. package/daemon/dist/domain/user-settings/settings-store.js +31 -0
  232. package/daemon/dist/domain/user-settings/settings-store.js.map +1 -1
  233. package/daemon/dist/domain/wake-resolver.js +2 -2
  234. package/daemon/dist/domain/wake-resolver.js.map +1 -1
  235. package/daemon/dist/domain/watchdog-jobs-repository.d.ts.map +1 -1
  236. package/daemon/dist/domain/watchdog-jobs-repository.js +19 -3
  237. package/daemon/dist/domain/watchdog-jobs-repository.js.map +1 -1
  238. package/daemon/dist/domain/whoami-service.d.ts +4 -1
  239. package/daemon/dist/domain/whoami-service.d.ts.map +1 -1
  240. package/daemon/dist/domain/whoami-service.js +25 -19
  241. package/daemon/dist/domain/whoami-service.js.map +1 -1
  242. package/daemon/dist/domain/workspace/project-read.d.ts +2 -0
  243. package/daemon/dist/domain/workspace/project-read.d.ts.map +1 -1
  244. package/daemon/dist/domain/workspace/project-read.js +19 -8
  245. package/daemon/dist/domain/workspace/project-read.js.map +1 -1
  246. package/daemon/dist/index.d.ts.map +1 -1
  247. package/daemon/dist/index.js +6 -0
  248. package/daemon/dist/index.js.map +1 -1
  249. package/daemon/dist/routes/chat.d.ts.map +1 -1
  250. package/daemon/dist/routes/chat.js +23 -2
  251. package/daemon/dist/routes/chat.js.map +1 -1
  252. package/daemon/dist/routes/proof.d.ts.map +1 -1
  253. package/daemon/dist/routes/proof.js +99 -10
  254. package/daemon/dist/routes/proof.js.map +1 -1
  255. package/daemon/dist/routes/provider.d.ts.map +1 -1
  256. package/daemon/dist/routes/provider.js +31 -0
  257. package/daemon/dist/routes/provider.js.map +1 -1
  258. package/daemon/dist/routes/queue.d.ts.map +1 -1
  259. package/daemon/dist/routes/queue.js +18 -6
  260. package/daemon/dist/routes/queue.js.map +1 -1
  261. package/daemon/dist/routes/rigs.js +5 -3
  262. package/daemon/dist/routes/rigs.js.map +1 -1
  263. package/daemon/dist/routes/scopes.d.ts.map +1 -1
  264. package/daemon/dist/routes/scopes.js +5 -3
  265. package/daemon/dist/routes/scopes.js.map +1 -1
  266. package/daemon/dist/routes/seat.d.ts.map +1 -1
  267. package/daemon/dist/routes/seat.js +32 -0
  268. package/daemon/dist/routes/seat.js.map +1 -1
  269. package/daemon/dist/startup.d.ts.map +1 -1
  270. package/daemon/dist/startup.js +12 -2
  271. package/daemon/dist/startup.js.map +1 -1
  272. package/daemon/docs/reference/agent-startup-guide.md +19 -0
  273. package/daemon/docs/reference/keeping-a-watch.md +156 -0
  274. package/daemon/docs/reference/rig-spec.md +2 -2
  275. package/daemon/docs/reference/sdlc-conventions.md +11 -1
  276. package/daemon/docs/reference/slack-app-setup.md +6 -4
  277. package/daemon/docs/reference/whats-new.md +135 -134
  278. package/daemon/specs/agents/shared/skills/core/openrig-skills/SKILL.md +1 -1
  279. package/dist/build-info.js +3 -3
  280. package/dist/commands/config.d.ts.map +1 -1
  281. package/dist/commands/config.js +2 -0
  282. package/dist/commands/config.js.map +1 -1
  283. package/dist/commands/daemon.d.ts.map +1 -1
  284. package/dist/commands/daemon.js +7 -0
  285. package/dist/commands/daemon.js.map +1 -1
  286. package/dist/commands/proof.d.ts.map +1 -1
  287. package/dist/commands/proof.js +11 -5
  288. package/dist/commands/proof.js.map +1 -1
  289. package/dist/commands/provider.d.ts.map +1 -1
  290. package/dist/commands/provider.js +72 -0
  291. package/dist/commands/provider.js.map +1 -1
  292. package/dist/commands/ps.d.ts +4 -0
  293. package/dist/commands/ps.d.ts.map +1 -1
  294. package/dist/commands/ps.js +13 -4
  295. package/dist/commands/ps.js.map +1 -1
  296. package/dist/commands/queue.d.ts +15 -5
  297. package/dist/commands/queue.d.ts.map +1 -1
  298. package/dist/commands/queue.js +113 -37
  299. package/dist/commands/queue.js.map +1 -1
  300. package/dist/commands/scope.d.ts.map +1 -1
  301. package/dist/commands/scope.js +57 -36
  302. package/dist/commands/scope.js.map +1 -1
  303. package/dist/commands/seat.d.ts.map +1 -1
  304. package/dist/commands/seat.js +161 -10
  305. package/dist/commands/seat.js.map +1 -1
  306. package/dist/commands/start.d.ts.map +1 -1
  307. package/dist/commands/start.js +4 -3
  308. package/dist/commands/start.js.map +1 -1
  309. package/dist/commands/up.d.ts.map +1 -1
  310. package/dist/commands/up.js +10 -0
  311. package/dist/commands/up.js.map +1 -1
  312. package/dist/config-store.d.ts +7 -1
  313. package/dist/config-store.d.ts.map +1 -1
  314. package/dist/config-store.js +43 -0
  315. package/dist/config-store.js.map +1 -1
  316. package/dist/lib/scope/mission-composition.d.ts +5 -1
  317. package/dist/lib/scope/mission-composition.d.ts.map +1 -1
  318. package/dist/lib/scope/mission-composition.js +61 -1
  319. package/dist/lib/scope/mission-composition.js.map +1 -1
  320. package/dist/lib/scope/scope-fs.d.ts +2 -1
  321. package/dist/lib/scope/scope-fs.d.ts.map +1 -1
  322. package/dist/lib/scope/scope-fs.js +4 -4
  323. package/dist/lib/scope/scope-fs.js.map +1 -1
  324. package/dist/lib/scope/slice-claims.d.ts +8 -0
  325. package/dist/lib/scope/slice-claims.d.ts.map +1 -0
  326. package/dist/lib/scope/slice-claims.js +88 -0
  327. package/dist/lib/scope/slice-claims.js.map +1 -0
  328. package/dist/queue-receipt.d.ts +41 -0
  329. package/dist/queue-receipt.d.ts.map +1 -0
  330. package/dist/queue-receipt.js +272 -0
  331. package/dist/queue-receipt.js.map +1 -0
  332. package/package.json +1 -1
  333. package/tui/dist/config/config-model.d.ts.map +1 -1
  334. package/tui/dist/config/config-model.js +2 -0
  335. package/tui/dist/config/config-model.js.map +1 -1
  336. package/tui/dist/hydrate.d.ts.map +1 -1
  337. package/tui/dist/hydrate.js +11 -4
  338. package/tui/dist/hydrate.js.map +1 -1
  339. package/tui/dist/pulse/pulse-model.d.ts +1 -0
  340. package/tui/dist/pulse/pulse-model.d.ts.map +1 -1
  341. package/tui/dist/pulse/pulse-model.js +7 -3
  342. package/tui/dist/pulse/pulse-model.js.map +1 -1
  343. package/tui/dist/pulse/render-pulse.d.ts.map +1 -1
  344. package/tui/dist/pulse/render-pulse.js +3 -1
  345. package/tui/dist/pulse/render-pulse.js.map +1 -1
  346. package/tui/dist/render.d.ts.map +1 -1
  347. package/tui/dist/render.js +26 -11
  348. package/tui/dist/render.js.map +1 -1
  349. package/tui/dist/types.d.ts +3 -0
  350. package/tui/dist/types.d.ts.map +1 -1
  351. package/ui/dist/assets/{index-D5n8EwkG.js → index-DhmxKuZf.js} +159 -159
  352. package/ui/dist/index.html +1 -1
@@ -0,0 +1,895 @@
1
+ import { createHash } from "node:crypto";
2
+ import fs from "node:fs";
3
+ import nodePath from "node:path";
4
+ import Database from "better-sqlite3";
5
+ import { isStagedSendScriptPath } from "../adapters/tmux.js";
6
+ import { codexCommandResumeToken, listNativeProcesses, processArgv, verifyCodexPaneProcess, } from "./native-process-lineage.js";
7
+ import { CodexThreadIdResolver, resolveCodexDbPaths } from "./codex-thread-id.js";
8
+ import { isShellForeground } from "./shell-classifier.js";
9
+ import { terminatePaneInPlace } from "./pane-cutover.js";
10
+ import { rebindAndVerifyPaneIdentity } from "./seat-attention-reconciler.js";
11
+ import { CODEX_NETWORK_DEFAULT_OVERRIDE } from "./codex-network-default.js";
12
+ const RESTART_STARTED = "seat.restart_in_place_started";
13
+ const RESTART_FINISHED = "seat.restart_in_place_finished";
14
+ const API_KEY_NAMES = ["OPENAI_API_KEY", "CODEX_API_KEY"];
15
+ const NOT_VERIFIED = [
16
+ "Which account the restarted Codex actually uses: only `--verify` with --expect-account and a completed tool turn can confirm it.",
17
+ "Whether Codex saved the conversation before it exited (TERM, then KILL after the timeout).",
18
+ ];
19
+ export class CodexInPlaceRestart {
20
+ deps;
21
+ io;
22
+ timing;
23
+ listProcesses;
24
+ now;
25
+ daemonStartedAt;
26
+ constructor(deps) {
27
+ this.deps = deps;
28
+ this.io = { ...defaultInPlaceRestartIo(), ...deps.io };
29
+ this.timing = {
30
+ pollMs: deps.timing?.pollMs ?? 100,
31
+ timeoutMs: deps.timing?.timeoutMs ?? 3_000,
32
+ sleep: deps.timing?.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
33
+ };
34
+ this.listProcesses = deps.listProcesses ?? listNativeProcesses;
35
+ this.now = deps.now ?? (() => new Date());
36
+ this.daemonStartedAt = deps.daemonStartedAt ?? new Date(Date.now() - process.uptime() * 1000);
37
+ }
38
+ /** The newest started-event seq for a node; a request records it before waiting for the lifecycle lease. */
39
+ latestStartedSeq(nodeId) {
40
+ const row = this.deps.db.prepare("SELECT MAX(seq) AS seq FROM events WHERE node_id = ? AND type = ?")
41
+ .get(nodeId, RESTART_STARTED);
42
+ return row?.seq ?? 0;
43
+ }
44
+ /** Every gate, read-only. Returns the report a restart would start from, or the refusal it would give. */
45
+ async plan(target) {
46
+ const observed = await this.observe(target, null);
47
+ if (!observed.ok)
48
+ return observed;
49
+ return { ...observed.report, message: "Preview only: nothing was stopped or restarted." };
50
+ }
51
+ async execute(target, input) {
52
+ const observed = await this.observe(target, input.seenStartedSeq);
53
+ if (!observed.ok)
54
+ return observed;
55
+ const { report } = observed;
56
+ const { sessionName, seat } = target;
57
+ this.persist({
58
+ type: RESTART_STARTED, rigId: seat.rigId, nodeId: seat.nodeId, logicalId: seat.logicalId, sessionName,
59
+ pane: report.pane, paneShape: report.paneShape, oldPid: observed.codexPid, oldStartedAt: observed.codexStartedAt,
60
+ codexHome: report.codexHome, reason: input.reason.trim(), operator: input.operator,
61
+ });
62
+ // From here every exit records a result, including a throw: a started restart with no result would
63
+ // otherwise refuse every later restart and preview of this seat.
64
+ const progress = { stage: "stop", paneHeld: undefined };
65
+ let result;
66
+ try {
67
+ result = await this.restart(target, observed, progress);
68
+ }
69
+ catch (error) {
70
+ result = {
71
+ ...report, outcome: "native_unknown", recoveryCommand: observed.relaunch.core,
72
+ ...(progress.paneHeld ? { paneHeld: progress.paneHeld } : {}),
73
+ couldNotSee: [`what happened after an error during ${progress.stage}: ${error instanceof Error ? error.message : String(error)}`],
74
+ message: `The restart stopped with an error during ${progress.stage}; the seat's state was not observed afterwards.`,
75
+ };
76
+ }
77
+ // The Account lines already in the pane when the restart finished. --verify counts only lines beyond
78
+ // these, so text that was there before (anywhere in history or on screen) is never evidence.
79
+ const accountLinesAtRestart = result.outcome === "restarted_account_unverified"
80
+ ? accountLineCounts(await this.deps.tmux.capturePaneHistory(result.pane).catch(() => null))
81
+ : null;
82
+ this.persist({
83
+ type: RESTART_FINISHED, rigId: seat.rigId, nodeId: seat.nodeId, logicalId: seat.logicalId, sessionName,
84
+ outcome: result.outcome, newPid: result.newProcess?.pid ?? null, newStartedAt: result.newProcess?.startedAt ?? null,
85
+ codexHome: result.codexHome, paneHeld: result.paneHeld ? true : false, accountLinesAtRestart, finishedAt: this.now().toISOString(),
86
+ });
87
+ return result;
88
+ }
89
+ /** The restart after its started event. Updates `progress` so a throw is reported at its stage. */
90
+ async restart(target, observed, progress) {
91
+ const { report, codexPid, codexStartedAt, panePid, relaunch, priorRemainOnExit, cwd, oldExe } = observed;
92
+ const { sessionName } = target;
93
+ const pane = report.pane;
94
+ // What resumes the original by hand: the exact relaunch command, without the seat environment wrapper.
95
+ const recoveryCommand = relaunch.core;
96
+ let launch;
97
+ if (report.paneShape === "shell_backed") {
98
+ const stopped = await this.stopUnderShell(pane, panePid, codexPid, codexStartedAt);
99
+ if (stopped === "running") {
100
+ return { ...report, outcome: "not_started", recoveryCommand, message: "Codex did not exit after TERM and KILL; nothing was restarted and the original process is still running." };
101
+ }
102
+ if (stopped === "unknown") {
103
+ return { ...report, outcome: "native_unknown", recoveryCommand, couldNotSee: ["whether the original Codex exited and the pane's shell is back in the foreground"],
104
+ message: "Signals were sent, but the pane could not be observed afterwards; no resume was attempted." };
105
+ }
106
+ // The shell is back: type the exact resume command the checks ran on, built from the running Codex's
107
+ // choices, and prove the resume. Preparing the launch again here would decide those choices afresh.
108
+ progress.stage = "resume";
109
+ launch = await this.deps.codex.sendResumeCommand(sessionName, relaunch.command, report.conversationId);
110
+ if (!launch.ok)
111
+ return this.incomplete(report, launch, recoveryCommand);
112
+ progress.stage = "identity check";
113
+ return this.verifyReplacement(target, report, oldExe);
114
+ }
115
+ // Hold the pane before any signal: without remain-on-exit, Codex exiting would destroy the pane and
116
+ // there would be nothing to respawn into. The hold counts only once tmux reads it back as on.
117
+ progress.stage = "pane hold";
118
+ const set = await this.deps.tmux.setRemainOnExit(pane, true);
119
+ const readBack = await this.deps.tmux.getPaneLocalOption(pane, "remain-on-exit");
120
+ if (readBack !== "on") {
121
+ const undo = readBack !== priorRemainOnExit ? await this.deps.tmux.restorePaneOption(pane, "remain-on-exit", priorRemainOnExit) : { ok: true };
122
+ const why = !set.ok ? set.message : readBack === undefined ? "the setting could not be read back" : `it reads back as ${readBack ?? "unset"}`;
123
+ return { ...report, outcome: "not_started", recoveryCommand,
124
+ ...(undo.ok ? {} : { warnings: [...report.warnings, `remain-on-exit could not be put back to ${priorRemainOnExit ?? "unset"}: ${undo.message}`] }),
125
+ message: `Nothing was stopped: the pane could not be held with remain-on-exit (${why}), so the original Codex was not signalled and is still running.` };
126
+ }
127
+ const held = { option: "remain-on-exit", value: "on", previous: priorRemainOnExit,
128
+ detail: "Left on so the pane survives for recovery; restored only after a verified live replacement." };
129
+ progress.paneHeld = held;
130
+ progress.stage = "stop";
131
+ const terminated = await terminatePaneInPlace(this.deps.tmux, pane, this.timing, "Codex");
132
+ if (!terminated.ok)
133
+ return this.afterFailedStop(target, report, observed, held, recoveryCommand, terminated.message, progress);
134
+ // Respawn the dead pane WITHOUT -k (scrollback stays) with the resume command as its own command.
135
+ progress.stage = "respawn";
136
+ const respawned = await this.deps.tmux.respawnPane(pane, relaunch.command, { cwd });
137
+ if (!respawned.ok) {
138
+ return this.afterFailedStop(target, report, observed, held, recoveryCommand, `the pane could not be respawned: ${respawned.message}`, progress);
139
+ }
140
+ if (respawned.warnings)
141
+ report.warnings.push(...respawned.warnings);
142
+ progress.stage = "resume";
143
+ launch = await this.deps.codex.verifyResumeInPane(sessionName, report.conversationId);
144
+ if (!launch.ok)
145
+ return this.incomplete(report, launch, recoveryCommand, held);
146
+ progress.stage = "identity check";
147
+ const verified = await this.verifyReplacement(target, report, oldExe);
148
+ if (verified.outcome !== "restarted_account_unverified")
149
+ return { ...verified, paneHeld: held };
150
+ progress.stage = "restoring remain-on-exit";
151
+ const restored = await this.deps.tmux.restorePaneOption(pane, "remain-on-exit", priorRemainOnExit);
152
+ if (!restored.ok) {
153
+ return { ...verified, paneHeld: { ...held, detail: `The replacement is verified, but remain-on-exit could not be restored: ${restored.message}` } };
154
+ }
155
+ progress.paneHeld = undefined;
156
+ return verified;
157
+ }
158
+ /**
159
+ * `--verify`: after the operator has had the restarted seat complete a real tool-using turn (and, for
160
+ * account evidence, shown Codex's /status), read what Codex recorded. `moved` needs BOTH a completed
161
+ * tool call in this conversation since the restart, with no error event, AND an account line on the
162
+ * pane that names the expected account. Nothing is typed into the seat.
163
+ */
164
+ async verify(target, input) {
165
+ const rows = this.deps.db.prepare("SELECT type, payload FROM events WHERE node_id = ? AND type IN (?, ?) ORDER BY seq DESC LIMIT 1")
166
+ .all(target.seat.nodeId, RESTART_STARTED, RESTART_FINISHED);
167
+ const latest = rows[0];
168
+ if (!latest)
169
+ return { ok: false, code: "no_restart_recorded", message: "This seat has no recorded in-place restart to verify." };
170
+ if (latest.type === RESTART_STARTED)
171
+ return { ok: false, code: "restart_not_completed", message: "The latest in-place restart of this seat has not finished." };
172
+ const finished = JSON.parse(latest.payload);
173
+ if (finished.outcome !== "restarted_account_unverified") {
174
+ return { ok: false, code: "restart_not_completed", message: `The latest in-place restart ended ${finished.outcome}; there is nothing to verify.` };
175
+ }
176
+ const current = await this.observeCodex(target.binding.tmuxPane, null);
177
+ if (!current || current.process.pid !== finished.newPid || (current.process.startedAt ?? null) !== finished.newStartedAt) {
178
+ return { ok: false, code: "seat_restarted_since", message: "The seat's Codex is no longer the process the restart started; verify the current occupant another way." };
179
+ }
180
+ const conversationId = readResumeToken(this.deps.db, target.sessionId);
181
+ const toolTurn = this.readToolTurn(finished.codexHome, conversationId, finished.finishedAt);
182
+ const accountEvidence = await this.readAccountLine(target.binding.tmuxPane, finished.accountLinesAtRestart ?? null, input.expectAccount ?? null);
183
+ const moved = toolTurn.state === "observed" && toolTurn.completed > 0 && toolTurn.errors === 0 && accountEvidence.state === "matched";
184
+ return {
185
+ ok: true, seat: target.seat, outcome: moved ? "moved" : "restarted_account_unverified", toolTurn, accountEvidence,
186
+ message: moved
187
+ ? "Moved: a tool turn completed in the same conversation since the restart, and Codex's status names the expected account."
188
+ : "Account unverified: moved needs a completed tool turn since the restart AND Codex's status naming the expected account (--expect-account).",
189
+ };
190
+ }
191
+ // -- observation (shared by plan and execute) --
192
+ async observe(target, seenStartedSeq) {
193
+ const { binding, sessionName, seat } = target;
194
+ const history = this.restartHistory(seat.nodeId, seenStartedSeq);
195
+ if ("code" in history)
196
+ return history;
197
+ const pane = binding.tmuxPane;
198
+ let panes;
199
+ try {
200
+ panes = await this.deps.tmux.listPanes(sessionName);
201
+ }
202
+ catch (error) {
203
+ return { ok: false, code: "tmux_probe_failed", message: `tmux pane lookup failed: ${error.message}; nothing was stopped.` };
204
+ }
205
+ if (!pane || panes.length !== 1 || panes[0]?.id !== pane) {
206
+ return { ok: false, code: "pane_unverified", message: `Session "${sessionName}" must have exactly the seat's bound pane ${pane ?? "(none)"}; observed ${panes.map((p) => p.id).join(", ") || "no panes"}. Nothing was stopped.` };
207
+ }
208
+ const token = readResumeToken(this.deps.db, target.sessionId);
209
+ if (!token) {
210
+ return { ok: false, code: "resume_binding_unverified", message: "The seat's session has no stored Codex conversation id, so there is nothing to resume in place.",
211
+ guidance: "Record the conversation with: rig seat set-resume-token" };
212
+ }
213
+ const codex = await this.observeCodex(pane, token);
214
+ if (!codex) {
215
+ return { ok: false, code: "resume_binding_unverified", message: "No single Codex process in the seat's pane could be tied to the stored conversation (a running Codex that names another conversation is refused too). Nothing was stopped." };
216
+ }
217
+ const { process, panePid, rows } = codex;
218
+ const startedAt = process.startedAt ?? null;
219
+ const facts = await this.io.readProcess(process.pid);
220
+ if (!facts)
221
+ return { ok: false, code: "settings_unverified", message: "The running Codex's working directory and Codex home could not be read (this observer reads /proc on Linux). Nothing was stopped." };
222
+ const codexHome = facts.env.CODEX_HOME ?? (facts.env.HOME ? nodePath.join(facts.env.HOME, ".codex") : null);
223
+ if (!codexHome)
224
+ return { ok: false, code: "settings_unverified", message: "The running Codex's home could not be determined (no CODEX_HOME or HOME). Nothing was stopped." };
225
+ // The conversation the PROCESS is on: argv for a resumed Codex, its own native logs for a fresh one.
226
+ const argvToken = codexCommandResumeToken(process.command);
227
+ let conversationProof;
228
+ if (argvToken === token)
229
+ conversationProof = "argv";
230
+ else if (argvToken === undefined && startedAt && (await this.io.readThreadId(process.pid, startedAt, codexHome)) === token)
231
+ conversationProof = "native_logs";
232
+ else
233
+ return { ok: false, code: "resume_binding_unverified", message: "The running Codex could not be proven to be on the seat's stored conversation (checked: its argv, then its native logs). Nothing was stopped." };
234
+ // Pane shape: the pane's root is Codex itself, or a shell whose child chain is Codex.
235
+ const chain = codexChain(rows, process, panePid);
236
+ const top = chain.at(-1);
237
+ const root = rows.find((row) => row.pid === panePid);
238
+ let paneShape;
239
+ // OpenRig types a Codex launch (and this restart's relaunch) as `/bin/sh <staged script>`, so a managed
240
+ // Codex runs one sh below the pane's shell. Only that script is accepted between them.
241
+ const between = rows.find((row) => row.pid === top.ppid);
242
+ const viaLaunchScript = !!between && between.ppid === panePid && isStagedLaunchScript(between);
243
+ if (top.pid === panePid)
244
+ paneShape = "direct_native";
245
+ else if (root && (top.ppid === panePid || viaLaunchScript) && isShellForeground(executableBase(root)))
246
+ paneShape = "shell_backed";
247
+ else
248
+ return { ok: false, code: "pane_shape_unknown", message: "The pane is neither a shell running Codex (directly or through OpenRig's own launch script) nor Codex itself; nothing was stopped." };
249
+ // Where the relaunched Codex would run, and with which home.
250
+ let cwd;
251
+ let shellFacts = null;
252
+ if (paneShape === "shell_backed") {
253
+ shellFacts = await this.io.readProcess(panePid);
254
+ if (!shellFacts?.cwd)
255
+ return { ok: false, code: "settings_unverified", message: "The pane shell's working directory could not be read. Nothing was stopped." };
256
+ cwd = shellFacts.cwd;
257
+ }
258
+ else {
259
+ if (!facts.cwd)
260
+ return { ok: false, code: "settings_unverified", message: "The running Codex's working directory could not be read. Nothing was stopped." };
261
+ cwd = facts.cwd;
262
+ }
263
+ const relaunchBinding = { ...binding, cwd };
264
+ // The probed and prepared launch choices come from the running Codex, so the relaunch keeps them and no
265
+ // Codex process is started to decide them again.
266
+ const runningArgv = processArgv(process.command);
267
+ const relaunch = await this.deps.codex.buildResumeCommand(relaunchBinding, { name: sessionName, resumeToken: token, running: runningLaunch(runningArgv) });
268
+ if (!relaunch.ok)
269
+ return { ok: false, code: "relaunch_unavailable", message: `The resume command could not be built: ${relaunch.error}. Nothing was stopped.` };
270
+ const relaunchHome = envAssignment(relaunch.command, "CODEX_HOME")
271
+ ?? (paneShape === "shell_backed"
272
+ ? shellFacts.env.CODEX_HOME ?? (shellFacts.env.HOME ? nodePath.join(shellFacts.env.HOME, ".codex") : null)
273
+ : (await this.deps.tmux.getSessionEnv(sessionName, "CODEX_HOME")) ?? null);
274
+ const effective = parseEffectiveSettings(runningArgv);
275
+ const relaunched = parseEffectiveSettings(shellWords(relaunch.core));
276
+ const drift = [
277
+ ...settingsDrift(effective, relaunched),
278
+ ...(facts.cwd && facts.cwd !== cwd ? ["cwd"] : []),
279
+ ...(relaunchHome !== codexHome ? ["codex home"] : []),
280
+ ];
281
+ if (drift.length > 0) {
282
+ return { ok: false, code: "settings_drift", detail: { differs: drift },
283
+ message: `Relaunching now would change ${drift.join(", ")} from what the running Codex uses, so this account-only restart refuses. Nothing was stopped.`,
284
+ guidance: "A saved model/effort/profile choice applies at a managed launch that is meant to change settings, not here. Run rig seat restart --plan to compare." };
285
+ }
286
+ let priorRemainOnExit = null;
287
+ if (paneShape === "direct_native") {
288
+ const prior = await this.deps.tmux.getPaneLocalOption(pane, "remain-on-exit");
289
+ if (prior === undefined)
290
+ return { ok: false, code: "tmux_probe_failed", message: "The pane's remain-on-exit setting could not be read, so it could not be put back; nothing was stopped." };
291
+ priorRemainOnExit = prior;
292
+ }
293
+ const report = {
294
+ ok: true, planOnly: seenStartedSeq === null, message: "", seat, sessionName, pane, paneShape,
295
+ conversationId: token, conversationProof,
296
+ activity: this.activity(seat.nodeId),
297
+ childProcesses: descendants(rows, process.pid, chain).map((row) => ({ pid: row.pid, name: executableBase(row) })),
298
+ settings: { effective, saved: target.saved,
299
+ note: "The restart keeps the settings the running Codex uses (effective). Saved choices for the next managed launch are shown, not applied." },
300
+ codexHome,
301
+ billingRoute: { state: "unknown", apiKeyEnvNames: facts.apiKeyEnvNames,
302
+ detail: "Which billing route Codex uses is not observable without reading credentials; API-key variable names alone don't establish it." },
303
+ account: this.accountEvidence(codexHome, startedAt, null),
304
+ peers: await this.peers(seat.nodeId, codexHome),
305
+ oldProcess: { pid: process.pid, startedAt },
306
+ ...history,
307
+ warnings: [],
308
+ };
309
+ return { ok: true, report, codexPid: top.pid, codexStartedAt: top.startedAt ?? null, panePid, relaunch, priorRemainOnExit, cwd, oldExe: facts.exe };
310
+ }
311
+ /** Refuses while a restart of this seat is in flight under THIS daemon, or ran while the request waited.
312
+ * A started restart from before this daemon started can't still be running: it is reported, not refused. */
313
+ restartHistory(nodeId, seenStartedSeq) {
314
+ const rows = this.deps.db.prepare("SELECT seq, type, payload, created_at FROM events WHERE node_id = ? AND type IN (?, ?) ORDER BY seq DESC LIMIT 2")
315
+ .all(nodeId, RESTART_STARTED, RESTART_FINISHED);
316
+ const latest = rows[0];
317
+ const lastFinished = rows.find((row) => row.type === RESTART_FINISHED);
318
+ const lastRestart = lastFinished ? (() => {
319
+ const payload = JSON.parse(lastFinished.payload);
320
+ return { lastRestart: { outcome: payload.outcome, finishedAt: payload.finishedAt } };
321
+ })() : {};
322
+ if (seenStartedSeq !== null && this.latestStartedSeq(nodeId) > seenStartedSeq) {
323
+ return { ok: false, code: "restart_in_progress", message: "Another in-place restart of this seat ran while this request waited; not restarting again. Inspect its result first (--plan shows it as lastRestart)." };
324
+ }
325
+ if (latest?.type !== RESTART_STARTED)
326
+ return lastRestart;
327
+ const startedAt = new Date(`${latest.created_at.replace(" ", "T")}Z`);
328
+ const started = JSON.parse(latest.payload);
329
+ if (!(startedAt.getTime() < this.daemonStartedAt.getTime())) {
330
+ return { ok: false, code: "restart_in_progress", message: `An in-place restart of this seat started at ${startedAt.toISOString()} under this daemon and has not recorded a result yet; it is still running. --plan shows its result once it finishes.` };
331
+ }
332
+ return { ...lastRestart, staleRestart: {
333
+ startedAt: startedAt.toISOString(), oldPid: started.oldPid ?? null, pane: started.pane ?? null,
334
+ detail: `Started before this daemon did (${this.daemonStartedAt.toISOString()}) and never recorded a result, so it is not running. The seat's state above is observed now.`,
335
+ } };
336
+ }
337
+ async observeCodex(pane, token) {
338
+ const rows = await this.listProcesses();
339
+ const panePid = await this.deps.tmux.getPanePid(pane);
340
+ if (!panePid)
341
+ return null;
342
+ const observation = await verifyCodexPaneProcess({ target: pane, tmux: { getPanePid: async () => panePid },
343
+ listProcesses: () => rows, ...(token ? { expectedToken: token } : {}) });
344
+ return observation && observation.panePid === panePid ? { process: observation.process, panePid, rows } : null;
345
+ }
346
+ /** Shell-backed: TERM the Codex chain's top process (not the pane's shell), KILL after the timeout, then
347
+ * require the original process gone AND the shell back in the foreground. */
348
+ async stopUnderShell(pane, panePid, pid, startedAt) {
349
+ const gone = async () => {
350
+ const rows = await this.listProcesses();
351
+ if (rows.length === 0)
352
+ return null;
353
+ if (rows.some((row) => row.pid === pid && (row.startedAt ?? null) === startedAt))
354
+ return false;
355
+ const command = await this.deps.tmux.getPaneCommand(pane);
356
+ const root = rows.find((row) => row.pid === panePid);
357
+ return command !== null && isShellForeground(command) && !!root;
358
+ };
359
+ const wait = async () => {
360
+ const polls = Math.max(1, Math.ceil(this.timing.timeoutMs / this.timing.pollMs));
361
+ let last = null;
362
+ for (let i = 0; i <= polls; i++) {
363
+ last = await gone();
364
+ if (last)
365
+ return true;
366
+ if (i < polls)
367
+ await this.timing.sleep(this.timing.pollMs);
368
+ }
369
+ return last;
370
+ };
371
+ await this.deps.tmux.signalPid(pid, "TERM");
372
+ if (await wait())
373
+ return "stopped";
374
+ await this.deps.tmux.signalPid(pid, "KILL");
375
+ const last = await wait();
376
+ return last ? "stopped" : last === null ? "unknown" : "running";
377
+ }
378
+ async verifyReplacement(target, report, oldExe) {
379
+ // Screen readiness can precede native-process visibility. Repeat only the joined
380
+ // observations, never the launch. The budget counts yields, not observation I/O.
381
+ const polls = Math.max(1, Math.ceil(this.timing.timeoutMs / this.timing.pollMs));
382
+ for (let attempt = 0;; attempt++) {
383
+ const identity = await rebindAndVerifyPaneIdentity({
384
+ db: this.deps.db, sessionRegistry: this.deps.sessionRegistry, tmux: this.deps.tmux,
385
+ nodeId: target.seat.nodeId, sessionName: target.sessionName, runtime: "codex",
386
+ expectedResumeToken: report.conversationId, requireExactResumeLineage: true, listProcesses: this.listProcesses,
387
+ });
388
+ const now = await this.observeCodex(report.pane, report.conversationId);
389
+ if (!identity.ok || !now) {
390
+ if (attempt < polls) {
391
+ await this.timing.sleep(this.timing.pollMs);
392
+ continue;
393
+ }
394
+ return { ...report, outcome: "native_unknown", couldNotSee: [`the replacement Codex's identity: ${identity.ok ? "process not observed" : identity.detail}`],
395
+ message: "Codex reported a resumed conversation, but the process in the pane could not be proven to be that conversation." };
396
+ }
397
+ const after = await this.io.readProcess(now.process.pid);
398
+ const before = report.oldProcess;
399
+ const startedAt = now.process.startedAt ?? null;
400
+ return {
401
+ ...report, outcome: "restarted_account_unverified",
402
+ newProcess: { pid: now.process.pid, startedAt, sameExecutable: after?.exe && oldExe ? after.exe === oldExe : null },
403
+ account: this.accountEvidence(report.codexHome, before.startedAt, startedAt),
404
+ message: "Restarted in place on the same conversation and pane. The account is not verified: run --verify after a real tool-using turn.",
405
+ };
406
+ }
407
+ }
408
+ /** A direct-native stop or respawn that failed: report what the pane and the original process are now,
409
+ * as observed, never an assumed state. A pane that is gone holds nothing. */
410
+ async afterFailedStop(target, report, observed, held, recoveryCommand, failure, progress) {
411
+ const seen = await this.observeAfterStop(target.sessionName, report.pane, observed.codexPid, observed.codexStartedAt);
412
+ const observedAfterStop = { ...seen, detail: `pane ${seen.pane}, original Codex ${seen.process}` };
413
+ const paneHeld = seen.pane === "gone" ? undefined : held;
414
+ progress.paneHeld = paneHeld;
415
+ const base = { ...report, recoveryCommand, observedAfterStop, ...(paneHeld ? { paneHeld } : {}) };
416
+ if (seen.process === "running" && seen.pane === "present") {
417
+ return { ...base, outcome: "not_started", message: `The original Codex is still running (observed): ${failure}` };
418
+ }
419
+ if (seen.process === "exited" && seen.pane === "gone") {
420
+ return { ...base, outcome: "incomplete",
421
+ remainingStep: "The seat's pane is gone and nothing replaced it. Resume the conversation in a new pane for this seat with the recovery command.",
422
+ message: `The original Codex exited and its pane is gone (observed); nothing was resumed: ${failure}` };
423
+ }
424
+ if (seen.process === "exited") {
425
+ return { ...base, outcome: "incomplete", remainingStep: "Resolve the pane, then resume with the recovery command. Nothing was retried.",
426
+ message: `The original Codex exited (observed), but the resume was not started: ${failure}` };
427
+ }
428
+ return { ...base, outcome: "native_unknown", couldNotSee: [`the seat's state after the stop (${observedAfterStop.detail})`],
429
+ message: `The stop did not complete as expected and the seat's state is not fully known: ${failure}` };
430
+ }
431
+ async observeAfterStop(sessionName, pane, pid, startedAt) {
432
+ let paneState;
433
+ try {
434
+ paneState = (await this.deps.tmux.listPanes(sessionName)).some((candidate) => candidate.id === pane) ? "present" : "gone";
435
+ }
436
+ catch (error) {
437
+ paneState = /can't find|not found|no such/i.test(error.message) ? "gone" : "unknown";
438
+ }
439
+ const rows = await (async () => this.listProcesses())().catch(() => []);
440
+ const processState = rows.length === 0 ? "unknown"
441
+ : rows.some((row) => row.pid === pid && (row.startedAt ?? null) === startedAt) ? "running" : "exited";
442
+ return { pane: paneState, process: processState };
443
+ }
444
+ incomplete(report, launch, recoveryCommand, paneHeld) {
445
+ const signIn = /sign in/i.test(launch.error);
446
+ const missing = launch.recovery === "retry_fresh";
447
+ return {
448
+ ...report, outcome: "incomplete", recoveryCommand, ...(paneHeld ? { paneHeld } : {}),
449
+ account: { ...report.account, startupGate: launch.recovery === "attention_required" ? launch.error : null },
450
+ remainingStep: signIn
451
+ ? `Whoever owns the sign-in signs in to Codex in ${report.codexHome}, then the conversation resumes with the recovery command.`
452
+ : missing
453
+ ? `Codex in ${report.codexHome} has no saved session for this conversation; no fresh session was started. Find where it was saved before resuming.`
454
+ : "Resolve what Codex shows in the pane, then resume with the recovery command. Nothing was retried.",
455
+ message: `The original Codex exited, but the resume was not proven: ${launch.error}`,
456
+ };
457
+ }
458
+ activity(nodeId) {
459
+ const state = this.deps.activity?.getSeatState(nodeId) ?? null;
460
+ if (!state)
461
+ return { state: "unknown", needsInput: null, detail: "No activity evidence for this seat." };
462
+ const decided = state.decidedBy ? ` (decided by ${state.decidedBy})` : "";
463
+ if (state.needsInput.count > 0)
464
+ return { state: "needs_input", needsInput: state.needsInput.count, detail: `${state.needsInput.reason ?? "Waiting for input"}${decided}.` };
465
+ if (state.activity === "working")
466
+ return { state: "working", needsInput: state.needsInput.count, detail: `Mid-turn${decided}: restarting interrupts the turn.` };
467
+ if (state.activity === "idle-at-prompt")
468
+ return { state: "idle", needsInput: state.needsInput.count, detail: `Idle at the prompt${decided}.` };
469
+ return { state: "unknown", needsInput: state.needsInput.count, detail: `Activity is "${state.activity}"${decided}.` };
470
+ }
471
+ accountEvidence(codexHome, oldStartedAt, newStartedAt) {
472
+ const store = this.io.readCredentialStore(codexHome);
473
+ const auth = this.io.statAuthFile(codexHome);
474
+ const oldStart = oldStartedAt ? Date.parse(oldStartedAt) : NaN;
475
+ const newStart = newStartedAt ? Date.parse(newStartedAt) : NaN;
476
+ return {
477
+ verdict: "unverified",
478
+ credentialStore: store === undefined
479
+ ? { value: null, state: "unreadable", detail: "config.toml could not be read; the credential backend in effect is unknown." }
480
+ : store === null
481
+ ? { value: null, state: "unset", detail: "config.toml sets no cli_auth_credentials_store; Codex's default applies. Administrator requirements were not read." }
482
+ : { value: store, state: "read", detail: store === "file" ? "File-backed: a switched auth.json is what Codex reads." : `Backend "${store}": a copied auth.json may not change the account in use.` },
483
+ authFile: !auth
484
+ ? { changedBeforeRestart: null, detail: "No auth.json to stat in this home." }
485
+ : Number.isNaN(newStart)
486
+ ? { changedBeforeRestart: Number.isNaN(oldStart) ? null : auth.mtimeMs > oldStart,
487
+ detail: "Whether auth.json changed after the running Codex started (stat only; never read). A change is not proof of which account." }
488
+ : { changedBeforeRestart: auth.mtimeMs < newStart, detail: "auth.json was last modified before the replacement started (stat only; never read)." },
489
+ startupGate: null,
490
+ notVerified: NOT_VERIFIED,
491
+ };
492
+ }
493
+ async peers(nodeId, codexHome) {
494
+ const rows = this.deps.db.prepare(`
495
+ SELECT n.id, n.logical_id, b.tmux_session, b.tmux_pane FROM nodes n
496
+ LEFT JOIN bindings b ON b.node_id = n.id
497
+ WHERE n.runtime = 'codex' AND n.id != ? ORDER BY n.logical_id`).all(nodeId);
498
+ const reports = [];
499
+ for (const row of rows) {
500
+ let home = "unknown";
501
+ if (row.tmux_pane && row.tmux_session) {
502
+ const observed = await this.observeCodex(row.tmux_pane, null).catch(() => null);
503
+ const facts = observed ? await this.io.readProcess(observed.process.pid) : null;
504
+ const peerHome = facts?.env.CODEX_HOME ?? (facts?.env.HOME ? nodePath.join(facts.env.HOME, ".codex") : null);
505
+ if (peerHome)
506
+ home = peerHome === codexHome ? "same" : "different";
507
+ }
508
+ reports.push({ logicalId: row.logical_id, sessionName: row.tmux_session, home,
509
+ effect: home === "same"
510
+ ? `Not restarted; account unverified. Shares home ${codexHome}, so it may take the new account at its next start or token refresh.`
511
+ : home === "different" ? "Not restarted; uses a different Codex home." : "Not restarted; account unverified; its Codex home could not be observed." });
512
+ }
513
+ return reports;
514
+ }
515
+ readToolTurn(codexHome, conversationId, since) {
516
+ const unreadable = { completed: 0, errors: 0, since, state: "unreadable" };
517
+ if (!conversationId)
518
+ return unreadable;
519
+ const path = this.io.findRollout(codexHome, conversationId);
520
+ const text = path ? this.io.readRolloutTail(path) : null;
521
+ if (text === null)
522
+ return unreadable;
523
+ const sinceMs = Date.parse(since);
524
+ let completed = 0;
525
+ let errors = 0;
526
+ for (const line of text.split("\n")) {
527
+ if (!line.trim())
528
+ continue;
529
+ let entry;
530
+ try {
531
+ entry = JSON.parse(line);
532
+ }
533
+ catch {
534
+ continue;
535
+ }
536
+ if (!entry.timestamp || !(Date.parse(entry.timestamp) > sinceMs))
537
+ continue;
538
+ // Only the event kind and time are read; message and tool content are not.
539
+ if (entry.type === "response_item" && /_call_output$/.test(entry.payload?.type ?? ""))
540
+ completed += 1;
541
+ if (entry.type === "event_msg" && entry.payload?.type === "error")
542
+ errors += 1;
543
+ }
544
+ return { completed, errors, since, state: completed > 0 ? "observed" : "not_observed" };
545
+ }
546
+ /**
547
+ * Account evidence counts only when the line was produced after the restart. A cursor position can't show
548
+ * that (a TUI rewrites rows above and below the cursor), so this compares occurrences: every Account line
549
+ * in the pane's whole text when the restart finished was counted, and a line is new only when the same
550
+ * text now occurs more often than it did then. Text that moved, stayed or was redrawn never counts more
551
+ * than once; anything that can't be compared is not observed.
552
+ */
553
+ async readAccountLine(pane, atRestart, expectAccount) {
554
+ if (!expectAccount?.trim())
555
+ return { state: "not_requested", detail: "No --expect-account given, so no account-specific evidence was checked." };
556
+ if (atRestart === null)
557
+ return { state: "not_observed", detail: "The pane's text at the restart was not recorded, so a line produced since can't be told apart from earlier text." };
558
+ const text = await this.deps.tmux.capturePaneHistory(pane).catch(() => null);
559
+ if (text === null)
560
+ return { state: "not_observed", detail: "The pane's text could not be read." };
561
+ const seen = new Map();
562
+ let newest;
563
+ for (const line of accountLines(text)) {
564
+ const key = accountLineKey(line);
565
+ const count = (seen.get(key) ?? 0) + 1;
566
+ seen.set(key, count);
567
+ if (count > (atRestart[key] ?? 0))
568
+ newest = line;
569
+ }
570
+ if (!newest)
571
+ return { state: "not_observed", detail: "No Codex status Account line was produced since the restart (lines already in the pane then don't count); show /status in the seat first." };
572
+ return accountLineNames(newest, expectAccount)
573
+ ? { state: "matched", detail: "An Account line produced since the restart names the expected account." }
574
+ : { state: "not_matched", detail: "The newest Account line produced since the restart does not name the expected account (it may show only a plan)." };
575
+ }
576
+ persist(event) {
577
+ let persisted = null;
578
+ this.deps.db.transaction(() => {
579
+ persisted = this.deps.eventBus.persistWithinTransaction(event);
580
+ })();
581
+ if (persisted)
582
+ this.deps.eventBus.notifySubscribers(persisted);
583
+ }
584
+ }
585
+ // -- helpers --
586
+ function readResumeToken(db, sessionId) {
587
+ const row = db.prepare("SELECT resume_token, resume_type FROM sessions WHERE id = ?").get(sessionId);
588
+ const token = row?.resume_token?.trim();
589
+ return token && (row?.resume_type == null || row.resume_type === "codex_id") ? token : null;
590
+ }
591
+ function executableBase(row) {
592
+ const name = row.executableName ?? processArgv(row.command)[0] ?? "";
593
+ return (name.split("/").pop() ?? name).replace(/^-/, "");
594
+ }
595
+ /** The shells `/bin/sh` can run as. macOS's /bin/sh is a shim that runs bash (or dash or zsh, if so
596
+ * configured), so the OS names the staged script's process after that shell, not sh. */
597
+ const SH_RUNS_AS = new Set(["sh", "bash", "dash", "zsh"]);
598
+ /** `/bin/sh <path>` running a launch script that OpenRig's tmux adapter staged, and nothing else. */
599
+ function isStagedLaunchScript(row) {
600
+ const argv = processArgv(row.command);
601
+ return SH_RUNS_AS.has(executableBase(row)) && argv.length === 2 && /^(?:\/bin\/)?sh$/.test(argv[0]) && isStagedSendScriptPath(argv[1]);
602
+ }
603
+ /** The Codex process and its Codex launcher ancestors, deepest first, up to the pane root. */
604
+ function codexChain(rows, deepest, panePid) {
605
+ const byPid = new Map(rows.map((row) => [row.pid, row]));
606
+ const chain = [deepest];
607
+ let current = deepest;
608
+ while (current.pid !== panePid) {
609
+ const parent = byPid.get(current.ppid);
610
+ if (!parent)
611
+ break;
612
+ const name = executableBase(parent);
613
+ // A Codex launcher above the native binary: codex itself, or node running Codex's launcher script.
614
+ const launcher = /^codex/i.test(name) || (name === "node" && processArgv(parent.command).some((arg) => /codex/i.test(arg)));
615
+ if (!launcher)
616
+ break;
617
+ chain.push(parent);
618
+ current = parent;
619
+ }
620
+ return chain;
621
+ }
622
+ /** Processes under the Codex process, other than Codex itself. Reported, never refused. */
623
+ function descendants(rows, codexPid, chain) {
624
+ const own = new Set(chain.map((row) => row.pid));
625
+ const byParent = new Map();
626
+ for (const row of rows) {
627
+ const siblings = byParent.get(row.ppid);
628
+ if (siblings)
629
+ siblings.push(row);
630
+ else
631
+ byParent.set(row.ppid, [row]);
632
+ }
633
+ const found = [];
634
+ const queue = [...(byParent.get(codexPid) ?? [])];
635
+ const seen = new Set();
636
+ while (queue.length > 0) {
637
+ const row = queue.shift();
638
+ if (seen.has(row.pid))
639
+ continue;
640
+ seen.add(row.pid);
641
+ if (!own.has(row.pid))
642
+ found.push(row);
643
+ queue.push(...(byParent.get(row.pid) ?? []));
644
+ }
645
+ return found;
646
+ }
647
+ const VALUE_OPTIONS = {
648
+ "-m": "model", "--model": "model", "-p": "profile", "--profile": "profile", "-s": "sandbox", "--sandbox": "sandbox",
649
+ "-a": "approval", "--ask-for-approval": "approval", "-c": "config", "--config": "config",
650
+ "-C": null, "--cd": null, "--add-dir": null, "-i": null, "--image": null,
651
+ };
652
+ /** The launch choices a running Codex's argv records, reused so the relaunch keeps them without probing. */
653
+ function runningLaunch(argv) {
654
+ const addDirs = [];
655
+ const configs = [];
656
+ for (let index = 1; index < argv.length; index += 1) {
657
+ const arg = argv[index];
658
+ if (arg === "--add-dir" && argv[index + 1] !== undefined)
659
+ addDirs.push(argv[++index]);
660
+ else if ((arg === "-c" || arg === "--config") && argv[index + 1] !== undefined)
661
+ configs.push(argv[++index]);
662
+ else if (arg.startsWith("--config="))
663
+ configs.push(arg.slice("--config=".length));
664
+ }
665
+ return { networkOverride: configs.includes(CODEX_NETWORK_DEFAULT_OVERRIDE), daemonOptOut: argv.includes("--no-daemon"), addDirs };
666
+ }
667
+ /** The settings a Codex command line runs with. cwd, add-dirs, images and the resume target are not settings. */
668
+ export function parseEffectiveSettings(argv) {
669
+ const settings = { model: null, profile: null, sandbox: null, approval: null, bypass: false, config: [] };
670
+ for (let index = 1; index < argv.length; index += 1) {
671
+ let arg = argv[index];
672
+ let value;
673
+ const eq = arg.startsWith("--") ? arg.indexOf("=") : -1;
674
+ if (eq > 0) {
675
+ value = arg.slice(eq + 1);
676
+ arg = arg.slice(0, eq);
677
+ }
678
+ if (arg === "--dangerously-bypass-approvals-and-sandbox" || arg === "--yolo") {
679
+ settings.bypass = true;
680
+ continue;
681
+ }
682
+ if (arg === "--full-auto") {
683
+ settings.sandbox ??= "workspace-write";
684
+ settings.approval ??= "on-request";
685
+ continue;
686
+ }
687
+ const key = Object.hasOwn(VALUE_OPTIONS, arg) ? VALUE_OPTIONS[arg] : undefined;
688
+ if (key === undefined)
689
+ continue;
690
+ if (value === undefined) {
691
+ value = argv[index + 1];
692
+ index += 1;
693
+ }
694
+ if (key === null || value === undefined)
695
+ continue;
696
+ if (key === "config")
697
+ settings.config.push(value);
698
+ else
699
+ settings[key] = value;
700
+ }
701
+ settings.config.sort();
702
+ return settings;
703
+ }
704
+ /** Which settings differ. Config overrides are named by key only; their values may be paths. */
705
+ function settingsDrift(running, relaunch) {
706
+ const differs = [];
707
+ for (const key of ["model", "profile", "sandbox", "approval"])
708
+ if (running[key] !== relaunch[key])
709
+ differs.push(key);
710
+ if (running.bypass !== relaunch.bypass)
711
+ differs.push("approval/sandbox bypass");
712
+ const configKey = (entry) => entry.split("=")[0];
713
+ const a = new Set(running.config);
714
+ const b = new Set(relaunch.config);
715
+ const keys = new Set([...running.config.filter((entry) => !b.has(entry)), ...relaunch.config.filter((entry) => !a.has(entry))].map(configKey));
716
+ for (const key of [...keys].sort())
717
+ differs.push(key === "model_reasoning_effort" ? "effort" : `config ${key}`);
718
+ return differs;
719
+ }
720
+ /** Split a POSIX command line built with shellQuote (single quotes, double quotes, backslashes). */
721
+ export function shellWords(command) {
722
+ const words = [];
723
+ let current = "";
724
+ let has = false;
725
+ for (let i = 0; i < command.length; i += 1) {
726
+ const char = command[i];
727
+ if (char === "'") {
728
+ const end = command.indexOf("'", i + 1);
729
+ current += command.slice(i + 1, end < 0 ? command.length : end);
730
+ i = end < 0 ? command.length : end;
731
+ has = true;
732
+ }
733
+ else if (char === '"') {
734
+ i += 1;
735
+ while (i < command.length && command[i] !== '"') {
736
+ if (command[i] === "\\" && i + 1 < command.length)
737
+ i += 1;
738
+ current += command[i];
739
+ i += 1;
740
+ }
741
+ has = true;
742
+ }
743
+ else if (char === "\\" && i + 1 < command.length) {
744
+ current += command[i + 1];
745
+ i += 1;
746
+ has = true;
747
+ }
748
+ else if (/\s/.test(char)) {
749
+ if (has)
750
+ words.push(current);
751
+ current = "";
752
+ has = false;
753
+ }
754
+ else {
755
+ current += char;
756
+ has = true;
757
+ }
758
+ }
759
+ if (has)
760
+ words.push(current);
761
+ return words;
762
+ }
763
+ /** A NAME=value assignment in a launch wrapper (`env NAME=…` or `/usr/bin/env 'NAME=…'`), before the command. */
764
+ function envAssignment(command, name) {
765
+ for (const word of shellWords(command)) {
766
+ if (word.startsWith(`${name}=`))
767
+ return word.slice(name.length + 1);
768
+ if (/^codex$|\/codex$/.test(word))
769
+ break;
770
+ }
771
+ return null;
772
+ }
773
+ /** The pane's status `Account:` lines, trimmed of frame borders, in order. */
774
+ function accountLines(text) {
775
+ return text.split("\n").map((line) => line.replace(/^[\s│|]+|[\s│|]+$/g, "")).filter((line) => /^Account:\s*\S/i.test(line));
776
+ }
777
+ /** A line's key in the recorded counts: a digest, so the event log holds no account names. */
778
+ function accountLineKey(line) {
779
+ return createHash("sha256").update(line).digest("hex").slice(0, 16);
780
+ }
781
+ /** How often each Account line occurs in the pane's text; null when the text couldn't be read. */
782
+ function accountLineCounts(text) {
783
+ if (text === null)
784
+ return null;
785
+ const counts = {};
786
+ for (const line of accountLines(text))
787
+ counts[accountLineKey(line)] = (counts[accountLineKey(line)] ?? 0) + 1;
788
+ return counts;
789
+ }
790
+ /** Whether a status `Account:` line names `expected` as a whole value: `bob@corp.com` doesn't match
791
+ * `jimbob@corp.com` or `bob@corp.com.au`. Case-insensitive, as account names are. */
792
+ export function accountLineNames(line, expected) {
793
+ const want = expected.trim();
794
+ if (!want)
795
+ return false;
796
+ const escaped = want.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
797
+ return new RegExp(`(^|[^A-Za-z0-9._%+@-])${escaped}($|[^A-Za-z0-9._%+@-])`, "i").test(line.replace(/^Account:\s*/i, " "));
798
+ }
799
+ export function defaultInPlaceRestartIo() {
800
+ return {
801
+ async readProcess(pid) {
802
+ try {
803
+ const environ = fs.readFileSync(`/proc/${pid}/environ`);
804
+ const env = {};
805
+ const apiKeyEnvNames = [];
806
+ for (const entry of environ.toString("utf8").split("\0")) {
807
+ const eq = entry.indexOf("=");
808
+ if (eq <= 0)
809
+ continue;
810
+ const key = entry.slice(0, eq);
811
+ if (key === "CODEX_HOME" || key === "HOME")
812
+ env[key] = entry.slice(eq + 1);
813
+ else if (API_KEY_NAMES.includes(key) && eq < entry.length - 1)
814
+ apiKeyEnvNames.push(key);
815
+ }
816
+ const read = (link) => { try {
817
+ return fs.readlinkSync(`/proc/${pid}/${link}`);
818
+ }
819
+ catch {
820
+ return null;
821
+ } };
822
+ return { cwd: read("cwd"), exe: read("exe"), env, apiKeyEnvNames: apiKeyEnvNames.sort() };
823
+ }
824
+ catch {
825
+ return null;
826
+ }
827
+ },
828
+ async readThreadId(pid, startedAt, codexHome) {
829
+ return new CodexThreadIdResolver({ codexHome }).resolve(pid, startedAt);
830
+ },
831
+ readCredentialStore(codexHome) {
832
+ let text;
833
+ try {
834
+ text = fs.readFileSync(nodePath.join(codexHome, "config.toml"), "utf8");
835
+ }
836
+ catch (error) {
837
+ return error.code === "ENOENT" ? null : undefined;
838
+ }
839
+ for (const line of text.split("\n")) {
840
+ if (/^\s*\[/.test(line))
841
+ break; // top-level keys only
842
+ const match = line.match(/^\s*cli_auth_credentials_store\s*=\s*["']([^"']*)["']/);
843
+ if (match)
844
+ return match[1];
845
+ }
846
+ return null;
847
+ },
848
+ statAuthFile(codexHome) {
849
+ try {
850
+ return { mtimeMs: fs.statSync(nodePath.join(codexHome, "auth.json")).mtimeMs };
851
+ }
852
+ catch {
853
+ return null;
854
+ }
855
+ },
856
+ findRollout(codexHome, conversationId) {
857
+ for (const dbPath of resolveCodexDbPaths("", "state", undefined, codexHome)) {
858
+ try {
859
+ const db = new Database(dbPath, { readonly: true });
860
+ try {
861
+ const row = db.prepare("SELECT rollout_path FROM threads WHERE id = ?").get(conversationId);
862
+ if (row?.rollout_path)
863
+ return row.rollout_path;
864
+ }
865
+ finally {
866
+ db.close();
867
+ }
868
+ }
869
+ catch { /* no native state in this file */ }
870
+ }
871
+ return null;
872
+ },
873
+ readRolloutTail(path) {
874
+ const maxBytes = 4 * 1024 * 1024;
875
+ try {
876
+ const fd = fs.openSync(path, "r");
877
+ try {
878
+ const size = fs.fstatSync(fd).size;
879
+ const start = Math.max(0, size - maxBytes);
880
+ const buffer = Buffer.alloc(size - start);
881
+ fs.readSync(fd, buffer, 0, buffer.length, start);
882
+ const text = buffer.toString("utf8");
883
+ return start > 0 ? text.slice(text.indexOf("\n") + 1) : text;
884
+ }
885
+ finally {
886
+ fs.closeSync(fd);
887
+ }
888
+ }
889
+ catch {
890
+ return null;
891
+ }
892
+ },
893
+ };
894
+ }
895
+ //# sourceMappingURL=seat-restart-in-place.js.map