@with-logic/elwood 0.2.0 → 0.2.2

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 (878) hide show
  1. package/CHANGELOG.md +571 -1
  2. package/CONTRIBUTING.md +3 -0
  3. package/README.md +6 -2
  4. package/dist/bridge/socket-release.d.ts +15 -0
  5. package/dist/bridge/socket-release.d.ts.map +1 -0
  6. package/dist/bridge/socket-release.js +22 -0
  7. package/dist/bridge/socket-release.js.map +1 -0
  8. package/dist/claude/composer/border.d.ts +12 -0
  9. package/dist/claude/composer/border.d.ts.map +1 -0
  10. package/dist/claude/composer/border.js +12 -0
  11. package/dist/claude/composer/border.js.map +1 -0
  12. package/dist/claude/composer/empty-input.d.ts +15 -0
  13. package/dist/claude/composer/empty-input.d.ts.map +1 -1
  14. package/dist/claude/composer/empty-input.js +30 -11
  15. package/dist/claude/composer/empty-input.js.map +1 -1
  16. package/dist/claude/composer/footer.d.ts +26 -2
  17. package/dist/claude/composer/footer.d.ts.map +1 -1
  18. package/dist/claude/composer/footer.js +29 -2
  19. package/dist/claude/composer/footer.js.map +1 -1
  20. package/dist/claude/composer/idle.d.ts +14 -0
  21. package/dist/claude/composer/idle.d.ts.map +1 -0
  22. package/dist/claude/composer/idle.js +17 -0
  23. package/dist/claude/composer/idle.js.map +1 -0
  24. package/dist/claude/composer/last.d.ts +2 -0
  25. package/dist/claude/composer/last.d.ts.map +1 -0
  26. package/dist/claude/composer/last.js +15 -0
  27. package/dist/claude/composer/last.js.map +1 -0
  28. package/dist/claude/composer/staged-input.d.ts.map +1 -1
  29. package/dist/claude/composer/staged-input.js +2 -1
  30. package/dist/claude/composer/staged-input.js.map +1 -1
  31. package/dist/claude/hooks/stop-failure.d.ts +15 -2
  32. package/dist/claude/hooks/stop-failure.d.ts.map +1 -1
  33. package/dist/claude/hooks/stop-failure.js +32 -9
  34. package/dist/claude/hooks/stop-failure.js.map +1 -1
  35. package/dist/claude/login/stages.d.ts.map +1 -1
  36. package/dist/claude/login/stages.js +2 -2
  37. package/dist/claude/login/stages.js.map +1 -1
  38. package/dist/claude/model-picker.d.ts +1 -1
  39. package/dist/claude/model-picker.d.ts.map +1 -1
  40. package/dist/claude/model-picker.js +27 -10
  41. package/dist/claude/model-picker.js.map +1 -1
  42. package/dist/claude/preflight.d.ts +1 -1
  43. package/dist/claude/preflight.d.ts.map +1 -1
  44. package/dist/claude/preflight.js +18 -12
  45. package/dist/claude/preflight.js.map +1 -1
  46. package/dist/claude/questions/navigation.d.ts.map +1 -1
  47. package/dist/claude/questions/navigation.js +1 -2
  48. package/dist/claude/questions/navigation.js.map +1 -1
  49. package/dist/claude/questions/screen.d.ts.map +1 -1
  50. package/dist/claude/questions/screen.js +10 -2
  51. package/dist/claude/questions/screen.js.map +1 -1
  52. package/dist/claude/screen-table.d.ts +8 -5
  53. package/dist/claude/screen-table.d.ts.map +1 -1
  54. package/dist/claude/screen-table.js +20 -12
  55. package/dist/claude/screen-table.js.map +1 -1
  56. package/dist/claude/session/bridge.d.ts +2 -1
  57. package/dist/claude/session/bridge.d.ts.map +1 -1
  58. package/dist/claude/session/bridge.js +3 -1
  59. package/dist/claude/session/bridge.js.map +1 -1
  60. package/dist/claude/session/build.d.ts.map +1 -1
  61. package/dist/claude/session/build.js +13 -11
  62. package/dist/claude/session/build.js.map +1 -1
  63. package/dist/claude/session/cleanup.d.ts +18 -0
  64. package/dist/claude/session/cleanup.d.ts.map +1 -0
  65. package/dist/claude/session/cleanup.js +10 -0
  66. package/dist/claude/session/cleanup.js.map +1 -0
  67. package/dist/claude/session/index.d.ts.map +1 -1
  68. package/dist/claude/session/index.js +27 -13
  69. package/dist/claude/session/index.js.map +1 -1
  70. package/dist/claude/session/instance.d.ts +7 -8
  71. package/dist/claude/session/instance.d.ts.map +1 -1
  72. package/dist/claude/session/instance.js +9 -13
  73. package/dist/claude/session/instance.js.map +1 -1
  74. package/dist/claude/session/resume.d.ts.map +1 -1
  75. package/dist/claude/session/resume.js +14 -5
  76. package/dist/claude/session/resume.js.map +1 -1
  77. package/dist/claude/session/transcript.js +2 -2
  78. package/dist/claude/session/transcript.js.map +1 -1
  79. package/dist/claude/simple.d.ts +1 -1
  80. package/dist/claude/simple.d.ts.map +1 -1
  81. package/dist/claude/simple.js +3 -3
  82. package/dist/claude/simple.js.map +1 -1
  83. package/dist/claude/startup-prompts.d.ts +2 -0
  84. package/dist/claude/startup-prompts.d.ts.map +1 -1
  85. package/dist/claude/startup-prompts.js +5 -1
  86. package/dist/claude/startup-prompts.js.map +1 -1
  87. package/dist/claude/transcript/cursor-io.d.ts +1 -1
  88. package/dist/claude/transcript/cursor-io.d.ts.map +1 -1
  89. package/dist/claude/transcript/cursor-io.js +1 -1
  90. package/dist/claude/transcript/cursor-io.js.map +1 -1
  91. package/dist/claude/transcript/cursor.d.ts +5 -3
  92. package/dist/claude/transcript/cursor.d.ts.map +1 -1
  93. package/dist/claude/transcript/cursor.js +18 -8
  94. package/dist/claude/transcript/cursor.js.map +1 -1
  95. package/dist/claude/transcript/drain-context.d.ts +4 -3
  96. package/dist/claude/transcript/drain-context.d.ts.map +1 -1
  97. package/dist/claude/transcript/drain-context.js.map +1 -1
  98. package/dist/claude/transcript/emit.d.ts +2 -1
  99. package/dist/claude/transcript/emit.d.ts.map +1 -1
  100. package/dist/claude/transcript/emit.js +1 -0
  101. package/dist/claude/transcript/emit.js.map +1 -1
  102. package/dist/claude/transcript/index.d.ts.map +1 -1
  103. package/dist/claude/transcript/index.js +2 -2
  104. package/dist/claude/transcript/index.js.map +1 -1
  105. package/dist/claude/transcript/warnings.d.ts +2 -1
  106. package/dist/claude/transcript/warnings.d.ts.map +1 -1
  107. package/dist/claude/transcript/warnings.js +3 -2
  108. package/dist/claude/transcript/warnings.js.map +1 -1
  109. package/dist/cli/config/effective.d.ts +2 -2
  110. package/dist/cli/config/effective.d.ts.map +1 -1
  111. package/dist/cli/config/effective.js +0 -2
  112. package/dist/cli/config/effective.js.map +1 -1
  113. package/dist/cli/interactive/spawn.d.ts +0 -2
  114. package/dist/cli/interactive/spawn.d.ts.map +1 -1
  115. package/dist/cli/interactive/spawn.js +27 -24
  116. package/dist/cli/interactive/spawn.js.map +1 -1
  117. package/dist/cli/lifecycle/index.d.ts.map +1 -1
  118. package/dist/cli/lifecycle/index.js +5 -3
  119. package/dist/cli/lifecycle/index.js.map +1 -1
  120. package/dist/cli/main-failure.d.ts.map +1 -1
  121. package/dist/cli/main-failure.js +5 -2
  122. package/dist/cli/main-failure.js.map +1 -1
  123. package/dist/cli/models/combined.d.ts.map +1 -1
  124. package/dist/cli/models/combined.js +3 -2
  125. package/dist/cli/models/combined.js.map +1 -1
  126. package/dist/cli/models/render.d.ts.map +1 -1
  127. package/dist/cli/models/render.js +2 -1
  128. package/dist/cli/models/render.js.map +1 -1
  129. package/dist/cli/output/cleanup-error.d.ts +22 -0
  130. package/dist/cli/output/cleanup-error.d.ts.map +1 -0
  131. package/dist/cli/output/cleanup-error.js +67 -0
  132. package/dist/cli/output/cleanup-error.js.map +1 -0
  133. package/dist/cli/output/types.d.ts +6 -2
  134. package/dist/cli/output/types.d.ts.map +1 -1
  135. package/dist/cli/request/agent-detect.d.ts +6 -2
  136. package/dist/cli/request/agent-detect.d.ts.map +1 -1
  137. package/dist/cli/request/agent-detect.js +14 -6
  138. package/dist/cli/request/agent-detect.js.map +1 -1
  139. package/dist/cli/request/finalize.d.ts +1 -1
  140. package/dist/cli/request/finalize.d.ts.map +1 -1
  141. package/dist/cli/request/finalize.js +4 -25
  142. package/dist/cli/request/finalize.js.map +1 -1
  143. package/dist/cli/request/index.d.ts.map +1 -1
  144. package/dist/cli/request/index.js +11 -5
  145. package/dist/cli/request/index.js.map +1 -1
  146. package/dist/cli/request/path-replacement.d.ts +27 -0
  147. package/dist/cli/request/path-replacement.d.ts.map +1 -0
  148. package/dist/cli/request/path-replacement.js +79 -0
  149. package/dist/cli/request/path-replacement.js.map +1 -0
  150. package/dist/cli/request/workspace.d.ts +8 -0
  151. package/dist/cli/request/workspace.d.ts.map +1 -0
  152. package/dist/cli/request/workspace.js +50 -0
  153. package/dist/cli/request/workspace.js.map +1 -0
  154. package/dist/cli/run/output.d.ts.map +1 -1
  155. package/dist/cli/run/output.js +2 -1
  156. package/dist/cli/run/output.js.map +1 -1
  157. package/dist/cli/run/subscribe.d.ts.map +1 -1
  158. package/dist/cli/run/subscribe.js +1 -1
  159. package/dist/cli/run/subscribe.js.map +1 -1
  160. package/dist/cli/session/index.d.ts +4 -4
  161. package/dist/cli/session/index.d.ts.map +1 -1
  162. package/dist/cli/session/index.js +5 -5
  163. package/dist/cli/session/index.js.map +1 -1
  164. package/dist/cli/session/launch.d.ts +2 -2
  165. package/dist/cli/session/launch.d.ts.map +1 -1
  166. package/dist/cli/session/launch.js +12 -3
  167. package/dist/cli/session/launch.js.map +1 -1
  168. package/dist/cli/session/turn-readers.d.ts +14 -0
  169. package/dist/cli/session/turn-readers.d.ts.map +1 -0
  170. package/dist/cli/session/turn-readers.js +13 -0
  171. package/dist/cli/session/turn-readers.js.map +1 -0
  172. package/dist/codex/config/default-model.d.ts +24 -0
  173. package/dist/codex/config/default-model.d.ts.map +1 -0
  174. package/dist/codex/config/default-model.js +133 -0
  175. package/dist/codex/config/default-model.js.map +1 -0
  176. package/dist/codex/config/home.d.ts +35 -0
  177. package/dist/codex/config/home.d.ts.map +1 -0
  178. package/dist/codex/config/home.js +122 -0
  179. package/dist/codex/config/home.js.map +1 -0
  180. package/dist/codex/config/lock.d.ts +17 -11
  181. package/dist/codex/config/lock.d.ts.map +1 -1
  182. package/dist/codex/config/lock.js +102 -11
  183. package/dist/codex/config/lock.js.map +1 -1
  184. package/dist/codex/config/read.d.ts +16 -0
  185. package/dist/codex/config/read.d.ts.map +1 -0
  186. package/dist/codex/config/read.js +75 -0
  187. package/dist/codex/config/read.js.map +1 -0
  188. package/dist/codex/config/restore.d.ts +17 -7
  189. package/dist/codex/config/restore.d.ts.map +1 -1
  190. package/dist/codex/config/restore.js +33 -20
  191. package/dist/codex/config/restore.js.map +1 -1
  192. package/dist/codex/config/transaction.d.ts +6 -4
  193. package/dist/codex/config/transaction.d.ts.map +1 -1
  194. package/dist/codex/config/transaction.js +4 -3
  195. package/dist/codex/config/transaction.js.map +1 -1
  196. package/dist/codex/images/clipboard-copies.d.ts +39 -0
  197. package/dist/codex/images/clipboard-copies.d.ts.map +1 -0
  198. package/dist/codex/images/clipboard-copies.js +91 -0
  199. package/dist/codex/images/clipboard-copies.js.map +1 -0
  200. package/dist/codex/model-picker.d.ts +1 -1
  201. package/dist/codex/model-picker.d.ts.map +1 -1
  202. package/dist/codex/model-picker.js +16 -6
  203. package/dist/codex/model-picker.js.map +1 -1
  204. package/dist/codex/preflight.d.ts +2 -2
  205. package/dist/codex/preflight.d.ts.map +1 -1
  206. package/dist/codex/preflight.js +28 -21
  207. package/dist/codex/preflight.js.map +1 -1
  208. package/dist/codex/pty.d.ts +25 -3
  209. package/dist/codex/pty.d.ts.map +1 -1
  210. package/dist/codex/pty.js +53 -7
  211. package/dist/codex/pty.js.map +1 -1
  212. package/dist/codex/questions/candidates.d.ts.map +1 -1
  213. package/dist/codex/questions/candidates.js +4 -5
  214. package/dist/codex/questions/candidates.js.map +1 -1
  215. package/dist/codex/questions/driver.js +2 -2
  216. package/dist/codex/questions/driver.js.map +1 -1
  217. package/dist/codex/questions/records.d.ts +1 -1
  218. package/dist/codex/questions/records.d.ts.map +1 -1
  219. package/dist/codex/questions/records.js +1 -3
  220. package/dist/codex/questions/records.js.map +1 -1
  221. package/dist/codex/screen/clearance.d.ts +6 -0
  222. package/dist/codex/screen/clearance.d.ts.map +1 -1
  223. package/dist/codex/screen/clearance.js +10 -0
  224. package/dist/codex/screen/clearance.js.map +1 -1
  225. package/dist/codex/screen-table.d.ts.map +1 -1
  226. package/dist/codex/screen-table.js +6 -2
  227. package/dist/codex/screen-table.js.map +1 -1
  228. package/dist/codex/session/bridge.d.ts +1 -0
  229. package/dist/codex/session/bridge.d.ts.map +1 -1
  230. package/dist/codex/session/bridge.js +3 -1
  231. package/dist/codex/session/bridge.js.map +1 -1
  232. package/dist/codex/session/build.d.ts.map +1 -1
  233. package/dist/codex/session/build.js +14 -12
  234. package/dist/codex/session/build.js.map +1 -1
  235. package/dist/codex/session/cleanup.d.ts +2 -2
  236. package/dist/codex/session/cleanup.d.ts.map +1 -1
  237. package/dist/codex/session/cleanup.js +8 -11
  238. package/dist/codex/session/cleanup.js.map +1 -1
  239. package/dist/codex/session/config-switch.d.ts +20 -0
  240. package/dist/codex/session/config-switch.d.ts.map +1 -0
  241. package/dist/codex/session/config-switch.js +42 -0
  242. package/dist/codex/session/config-switch.js.map +1 -0
  243. package/dist/codex/session/hooks.d.ts.map +1 -1
  244. package/dist/codex/session/hooks.js +7 -0
  245. package/dist/codex/session/hooks.js.map +1 -1
  246. package/dist/codex/session/index.d.ts.map +1 -1
  247. package/dist/codex/session/index.js +34 -16
  248. package/dist/codex/session/index.js.map +1 -1
  249. package/dist/codex/session/instance.d.ts +10 -12
  250. package/dist/codex/session/instance.d.ts.map +1 -1
  251. package/dist/codex/session/instance.js +42 -37
  252. package/dist/codex/session/instance.js.map +1 -1
  253. package/dist/codex/session/readiness.d.ts +11 -0
  254. package/dist/codex/session/readiness.d.ts.map +1 -0
  255. package/dist/codex/session/readiness.js +22 -0
  256. package/dist/codex/session/readiness.js.map +1 -0
  257. package/dist/codex/session/resume.d.ts.map +1 -1
  258. package/dist/codex/session/resume.js +9 -4
  259. package/dist/codex/session/resume.js.map +1 -1
  260. package/dist/codex/session/transcript.d.ts.map +1 -1
  261. package/dist/codex/session/transcript.js +12 -1
  262. package/dist/codex/session/transcript.js.map +1 -1
  263. package/dist/codex/session/turn-commit.d.ts +58 -0
  264. package/dist/codex/session/turn-commit.d.ts.map +1 -0
  265. package/dist/codex/session/turn-commit.js +97 -0
  266. package/dist/codex/session/turn-commit.js.map +1 -0
  267. package/dist/codex/session/warnings.d.ts +13 -6
  268. package/dist/codex/session/warnings.d.ts.map +1 -1
  269. package/dist/codex/session/warnings.js +36 -9
  270. package/dist/codex/session/warnings.js.map +1 -1
  271. package/dist/codex/startup-prompts.d.ts +3 -0
  272. package/dist/codex/startup-prompts.d.ts.map +1 -1
  273. package/dist/codex/startup-prompts.js +20 -13
  274. package/dist/codex/startup-prompts.js.map +1 -1
  275. package/dist/codex/startup-warnings.d.ts +2 -0
  276. package/dist/codex/startup-warnings.d.ts.map +1 -1
  277. package/dist/codex/startup-warnings.js +6 -0
  278. package/dist/codex/startup-warnings.js.map +1 -1
  279. package/dist/codex/transcript/payload.d.ts +8 -0
  280. package/dist/codex/transcript/payload.d.ts.map +1 -0
  281. package/dist/codex/transcript/payload.js +11 -0
  282. package/dist/codex/transcript/payload.js.map +1 -0
  283. package/dist/codex/transcript/rejection.d.ts +15 -0
  284. package/dist/codex/transcript/rejection.d.ts.map +1 -0
  285. package/dist/codex/transcript/rejection.js +44 -0
  286. package/dist/codex/transcript/rejection.js.map +1 -0
  287. package/dist/codex/transcript/turn-end.d.ts +8 -0
  288. package/dist/codex/transcript/turn-end.d.ts.map +1 -0
  289. package/dist/codex/transcript/turn-end.js +17 -0
  290. package/dist/codex/transcript/turn-end.js.map +1 -0
  291. package/dist/codex/transcript/watcher.d.ts +5 -2
  292. package/dist/codex/transcript/watcher.d.ts.map +1 -1
  293. package/dist/codex/transcript/watcher.js +11 -6
  294. package/dist/codex/transcript/watcher.js.map +1 -1
  295. package/dist/codex/trust-retry.d.ts +12 -0
  296. package/dist/codex/trust-retry.d.ts.map +1 -0
  297. package/dist/codex/trust-retry.js +12 -0
  298. package/dist/codex/trust-retry.js.map +1 -0
  299. package/dist/codex/update/skip-attempt.d.ts +2 -0
  300. package/dist/codex/update/skip-attempt.d.ts.map +1 -1
  301. package/dist/codex/update/skip-attempt.js +4 -0
  302. package/dist/codex/update/skip-attempt.js.map +1 -1
  303. package/dist/codex/update/tracker.d.ts +2 -0
  304. package/dist/codex/update/tracker.d.ts.map +1 -1
  305. package/dist/codex/update/tracker.js +4 -0
  306. package/dist/codex/update/tracker.js.map +1 -1
  307. package/dist/core/delay.d.ts +3 -2
  308. package/dist/core/delay.d.ts.map +1 -1
  309. package/dist/core/delay.js +3 -2
  310. package/dist/core/delay.js.map +1 -1
  311. package/dist/core/errors.d.ts +33 -4
  312. package/dist/core/errors.d.ts.map +1 -1
  313. package/dist/core/errors.js +51 -7
  314. package/dist/core/errors.js.map +1 -1
  315. package/dist/core/images/materialize.d.ts +13 -4
  316. package/dist/core/images/materialize.d.ts.map +1 -1
  317. package/dist/core/images/materialize.js +48 -11
  318. package/dist/core/images/materialize.js.map +1 -1
  319. package/dist/core/input/abort.d.ts +15 -0
  320. package/dist/core/input/abort.d.ts.map +1 -1
  321. package/dist/core/input/abort.js +18 -2
  322. package/dist/core/input/abort.js.map +1 -1
  323. package/dist/core/input/constants.d.ts +4 -1
  324. package/dist/core/input/constants.d.ts.map +1 -1
  325. package/dist/core/input/constants.js +4 -1
  326. package/dist/core/input/constants.js.map +1 -1
  327. package/dist/core/input/index.d.ts +1 -1
  328. package/dist/core/input/index.d.ts.map +1 -1
  329. package/dist/core/input/index.js +1 -1
  330. package/dist/core/input/index.js.map +1 -1
  331. package/dist/core/models/clearance.d.ts +38 -0
  332. package/dist/core/models/clearance.d.ts.map +1 -0
  333. package/dist/core/models/clearance.js +38 -0
  334. package/dist/core/models/clearance.js.map +1 -0
  335. package/dist/core/models/close-wait.d.ts +17 -0
  336. package/dist/core/models/close-wait.d.ts.map +1 -0
  337. package/dist/core/models/close-wait.js +44 -0
  338. package/dist/core/models/close-wait.js.map +1 -0
  339. package/dist/core/models/dialog-region.d.ts +18 -3
  340. package/dist/core/models/dialog-region.d.ts.map +1 -1
  341. package/dist/core/models/dialog-region.js +29 -40
  342. package/dist/core/models/dialog-region.js.map +1 -1
  343. package/dist/core/models/input.d.ts +10 -1
  344. package/dist/core/models/input.d.ts.map +1 -1
  345. package/dist/core/models/input.js +15 -2
  346. package/dist/core/models/input.js.map +1 -1
  347. package/dist/core/models/intervention.d.ts +2 -2
  348. package/dist/core/models/intervention.d.ts.map +1 -1
  349. package/dist/core/models/intervention.js +6 -3
  350. package/dist/core/models/intervention.js.map +1 -1
  351. package/dist/core/models/own-picker.d.ts +21 -0
  352. package/dist/core/models/own-picker.d.ts.map +1 -0
  353. package/dist/core/models/own-picker.js +21 -0
  354. package/dist/core/models/own-picker.js.map +1 -0
  355. package/dist/core/models/picker.d.ts +34 -4
  356. package/dist/core/models/picker.d.ts.map +1 -1
  357. package/dist/core/models/picker.js +50 -44
  358. package/dist/core/models/picker.js.map +1 -1
  359. package/dist/core/models/row-block.d.ts +66 -0
  360. package/dist/core/models/row-block.d.ts.map +1 -0
  361. package/dist/core/models/row-block.js +87 -0
  362. package/dist/core/models/row-block.js.map +1 -0
  363. package/dist/core/models/rows.d.ts +32 -3
  364. package/dist/core/models/rows.d.ts.map +1 -1
  365. package/dist/core/models/rows.js +48 -20
  366. package/dist/core/models/rows.js.map +1 -1
  367. package/dist/core/models/tui-screen.d.ts +6 -1
  368. package/dist/core/models/tui-screen.d.ts.map +1 -1
  369. package/dist/core/models/tui-screen.js +25 -10
  370. package/dist/core/models/tui-screen.js.map +1 -1
  371. package/dist/core/models/window.d.ts +49 -0
  372. package/dist/core/models/window.d.ts.map +1 -0
  373. package/dist/core/models/window.js +105 -0
  374. package/dist/core/models/window.js.map +1 -0
  375. package/dist/core/path-trust.d.ts +11 -0
  376. package/dist/core/path-trust.d.ts.map +1 -0
  377. package/dist/core/path-trust.js +21 -0
  378. package/dist/core/path-trust.js.map +1 -0
  379. package/dist/core/screen-facts.d.ts +3 -1
  380. package/dist/core/screen-facts.d.ts.map +1 -1
  381. package/dist/core/screen-facts.js +1 -0
  382. package/dist/core/screen-facts.js.map +1 -1
  383. package/dist/core/simple/boundary-signal.d.ts +41 -6
  384. package/dist/core/simple/boundary-signal.d.ts.map +1 -1
  385. package/dist/core/simple/boundary-signal.js +43 -3
  386. package/dist/core/simple/boundary-signal.js.map +1 -1
  387. package/dist/core/simple/close.d.ts +2 -1
  388. package/dist/core/simple/close.d.ts.map +1 -1
  389. package/dist/core/simple/close.js +10 -2
  390. package/dist/core/simple/close.js.map +1 -1
  391. package/dist/core/simple/turn-boundary.d.ts +2 -0
  392. package/dist/core/simple/turn-boundary.d.ts.map +1 -1
  393. package/dist/core/simple/turn-boundary.js +1 -1
  394. package/dist/core/simple/turn-boundary.js.map +1 -1
  395. package/dist/core/simple/turn-types.d.ts +9 -3
  396. package/dist/core/simple/turn-types.d.ts.map +1 -1
  397. package/dist/core/simple/turn-types.js +10 -2
  398. package/dist/core/simple/turn-types.js.map +1 -1
  399. package/dist/core/simple/turn.d.ts +2 -0
  400. package/dist/core/simple/turn.d.ts.map +1 -1
  401. package/dist/core/simple/turn.js +11 -5
  402. package/dist/core/simple/turn.js.map +1 -1
  403. package/dist/core/terminal-options.d.ts +17 -1
  404. package/dist/core/terminal-options.d.ts.map +1 -1
  405. package/dist/core/terminal-options.js +20 -8
  406. package/dist/core/terminal-options.js.map +1 -1
  407. package/dist/core/transcript/cursor-io.d.ts +8 -0
  408. package/dist/core/transcript/cursor-io.d.ts.map +1 -1
  409. package/dist/core/transcript/cursor-io.js +11 -5
  410. package/dist/core/transcript/cursor-io.js.map +1 -1
  411. package/dist/core/transcript/cursor.d.ts +28 -4
  412. package/dist/core/transcript/cursor.d.ts.map +1 -1
  413. package/dist/core/transcript/cursor.js +53 -7
  414. package/dist/core/transcript/cursor.js.map +1 -1
  415. package/dist/core/transcript/drain.d.ts +43 -0
  416. package/dist/core/transcript/drain.d.ts.map +1 -0
  417. package/dist/core/transcript/drain.js +67 -0
  418. package/dist/core/transcript/drain.js.map +1 -0
  419. package/dist/core/trust/abortable.d.ts +12 -0
  420. package/dist/core/trust/abortable.d.ts.map +1 -0
  421. package/dist/core/trust/abortable.js +48 -0
  422. package/dist/core/trust/abortable.js.map +1 -0
  423. package/dist/core/trust/allowlist.d.ts +2 -0
  424. package/dist/core/trust/allowlist.d.ts.map +1 -1
  425. package/dist/core/trust/allowlist.js +4 -1
  426. package/dist/core/trust/allowlist.js.map +1 -1
  427. package/dist/core/trust/blocking.d.ts +11 -0
  428. package/dist/core/trust/blocking.d.ts.map +1 -1
  429. package/dist/core/trust/blocking.js +5 -4
  430. package/dist/core/trust/blocking.js.map +1 -1
  431. package/dist/core/trust/copy.d.ts.map +1 -1
  432. package/dist/core/trust/copy.js +7 -1
  433. package/dist/core/trust/copy.js.map +1 -1
  434. package/dist/core/trust/deadline.d.ts +24 -0
  435. package/dist/core/trust/deadline.d.ts.map +1 -0
  436. package/dist/core/trust/deadline.js +56 -0
  437. package/dist/core/trust/deadline.js.map +1 -0
  438. package/dist/core/trust/dialog.d.ts +11 -2
  439. package/dist/core/trust/dialog.d.ts.map +1 -1
  440. package/dist/core/trust/dialog.js +12 -3
  441. package/dist/core/trust/dialog.js.map +1 -1
  442. package/dist/core/trust/options.d.ts +5 -1
  443. package/dist/core/trust/options.d.ts.map +1 -1
  444. package/dist/core/trust/options.js +8 -2
  445. package/dist/core/trust/options.js.map +1 -1
  446. package/dist/core/trust/prompts.d.ts +3 -0
  447. package/dist/core/trust/prompts.d.ts.map +1 -1
  448. package/dist/core/trust/prompts.js +2 -1
  449. package/dist/core/trust/prompts.js.map +1 -1
  450. package/dist/core/trust/responder.d.ts +10 -5
  451. package/dist/core/trust/responder.d.ts.map +1 -1
  452. package/dist/core/trust/responder.js +37 -33
  453. package/dist/core/trust/responder.js.map +1 -1
  454. package/dist/core/trust/step.d.ts +67 -0
  455. package/dist/core/trust/step.d.ts.map +1 -0
  456. package/dist/core/trust/step.js +45 -0
  457. package/dist/core/trust/step.js.map +1 -0
  458. package/dist/core/trust/types.d.ts +21 -1
  459. package/dist/core/trust/types.d.ts.map +1 -1
  460. package/dist/core/trust/types.js +25 -1
  461. package/dist/core/trust/types.js.map +1 -1
  462. package/dist/core/trust/view.d.ts +2 -0
  463. package/dist/core/trust/view.d.ts.map +1 -1
  464. package/dist/core/trust/view.js +3 -0
  465. package/dist/core/trust/view.js.map +1 -1
  466. package/dist/core/trust/write.d.ts +31 -6
  467. package/dist/core/trust/write.d.ts.map +1 -1
  468. package/dist/core/trust/write.js +95 -80
  469. package/dist/core/trust/write.js.map +1 -1
  470. package/dist/core/turn-state.d.ts +18 -4
  471. package/dist/core/turn-state.d.ts.map +1 -1
  472. package/dist/core/turn-state.js +47 -25
  473. package/dist/core/turn-state.js.map +1 -1
  474. package/dist/core/warnings/images.d.ts +22 -0
  475. package/dist/core/warnings/images.d.ts.map +1 -0
  476. package/dist/core/warnings/images.js +3 -0
  477. package/dist/core/warnings/images.js.map +1 -0
  478. package/dist/core/warnings/index.d.ts +3 -2
  479. package/dist/core/warnings/index.d.ts.map +1 -1
  480. package/dist/core/warnings/lifecycle.d.ts +35 -4
  481. package/dist/core/warnings/lifecycle.d.ts.map +1 -1
  482. package/dist/core/warnings/lifecycle.js +1 -1
  483. package/dist/core/warnings/update.d.ts +8 -4
  484. package/dist/core/warnings/update.d.ts.map +1 -1
  485. package/dist/core/warnings/update.js +21 -6
  486. package/dist/core/warnings/update.js.map +1 -1
  487. package/dist/pty/exit-latch.d.ts +28 -0
  488. package/dist/pty/exit-latch.d.ts.map +1 -0
  489. package/dist/pty/exit-latch.js +50 -0
  490. package/dist/pty/exit-latch.js.map +1 -0
  491. package/dist/runtime/exit-status.d.ts +7 -0
  492. package/dist/runtime/exit-status.d.ts.map +1 -0
  493. package/dist/runtime/exit-status.js +13 -0
  494. package/dist/runtime/exit-status.js.map +1 -0
  495. package/dist/runtime/home-guard/index.d.ts +45 -0
  496. package/dist/runtime/home-guard/index.d.ts.map +1 -0
  497. package/dist/runtime/home-guard/index.js +69 -0
  498. package/dist/runtime/home-guard/index.js.map +1 -0
  499. package/dist/runtime/loop-restore.d.ts +9 -9
  500. package/dist/runtime/loop-restore.d.ts.map +1 -1
  501. package/dist/runtime/loop-restore.js +10 -10
  502. package/dist/runtime/loop-restore.js.map +1 -1
  503. package/dist/runtime/probe/capped-buffer.d.ts +16 -0
  504. package/dist/runtime/probe/capped-buffer.d.ts.map +1 -0
  505. package/dist/runtime/probe/capped-buffer.js +38 -0
  506. package/dist/runtime/probe/capped-buffer.js.map +1 -0
  507. package/dist/runtime/probe/capture.d.ts +55 -0
  508. package/dist/runtime/probe/capture.d.ts.map +1 -0
  509. package/dist/runtime/probe/capture.js +71 -0
  510. package/dist/runtime/probe/capture.js.map +1 -0
  511. package/dist/runtime/{probe-cleanup.d.ts → probe/cleanup.d.ts} +2 -2
  512. package/dist/runtime/probe/cleanup.d.ts.map +1 -0
  513. package/dist/runtime/{probe-cleanup.js → probe/cleanup.js} +3 -3
  514. package/dist/runtime/probe/cleanup.js.map +1 -0
  515. package/dist/runtime/probe/directory-key.d.ts +26 -0
  516. package/dist/runtime/probe/directory-key.d.ts.map +1 -0
  517. package/dist/runtime/probe/directory-key.js +46 -0
  518. package/dist/runtime/probe/directory-key.js.map +1 -0
  519. package/dist/runtime/probe/index.d.ts +25 -0
  520. package/dist/runtime/probe/index.d.ts.map +1 -0
  521. package/dist/runtime/probe/index.js +153 -0
  522. package/dist/runtime/probe/index.js.map +1 -0
  523. package/dist/runtime/probe/lookup.d.ts +12 -0
  524. package/dist/runtime/probe/lookup.d.ts.map +1 -0
  525. package/dist/runtime/probe/lookup.js +29 -0
  526. package/dist/runtime/probe/lookup.js.map +1 -0
  527. package/dist/runtime/probe/named-pipes.d.ts +35 -0
  528. package/dist/runtime/probe/named-pipes.d.ts.map +1 -0
  529. package/dist/runtime/probe/named-pipes.js +167 -0
  530. package/dist/runtime/probe/named-pipes.js.map +1 -0
  531. package/dist/runtime/probe/private-directory.d.ts +14 -0
  532. package/dist/runtime/probe/private-directory.d.ts.map +1 -0
  533. package/dist/runtime/probe/private-directory.js +50 -0
  534. package/dist/runtime/probe/private-directory.js.map +1 -0
  535. package/dist/runtime/probe/results.d.ts +14 -0
  536. package/dist/runtime/probe/results.d.ts.map +1 -0
  537. package/dist/runtime/probe/results.js +38 -0
  538. package/dist/runtime/probe/results.js.map +1 -0
  539. package/dist/runtime/probe/slots.d.ts +17 -0
  540. package/dist/runtime/probe/slots.d.ts.map +1 -0
  541. package/dist/runtime/probe/slots.js +35 -0
  542. package/dist/runtime/probe/slots.js.map +1 -0
  543. package/dist/runtime/probe/start.d.ts +64 -0
  544. package/dist/runtime/probe/start.d.ts.map +1 -0
  545. package/dist/runtime/probe/start.js +80 -0
  546. package/dist/runtime/probe/start.js.map +1 -0
  547. package/dist/runtime/probe-cache.d.ts +19 -0
  548. package/dist/runtime/probe-cache.d.ts.map +1 -0
  549. package/dist/runtime/probe-cache.js +56 -0
  550. package/dist/runtime/probe-cache.js.map +1 -0
  551. package/dist/runtime/readiness/initial-ready.d.ts +31 -5
  552. package/dist/runtime/readiness/initial-ready.d.ts.map +1 -1
  553. package/dist/runtime/readiness/initial-ready.js +64 -18
  554. package/dist/runtime/readiness/initial-ready.js.map +1 -1
  555. package/dist/runtime/seams.d.ts +14 -2
  556. package/dist/runtime/seams.d.ts.map +1 -1
  557. package/dist/runtime/seams.js +2 -2
  558. package/dist/runtime/seams.js.map +1 -1
  559. package/dist/runtime/session/base.d.ts.map +1 -1
  560. package/dist/runtime/session/base.js +9 -6
  561. package/dist/runtime/session/base.js.map +1 -1
  562. package/dist/runtime/session/commands.d.ts +6 -4
  563. package/dist/runtime/session/commands.d.ts.map +1 -1
  564. package/dist/runtime/session/commands.js +5 -2
  565. package/dist/runtime/session/commands.js.map +1 -1
  566. package/dist/runtime/session/frames.d.ts +12 -2
  567. package/dist/runtime/session/frames.d.ts.map +1 -1
  568. package/dist/runtime/session/frames.js +10 -3
  569. package/dist/runtime/session/frames.js.map +1 -1
  570. package/dist/runtime/session/image-attach.js +7 -2
  571. package/dist/runtime/session/image-attach.js.map +1 -1
  572. package/dist/runtime/session/launch-directory.d.ts +33 -0
  573. package/dist/runtime/session/launch-directory.d.ts.map +1 -0
  574. package/dist/runtime/session/launch-directory.js +84 -0
  575. package/dist/runtime/session/launch-directory.js.map +1 -0
  576. package/dist/runtime/session/lifecycle.d.ts +3 -3
  577. package/dist/runtime/session/lifecycle.d.ts.map +1 -1
  578. package/dist/runtime/session/lifecycle.js +1 -5
  579. package/dist/runtime/session/lifecycle.js.map +1 -1
  580. package/dist/runtime/session/loops.d.ts +1 -1
  581. package/dist/runtime/session/loops.d.ts.map +1 -1
  582. package/dist/runtime/session/loops.js +6 -3
  583. package/dist/runtime/session/loops.js.map +1 -1
  584. package/dist/runtime/session/picker-cleanup.d.ts +6 -21
  585. package/dist/runtime/session/picker-cleanup.d.ts.map +1 -1
  586. package/dist/runtime/session/picker-cleanup.js +19 -31
  587. package/dist/runtime/session/picker-cleanup.js.map +1 -1
  588. package/dist/runtime/session/picker-hold.d.ts +24 -0
  589. package/dist/runtime/session/picker-hold.d.ts.map +1 -0
  590. package/dist/runtime/session/picker-hold.js +42 -0
  591. package/dist/runtime/session/picker-hold.js.map +1 -0
  592. package/dist/runtime/session/picker.d.ts +15 -13
  593. package/dist/runtime/session/picker.d.ts.map +1 -1
  594. package/dist/runtime/session/picker.js +49 -27
  595. package/dist/runtime/session/picker.js.map +1 -1
  596. package/dist/runtime/session/prepare-state.d.ts +6 -0
  597. package/dist/runtime/session/prepare-state.d.ts.map +1 -0
  598. package/dist/runtime/session/prepare-state.js +14 -0
  599. package/dist/runtime/session/prepare-state.js.map +1 -0
  600. package/dist/runtime/session/questions.d.ts +2 -2
  601. package/dist/runtime/session/questions.d.ts.map +1 -1
  602. package/dist/runtime/session/questions.js.map +1 -1
  603. package/dist/runtime/session/readiness.d.ts +14 -5
  604. package/dist/runtime/session/readiness.d.ts.map +1 -1
  605. package/dist/runtime/session/readiness.js +22 -6
  606. package/dist/runtime/session/readiness.js.map +1 -1
  607. package/dist/runtime/session/reap.d.ts +11 -7
  608. package/dist/runtime/session/reap.d.ts.map +1 -1
  609. package/dist/runtime/session/reap.js +16 -10
  610. package/dist/runtime/session/reap.js.map +1 -1
  611. package/dist/runtime/session/runtime-cleanup.d.ts +18 -0
  612. package/dist/runtime/session/runtime-cleanup.d.ts.map +1 -0
  613. package/dist/runtime/session/runtime-cleanup.js +18 -0
  614. package/dist/runtime/session/runtime-cleanup.js.map +1 -0
  615. package/dist/runtime/session/session-input.d.ts +32 -0
  616. package/dist/runtime/session/session-input.d.ts.map +1 -0
  617. package/dist/runtime/session/{picker-input.js → session-input.js} +35 -7
  618. package/dist/runtime/session/session-input.js.map +1 -0
  619. package/dist/runtime/session/shutdown-binding.d.ts +12 -1
  620. package/dist/runtime/session/shutdown-binding.d.ts.map +1 -1
  621. package/dist/runtime/session/shutdown-binding.js +33 -1
  622. package/dist/runtime/session/shutdown-binding.js.map +1 -1
  623. package/dist/runtime/session/shutdown-cleanup.d.ts +17 -0
  624. package/dist/runtime/session/shutdown-cleanup.d.ts.map +1 -0
  625. package/dist/runtime/session/shutdown-cleanup.js +49 -0
  626. package/dist/runtime/session/shutdown-cleanup.js.map +1 -0
  627. package/dist/runtime/session/shutdown.d.ts +3 -2
  628. package/dist/runtime/session/shutdown.d.ts.map +1 -1
  629. package/dist/runtime/session/shutdown.js +13 -24
  630. package/dist/runtime/session/shutdown.js.map +1 -1
  631. package/dist/runtime/shell-quote.d.ts +8 -0
  632. package/dist/runtime/shell-quote.d.ts.map +1 -0
  633. package/dist/runtime/shell-quote.js +10 -0
  634. package/dist/runtime/shell-quote.js.map +1 -0
  635. package/dist/runtime/shell.d.ts +36 -12
  636. package/dist/runtime/shell.d.ts.map +1 -1
  637. package/dist/runtime/shell.js +90 -14
  638. package/dist/runtime/shell.js.map +1 -1
  639. package/dist/runtime/shutdown/coalesce.d.ts +15 -0
  640. package/dist/runtime/shutdown/coalesce.d.ts.map +1 -0
  641. package/dist/runtime/shutdown/coalesce.js +42 -0
  642. package/dist/runtime/shutdown/coalesce.js.map +1 -0
  643. package/dist/runtime/shutdown/detached-sweep/failures.d.ts +24 -0
  644. package/dist/runtime/shutdown/detached-sweep/failures.d.ts.map +1 -0
  645. package/dist/runtime/shutdown/detached-sweep/failures.js +32 -0
  646. package/dist/runtime/shutdown/detached-sweep/failures.js.map +1 -0
  647. package/dist/runtime/shutdown/detached-sweep/index.d.ts +55 -0
  648. package/dist/runtime/shutdown/detached-sweep/index.d.ts.map +1 -0
  649. package/dist/runtime/shutdown/detached-sweep/index.js +129 -0
  650. package/dist/runtime/shutdown/detached-sweep/index.js.map +1 -0
  651. package/dist/runtime/shutdown/detached-sweep/kill.d.ts +37 -0
  652. package/dist/runtime/shutdown/detached-sweep/kill.d.ts.map +1 -0
  653. package/dist/runtime/shutdown/detached-sweep/kill.js +87 -0
  654. package/dist/runtime/shutdown/detached-sweep/kill.js.map +1 -0
  655. package/dist/runtime/shutdown/detached-sweep/record.d.ts +67 -0
  656. package/dist/runtime/shutdown/detached-sweep/record.d.ts.map +1 -0
  657. package/dist/runtime/shutdown/detached-sweep/record.js +111 -0
  658. package/dist/runtime/shutdown/detached-sweep/record.js.map +1 -0
  659. package/dist/runtime/shutdown/detached-sweep/rounds.d.ts +78 -0
  660. package/dist/runtime/shutdown/detached-sweep/rounds.d.ts.map +1 -0
  661. package/dist/runtime/shutdown/detached-sweep/rounds.js +116 -0
  662. package/dist/runtime/shutdown/detached-sweep/rounds.js.map +1 -0
  663. package/dist/runtime/shutdown/detached-sweep/survivors.d.ts +40 -0
  664. package/dist/runtime/shutdown/detached-sweep/survivors.d.ts.map +1 -0
  665. package/dist/runtime/shutdown/detached-sweep/survivors.js +112 -0
  666. package/dist/runtime/shutdown/detached-sweep/survivors.js.map +1 -0
  667. package/dist/runtime/shutdown/leader-group.d.ts +29 -0
  668. package/dist/runtime/shutdown/leader-group.d.ts.map +1 -0
  669. package/dist/runtime/shutdown/leader-group.js +35 -0
  670. package/dist/runtime/shutdown/leader-group.js.map +1 -0
  671. package/dist/runtime/shutdown/native-read-admission.d.ts +13 -0
  672. package/dist/runtime/shutdown/native-read-admission.d.ts.map +1 -0
  673. package/dist/runtime/shutdown/native-read-admission.js +64 -0
  674. package/dist/runtime/shutdown/native-read-admission.js.map +1 -0
  675. package/dist/runtime/shutdown/process-table.d.ts +80 -0
  676. package/dist/runtime/shutdown/process-table.d.ts.map +1 -0
  677. package/dist/runtime/shutdown/process-table.js +147 -0
  678. package/dist/runtime/shutdown/process-table.js.map +1 -0
  679. package/dist/runtime/shutdown/process-tree-ops.d.ts +39 -0
  680. package/dist/runtime/shutdown/process-tree-ops.d.ts.map +1 -0
  681. package/dist/runtime/shutdown/process-tree-ops.js +73 -0
  682. package/dist/runtime/shutdown/process-tree-ops.js.map +1 -0
  683. package/dist/runtime/shutdown/read-timeout.d.ts +4 -0
  684. package/dist/runtime/shutdown/read-timeout.d.ts.map +1 -0
  685. package/dist/runtime/shutdown/read-timeout.js +6 -0
  686. package/dist/runtime/shutdown/read-timeout.js.map +1 -0
  687. package/dist/runtime/shutdown/reap-queue.d.ts +10 -0
  688. package/dist/runtime/shutdown/reap-queue.d.ts.map +1 -0
  689. package/dist/runtime/shutdown/reap-queue.js +29 -0
  690. package/dist/runtime/shutdown/reap-queue.js.map +1 -0
  691. package/dist/runtime/shutdown/reap-tree.d.ts +42 -32
  692. package/dist/runtime/shutdown/reap-tree.d.ts.map +1 -1
  693. package/dist/runtime/shutdown/reap-tree.js +78 -71
  694. package/dist/runtime/shutdown/reap-tree.js.map +1 -1
  695. package/dist/runtime/shutdown/shared-daemon.d.ts +59 -0
  696. package/dist/runtime/shutdown/shared-daemon.d.ts.map +1 -0
  697. package/dist/runtime/shutdown/shared-daemon.js +139 -0
  698. package/dist/runtime/shutdown/shared-daemon.js.map +1 -0
  699. package/dist/runtime/shutdown/teardown.d.ts +15 -0
  700. package/dist/runtime/shutdown/teardown.d.ts.map +1 -1
  701. package/dist/runtime/shutdown/teardown.js +19 -6
  702. package/dist/runtime/shutdown/teardown.js.map +1 -1
  703. package/dist/runtime/shutdown/terminate.d.ts +21 -1
  704. package/dist/runtime/shutdown/terminate.d.ts.map +1 -1
  705. package/dist/runtime/shutdown/terminate.js +83 -56
  706. package/dist/runtime/shutdown/terminate.js.map +1 -1
  707. package/dist/runtime/startup/buffer.d.ts +26 -7
  708. package/dist/runtime/startup/buffer.d.ts.map +1 -1
  709. package/dist/runtime/startup/buffer.js +42 -19
  710. package/dist/runtime/startup/buffer.js.map +1 -1
  711. package/dist/runtime/startup/cleanup.d.ts +37 -11
  712. package/dist/runtime/startup/cleanup.d.ts.map +1 -1
  713. package/dist/runtime/startup/cleanup.js +55 -25
  714. package/dist/runtime/startup/cleanup.js.map +1 -1
  715. package/dist/runtime/startup/index.d.ts +13 -1
  716. package/dist/runtime/startup/index.d.ts.map +1 -1
  717. package/dist/runtime/startup/index.js +38 -6
  718. package/dist/runtime/startup/index.js.map +1 -1
  719. package/dist/runtime/stream-marker.d.ts +17 -0
  720. package/dist/runtime/stream-marker.d.ts.map +1 -0
  721. package/dist/runtime/stream-marker.js +35 -0
  722. package/dist/runtime/stream-marker.js.map +1 -0
  723. package/dist/runtime/update/acquire.d.ts +16 -0
  724. package/dist/runtime/update/acquire.d.ts.map +1 -0
  725. package/dist/runtime/update/acquire.js +62 -0
  726. package/dist/runtime/update/acquire.js.map +1 -0
  727. package/dist/runtime/update/completion.d.ts +12 -0
  728. package/dist/runtime/update/completion.d.ts.map +1 -0
  729. package/dist/runtime/update/completion.js +50 -0
  730. package/dist/runtime/update/completion.js.map +1 -0
  731. package/dist/runtime/update/lock.d.ts +1 -1
  732. package/dist/runtime/update/lock.d.ts.map +1 -1
  733. package/dist/runtime/update/lock.js +21 -38
  734. package/dist/runtime/update/lock.js.map +1 -1
  735. package/dist/runtime/update/once.d.ts +16 -13
  736. package/dist/runtime/update/once.d.ts.map +1 -1
  737. package/dist/runtime/update/once.js +41 -31
  738. package/dist/runtime/update/once.js.map +1 -1
  739. package/dist/runtime/update/owner.js +1 -1
  740. package/dist/runtime/update/retained-lease.js +1 -1
  741. package/dist/runtime/update/waiting.d.ts +14 -1
  742. package/dist/runtime/update/waiting.d.ts.map +1 -1
  743. package/dist/runtime/update/waiting.js +20 -12
  744. package/dist/runtime/update/waiting.js.map +1 -1
  745. package/dist/state/canonical-path.d.ts +5 -0
  746. package/dist/state/canonical-path.d.ts.map +1 -1
  747. package/dist/state/canonical-path.js +11 -5
  748. package/dist/state/canonical-path.js.map +1 -1
  749. package/dist/state/directories.d.ts +31 -1
  750. package/dist/state/directories.d.ts.map +1 -1
  751. package/dist/state/directories.js +75 -13
  752. package/dist/state/directories.js.map +1 -1
  753. package/dist/state/files.d.ts +3 -3
  754. package/dist/state/files.d.ts.map +1 -1
  755. package/dist/state/files.js +4 -10
  756. package/dist/state/files.js.map +1 -1
  757. package/dist/state/launch-ownership.d.ts +4 -2
  758. package/dist/state/launch-ownership.d.ts.map +1 -1
  759. package/dist/state/launch-ownership.js +18 -10
  760. package/dist/state/launch-ownership.js.map +1 -1
  761. package/dist/state/launch-publication.d.ts +7 -1
  762. package/dist/state/launch-publication.d.ts.map +1 -1
  763. package/dist/state/launch-publication.js +14 -2
  764. package/dist/state/launch-publication.js.map +1 -1
  765. package/dist/state/loop-store.d.ts +15 -4
  766. package/dist/state/loop-store.d.ts.map +1 -1
  767. package/dist/state/loop-store.js +13 -7
  768. package/dist/state/loop-store.js.map +1 -1
  769. package/dist/state/preparation/created-directories.d.ts +13 -0
  770. package/dist/state/preparation/created-directories.d.ts.map +1 -0
  771. package/dist/state/preparation/created-directories.js +45 -0
  772. package/dist/state/preparation/created-directories.js.map +1 -0
  773. package/dist/state/preparation/ignore-sync.d.ts +9 -0
  774. package/dist/state/preparation/ignore-sync.d.ts.map +1 -0
  775. package/dist/state/preparation/ignore-sync.js +47 -0
  776. package/dist/state/preparation/ignore-sync.js.map +1 -0
  777. package/dist/state/preparation/index.d.ts +25 -0
  778. package/dist/state/preparation/index.d.ts.map +1 -0
  779. package/dist/state/preparation/index.js +31 -0
  780. package/dist/state/preparation/index.js.map +1 -0
  781. package/dist/state/preparation/install-ignore.d.ts +6 -0
  782. package/dist/state/preparation/install-ignore.d.ts.map +1 -0
  783. package/dist/state/preparation/install-ignore.js +87 -0
  784. package/dist/state/preparation/install-ignore.js.map +1 -0
  785. package/dist/state/preparation/rollback-parent.d.ts +3 -0
  786. package/dist/state/preparation/rollback-parent.d.ts.map +1 -0
  787. package/dist/state/preparation/rollback-parent.js +18 -0
  788. package/dist/state/preparation/rollback-parent.js.map +1 -0
  789. package/dist/state/preparation/root-trust.d.ts +3 -0
  790. package/dist/state/preparation/root-trust.d.ts.map +1 -0
  791. package/dist/state/preparation/root-trust.js +70 -0
  792. package/dist/state/preparation/root-trust.js.map +1 -0
  793. package/dist/state/private-session.d.ts +5 -2
  794. package/dist/state/private-session.d.ts.map +1 -1
  795. package/dist/state/private-session.js +21 -9
  796. package/dist/state/private-session.js.map +1 -1
  797. package/dist/state/root.d.ts +25 -0
  798. package/dist/state/root.d.ts.map +1 -0
  799. package/dist/state/root.js +84 -0
  800. package/dist/state/root.js.map +1 -0
  801. package/dist/state/runtime-paths.d.ts +5 -4
  802. package/dist/state/runtime-paths.d.ts.map +1 -1
  803. package/dist/state/runtime-paths.js +5 -4
  804. package/dist/state/runtime-paths.js.map +1 -1
  805. package/dist/state/setup-failure.d.ts +3 -0
  806. package/dist/state/setup-failure.d.ts.map +1 -0
  807. package/dist/state/setup-failure.js +13 -0
  808. package/dist/state/setup-failure.js.map +1 -0
  809. package/dist/state/socket-home.d.ts +17 -12
  810. package/dist/state/socket-home.d.ts.map +1 -1
  811. package/dist/state/socket-home.js +31 -14
  812. package/dist/state/socket-home.js.map +1 -1
  813. package/dist/state/store.d.ts +1 -3
  814. package/dist/state/store.d.ts.map +1 -1
  815. package/dist/state/store.js +3 -18
  816. package/dist/state/store.js.map +1 -1
  817. package/dist/terminal/cursor.d.ts +1 -1
  818. package/dist/terminal/cursor.d.ts.map +1 -1
  819. package/dist/terminal/cursor.js +36 -1
  820. package/dist/terminal/cursor.js.map +1 -1
  821. package/dist/terminal/headless.d.ts +1 -1
  822. package/dist/terminal/headless.d.ts.map +1 -1
  823. package/dist/terminal/headless.js +1 -3
  824. package/dist/terminal/headless.js.map +1 -1
  825. package/docs/cli-behavior.md +1218 -28
  826. package/docs/cli.md +2 -0
  827. package/docs/codex/cli.md +4 -2
  828. package/docs/embedding.md +7 -0
  829. package/docs/performance/terminal-engines.md +88 -0
  830. package/docs/performance.md +120 -0
  831. package/package.json +4 -4
  832. package/prd/04-architecture.md +32 -0
  833. package/prd/05-api/5.1-starting-claude.md +17 -1
  834. package/prd/05-api/5.2-resuming-claude.md +5 -0
  835. package/prd/05-api/5.3-claude-session-api.md +267 -40
  836. package/prd/05-api/5.4-events.md +77 -10
  837. package/prd/05-api/5.5-starting-codex.md +3 -1
  838. package/prd/05-api/5.6-resuming-codex.md +4 -1
  839. package/prd/05-api/5.7-codex-session-api.md +87 -5
  840. package/prd/05-api/5.8-session-api.md +32 -1
  841. package/prd/06-claude-hook-bridge.md +2 -0
  842. package/prd/07a-codex-hook-bridge.md +5 -0
  843. package/prd/08-state.md +63 -7
  844. package/prd/09-lifecycle.md +126 -35
  845. package/prd/10-errors.md +11 -4
  846. package/prd/12-e2e.md +81 -0
  847. package/prd/12a-cli.md +63 -9
  848. package/prd/14-conformance.md +52 -35
  849. package/prd/16-maintainer-review.md +11 -0
  850. package/dist/claude/model-composer.d.ts +0 -2
  851. package/dist/claude/model-composer.d.ts.map +0 -1
  852. package/dist/claude/model-composer.js +0 -12
  853. package/dist/claude/model-composer.js.map +0 -1
  854. package/dist/claude/transcript/drain.d.ts +0 -33
  855. package/dist/claude/transcript/drain.d.ts.map +0 -1
  856. package/dist/claude/transcript/drain.js +0 -56
  857. package/dist/claude/transcript/drain.js.map +0 -1
  858. package/dist/codex/session/cli-exit.d.ts +0 -24
  859. package/dist/codex/session/cli-exit.d.ts.map +0 -1
  860. package/dist/codex/session/cli-exit.js +0 -43
  861. package/dist/codex/session/cli-exit.js.map +0 -1
  862. package/dist/codex/transcript/drain.d.ts +0 -30
  863. package/dist/codex/transcript/drain.d.ts.map +0 -1
  864. package/dist/codex/transcript/drain.js +0 -48
  865. package/dist/codex/transcript/drain.js.map +0 -1
  866. package/dist/runtime/probe-cleanup.d.ts.map +0 -1
  867. package/dist/runtime/probe-cleanup.js.map +0 -1
  868. package/dist/runtime/probe.d.ts +0 -11
  869. package/dist/runtime/probe.d.ts.map +0 -1
  870. package/dist/runtime/probe.js +0 -113
  871. package/dist/runtime/probe.js.map +0 -1
  872. package/dist/runtime/session/foreign-picker.d.ts +0 -8
  873. package/dist/runtime/session/foreign-picker.d.ts.map +0 -1
  874. package/dist/runtime/session/foreign-picker.js +0 -18
  875. package/dist/runtime/session/foreign-picker.js.map +0 -1
  876. package/dist/runtime/session/picker-input.d.ts +0 -17
  877. package/dist/runtime/session/picker-input.d.ts.map +0 -1
  878. package/dist/runtime/session/picker-input.js.map +0 -1
@@ -20,14 +20,14 @@ Each criterion has:
20
20
  |---|---:|---|
21
21
  | C-API-01 | §5.1 | `startClaude({ cwd })` returns a `ClaudeSessionApi` with a stable non-empty `elwoodSessionId`. |
22
22
  | C-API-02 | §5.1 | `startClaude` accepts launch-time hook handlers before Claude starts. |
23
- | C-API-03 | §5.2 | `resumeClaude({ elwoodSessionId })` resumes using Elwood metadata without requiring a Claude session ID from the caller, provided project-local state is discoverable from the current process cwd or caller-supplied `cwd`/`stateDir`. |
23
+ | C-API-03 | §5.2 | `resumeClaude({ elwoodSessionId })` resumes using Elwood metadata without requiring a Claude session ID from the caller, provided project-local state is discoverable from the current process cwd or caller-supplied `cwd`/`stateDir`. It relaunches Claude in the caller-supplied `cwd`, else in the record's `cwd`. |
24
24
  | C-API-04 | §5.3 | `ClaudeSessionApi` exposes `sendPrompt`, `sendKeys`, `resize`, `stop`, `kill`, and `teardown`. |
25
25
  | C-API-05 | §5.3 | `sendPrompt` writes through the PTY input path, not through Claude print mode or SDK APIs. |
26
26
  | C-API-06 | §5.3 | `sendPrompt` supports multi-line prompt text as one submitted user prompt. |
27
27
  | C-API-07 | §5.3 | Calling `sendPrompt` while Claude is busy writes input immediately rather than waiting for turn readiness, serializes against queue-backed prompt/message/command submissions, and resolves after its submitting Enter is dispatched; terminated sessions may still fail with `session_not_running`. |
28
28
  | C-API-08 | §5.4 | Consumers can subscribe and unsubscribe from typed session events. |
29
29
  | C-API-09 | §5.5 | `startCodex({ cwd })` returns a `CodexSessionApi` with a stable non-empty `elwoodSessionId`. |
30
- | C-API-10 | §5.6 | `resumeCodex({ elwoodSessionId })` resumes using Elwood metadata without requiring a Codex session ID from the caller, provided project-local state is discoverable from the current process cwd or caller-supplied `cwd`/`stateDir`. |
30
+ | C-API-10 | §5.6 | `resumeCodex({ elwoodSessionId })` resumes using Elwood metadata without requiring a Codex session ID from the caller, provided project-local state is discoverable from the current process cwd or caller-supplied `cwd`/`stateDir`. It relaunches Codex in the record's `cwd` even when a different `cwd` is supplied, since that `cwd` only locates the state store. |
31
31
  | C-API-11 | §5.7 | `CodexSessionApi` exposes the same terminal control and lifecycle methods as `ClaudeSessionApi`. |
32
32
  | C-API-12 | §5.4 | Claude and Codex sessions emit adapter-neutral `activity` events for common lifecycle, message, tool, transcript, and hook-error observations, with normalized metadata for common timeline rendering. |
33
33
  | C-API-13 | §5.3 | Claude and Codex sessions expose `sendMessage` as the adapter-neutral queued message submission API. |
@@ -41,8 +41,8 @@ Each criterion has:
41
41
  | C-API-20 | §5.3 §5.7 §8.1 §9.4 | Claude and Codex transcript activity is flushed before terminal exit and terminal lifecycle status are emitted; reentrant shutdown joins observed natural-exit finalization. Within the same process, a validated successor resume revokes the old launch's shared-state mutation authority, including deferred and repeated teardown. |
42
42
  | C-API-21 | §5.1 §5.5 | A caller-provided `persona` is submitted as the session's first user message on the first ready transition, ahead of caller-queued messages; it is not persisted and not re-sent on resume. |
43
43
  | C-API-22 | §5.3 §5.7 | `compact()` submits the adapter's `/compact` command through the readiness queue, resolves on the adapter's `PostCompact` hook, rejects with `compact_failed` on timeout, and rejects with `session_not_running` if the session terminates first. The one recovery Enter for a swallowed submission is a queued write: it waits behind a model operation that owns the input queue, is dropped once compaction settles, and is withheld from blocking dialogs and from any visible model dialog. |
44
- | C-API-23 | §5.3 §5.7 | `listModels()` parses the adapter's rendered model picker into typed options with current/default markers, cancels with Escape, and leaves the session model unchanged. |
45
- | C-API-24 | §5.3 §5.7 | `setModel(id)` switches the session model through cursor navigation; Elwood itself never persists a new default into user-owned configuration (Claude uses the session-only key; the Codex CLI persists its own picker selection, documented as a §4.5 deviation) and unknown ids reject with `model_automation_failed` listing available ids. When Claude interposes its built-in model- or effort-cache warning, Elwood selects the affirmative row from one bottom-most contiguous live dialog across numbered/unnumbered layouts, revalidates it before confirmation, and resolves only after the idle composer returns; transcript text cannot participate and a `PreModelSwitch` hook confirmation remains human-controlled. |
44
+ | C-API-23 | §5.3 §5.7 | `listModels()` parses the adapter's rendered model picker into typed options with current/default markers, cancels with Escape, and leaves the session model unchanged. It returns every row the picker lists in picker order, including rows outside a scrolling window (Claude's `↑`/`↓` window and `… +K models`), which it reveals with gated, confirmed arrow keys only, never past either end of the list. While a walk still needs rows (a listing, or `setModel` before its target is rendered), the walk never acts on a frame with a `↓` window but no positive count, a row number or length that is not an exact integer, or a length other than the first one read; it waits such frames out, and rejects with `model_automation_failed` once one timeout bounding the whole walk expires. |
45
+ | C-API-24 | §5.3 §5.7 | `setModel(id)` switches the session model through cursor navigation; Elwood itself never persists a new default into user-owned configuration (Claude uses the session-only key; the Codex CLI persists its own picker selection, documented as a §4.5 deviation) and unknown ids reject with `model_automation_failed` listing available ids. A row outside a scrolling picker window is selectable like any listed row; the walk stops once that row is rendered. When Claude interposes its built-in model- or effort-cache warning, Elwood selects the affirmative row from one bottom-most contiguous live dialog across numbered/unnumbered layouts, revalidates it before confirmation, and resolves only after the idle composer returns; transcript text cannot participate and a `PreModelSwitch` hook confirmation remains human-controlled. |
46
46
  | C-API-35 | §5.3 §5.7 | `listModels()`/`setModel()` dispatch the picker command even while a turn is in flight (they do not wait for `ready`), so picker automation is not stalled by an in-flight turn such as an MCP-server boot spinner; `compact` and messages still wait for the active turn. Ordering is FIFO except for the documented overtaking of a readiness-waiting head by an immediate operation (a picker command, a `sendPrompt`, or already-ready running-turn guidance); same-class and post-ready ordering stays FIFO, and no two submissions interleave. |
47
47
  | C-API-36 | §5.3 | A Claude session requested below 100 columns bootstraps at 100 columns and holds only the PHYSICAL pre-ready resize while recording each pre-ready requested size in memory (multiple pre-ready resizes leave the last one as the latest requested size; terminal size is never persisted, §8.2); it restores the latest requested size before its initial ready queue drains, so narrow visible terminals do not lose their first prompt. |
48
48
  | C-API-37 | §5.3 §5.7 | Claude and Codex expose `sendGuidance(message)`: before first readiness and while blocked it queues safely like `sendMessage`; during a post-ready running turn it overtakes readiness-waiting operations and enters the TUI immediately. Guidance serializes with queue-backed prompt/message/command submissions and resolves only after the submitting Enter is dispatched; raw `sendKeys` intentionally bypasses that queue. |
@@ -51,15 +51,15 @@ Each criterion has:
51
51
  | C-API-25 | §5.3 | Promise-returning session methods called after a terminal status reject with `session_not_running` instead of throwing synchronously; expired Stop input retains the earlier `wait_timeout` admission rejection (C-HOOK-04). |
52
52
  | C-API-26 | §5.2 §5.6 | `startOrResumeClaude`/`startOrResumeCodex` resume when possible, fall back to a fresh start only on `state_not_found`, `resume_unavailable`, or `adapter_mismatch`, rethrow all other errors, and report `resumed` in the result. |
53
53
  | C-API-27 | §5.7 | `ElwoodAgentSession` is exported and both `ClaudeSessionApi` and `CodexSessionApi` (the raw live sessions the `ClaudeSession`/`CodexSession` classes wrap) are assignable to it, covering common events, io, commands, and lifecycle. |
54
- | C-API-28 | §5.3 | Initial readiness fires from the adapter's pre-input readiness hook — Codex's `SessionStart`, Claude's `InstructionsLoaded`. On a COLD start the rendered composer is never a readiness signal — the boot-time composer placeholder alone never releases the first queued message (it is painted before the input loop is live and would be swallowed). On a RESUME the CLI reattaches to an existing conversation without re-firing its pre-input hook and its input loop is live when the composer paints, so a resumed session fires initial readiness on the FIRST rendered composer marker (or on the readiness hook if it re-fires, whichever is first), reaching readiness in ~1s instead of the full deadline. A blocking dialog's option caret is byte-identical to the composer marker, so the resume-composer path does NOT fire while a blocking dialog is on screen — readiness waits for the dialog to clear, so a draining queued message can never approve it. This dialog gate applies to EVERY readiness source, not just the resume-composer path: a blocking dialog on screen — including one that first rendered while the session was still `starting`, before it could latch the `blocked` status — defers a hook OR deadline readiness mark too, so no readiness source ever drains the queue into a dialog. The deferred mark is reconciled only when a rendered frame shows a nonblocking idle composer; ongoing work and blank or partial repaints retain the hold. A bounded deadline after the first rendered frame is the ultimate fallback on BOTH adapters and BOTH start/resume, so a missing/failed readiness hook (and, on cold start, an absent composer signal) cannot starve queued persona/messages forever. Readiness fires exactly once (idempotent): whichever of hook, resume-composer signal, or deadline comes first releases the first queued message, and the others are no-ops. |
54
+ | C-API-28 | §5.3 | Initial readiness fires from the adapter's pre-input readiness hook — Codex's `SessionStart`, Claude's `InstructionsLoaded`. On a COLD start the rendered composer is never a readiness signal — the boot-time composer placeholder alone never releases the first queued message (it is painted before the input loop is live and would be swallowed). On a RESUME the CLI reattaches to an existing conversation without re-firing its pre-input hook and its input loop is live when the composer paints, so a resumed session fires initial readiness on the FIRST rendered composer marker (or on the readiness hook if it re-fires, whichever is first), reaching readiness in ~1s instead of the full deadline. A blocking dialog's option caret is byte-identical to the composer marker, so the resume-composer path does NOT fire while a blocking dialog is on screen — readiness waits for the dialog to clear, so a draining queued message can never approve it. This dialog gate applies to EVERY readiness source, not just the resume-composer path: a blocking dialog on screen — including one that first rendered while the session was still `starting`, before it could latch the `blocked` status — defers a hook OR deadline readiness mark too, so no readiness source ever drains the queue into a dialog. The deferred mark is reconciled only when a rendered frame shows a nonblocking idle composer; ongoing work and blank or partial repaints retain the hold. A bounded deadline after the first rendered frame is the ultimate fallback on BOTH adapters and BOTH start/resume, so a missing/failed readiness hook (and, on cold start, an absent composer signal) cannot starve queued persona/messages forever. Codex's deadline releases only onto a frame showing a configured session's composer (the model footer beneath it) outside any blocking dialog: one that expires on the boot composer Codex paints before its session is configured, ahead of a late trust gate, fires on the first such frame instead, so a gate painting before the backstop is answered before `ready` and the queue never drains toward it; on-screen work does not delay it, and a backstop 60 seconds after the first rendered frame releases readiness without that composer, after which a later gate can still follow `ready`. A backstop release while the latest frame shows no configured composer emits one content-free `initial_ready_backstop` warning. Readiness fires exactly once (idempotent): whichever of hook, resume-composer signal, or deadline comes first releases the first queued message, and the others are no-ops. |
55
55
  | C-TURN-01 | §5.3 | While a turn runs the session is `running`; when the turn ends by any means — completion, Escape interrupt, or otherwise — the session transitions to `ready` and emits the corresponding `status` activity, on both adapters. |
56
56
  | C-TURN-02 | §5.3 | An Escape interrupt of a running turn produces the `ready` transition from rendered TUI state alone, with no dependency on a `Stop` hook. |
57
- | C-TURN-03 | §5.3 | Turn-state detection uses documented per-adapter indicator constants over `snapshot().text`; redundant edges are idempotent, and screens showing neither indicator hold state. Watching activates only after initial readiness. On resume, rendered edges remain suppressed through transcript replay until the first quiet, non-blocking composer; explicit running evidence releases that suppression immediately and synchronizes the watcher, and the CURRENT frame is still evaluated so the real turn's rendered end is retained even when it is the first frame after release (the turn ended exactly as the evidence arrived). |
57
+ | C-TURN-03 | §5.3 | Turn-state detection uses documented per-adapter indicator constants over `snapshot().text`; redundant edges are idempotent, and screens showing neither indicator hold state. Watching activates only after initial readiness. On resume, rendered edges remain suppressed through transcript replay until the composer stays quiet and non-blocking for a sustained run of frames; a CLI resume-loading placeholder frame is never quiet, and the first quiet composer after that placeholder's load work ends suppression; explicit running evidence releases that suppression immediately and synchronizes the watcher, and the CURRENT frame is still evaluated so the real turn's rendered end is retained even when it is the first frame after release (the turn ended exactly as the evidence arrived). |
58
58
  | C-TURN-04 | §5.3 | The interrupt `ready` transition fires at any terminal width: via the working-token edge where the footer fits, and via the adapter's interrupt end banner (which Claude's footer elision below ~66 columns makes necessary) on narrow screens. Retained pre-submission frames cannot complete newer physical input; a fresh interrupt banner absent at submission still works without a working footer. |
59
- | C-TURN-05 | §5.3 | Turn-state detection also consumes the OSC window title as a width-independent working signal: both adapters render a braille-spinner glyph (U+2800–U+28FF), and Claude also renders the observed `◐ ` prefix while a turn runs, so a turn `started` edge can fire from the title alone when the on-screen footer token is elided. The title is exposed on the terminal handle and is not persisted. |
59
+ | C-TURN-05 | §5.3 | Turn-state detection also consumes the OSC window title as a width-independent working signal: both adapters render a braille-spinner glyph (U+2800–U+28FF), and Claude also renders the observed half-circle prefixes `◐ ` and `◑ `, alternating between them while a turn runs; every frame of that animation reads as working, so a turn `started` edge can fire from the title alone when the on-screen footer token is elided, and no frame of a running turn reads as idle. The title is exposed on the terminal handle and is not persisted. |
60
60
  | C-ATTN-01 | §5.3 | A tool permission dialog rendered mid-turn transitions the session to `blocked` and emits an `attention` activity whose label names the matched screen-fact rule ids, on both adapters. |
61
61
  | C-ATTN-02 | §5.3 | A session enters `blocked` from either `running` or `ready` when a blocking dialog appears, and clears directly to `running` when the current frame shows work, or to `ready` when idle; working clearance emits no intermediate readiness and leaves queued input suspended, including deferred initial readiness. A held caller prompt restores running at physical submission before a follow-up can dispatch, without repeating caller intent bookkeeping. Blocking evidence never moves a session out of a terminal status. |
62
- | C-ATTN-03 | §5.3 | Startup prompts successfully answered within the automation bound never produce `blocked`; unauthorized trust candidates block immediately. An off-allowlist gate inside the §5.4 grammar (recognized native header prefix, non-empty header, selectable options, frame-ending native footer, no conversation row above) is hold-only under every `autotrust` value: it holds readiness and queued input, never receives automated input, and blocks with attention rule id `<agent>-unknown_gate-prompt`; a dialog another blocking rule names keeps that label alone. Gates outside that grammar, conversation-quoted copies, footer-less or header-less shapes, and the native composer are not held. An authorized trust candidate that remains unsupported or uncleared after five seconds produces recoverable `blocked` and existing human attention, without requiring another frame; raw input and a later valid layout can recover it (C-TRUST-01). A blocking prompt painted while `starting` blocks once the session is live AND emits its attention activity then. While still blocked, a changed set of blocking rule ids emits updated attention without another blocking status decision; identical sets do not re-emit. |
62
+ | C-ATTN-03 | §5.3 | Startup prompts successfully answered within the automation bound never produce `blocked`; unauthorized trust candidates block immediately. An off-allowlist gate inside the §5.4 grammar (recognized native header prefix, non-empty header, selectable options, frame-ending native footer, no conversation row above) is hold-only under every `autotrust` value: it holds readiness and queued input, never receives automated input, and blocks with attention rule id `<agent>-unknown_gate-prompt`; a dialog another blocking rule names keeps that label alone. Gates outside that grammar, conversation-quoted copies, footer-less or header-less shapes, and the native composer are not held. An authorized trust candidate that remains unsupported or uncleared after five seconds produces recoverable `blocked` and existing human attention, without requiring another frame; raw input and a later valid layout can recover it (C-TRUST-01). Those five seconds are cumulative visible time: a frame without the gate pauses the deadline and the same gate's return resumes the remainder, never a fresh interval. A gate erased after its answer before those five seconds are spent, and absent until verified clearance, never yields `blocked` or attention while the CLI paints its composer; its input hold still lasts until verified clearance. A blocking prompt painted while `starting` blocks once the session is live AND emits its attention activity then. While still blocked, a changed set of blocking rule ids emits updated attention without another blocking status decision; identical sets do not re-emit. |
63
63
  | C-ATTN-04 | §5.3 | Claude's model/effort switch confirmation dialogs are blocking prompts even though their selection caret resembles the composer marker. The built-in cache warning is automatically confirmed only as part of `setModel`; hook-requested confirmations remain blocked for a human decision. |
64
64
  | C-ATTN-05 | §5.3 | Native Claude questionnaires remain blocking when question copy contains composer-like carets, header-like rows, fences, or navigation-footer text. A completed questionnaire quoted above a later native composer does not supply questionnaire-blocking evidence. |
65
65
  | C-API-33 | §5.3 | `statusDecisions()` returns a live-only log of at most the 50 most recent status decisions, oldest first, each recording the evidence, the from/to statuses (`to` undefined when ignored), and a human-readable reason; it is never persisted and carries no prompt, terminal, or hook payload content. |
@@ -68,22 +68,25 @@ Each criterion has:
68
68
  | C-API-32 | §5.2 §5.6 | Resume defaults launch-policy options from the record's persisted posture; explicit resume options override field by field, and the effective posture is re-persisted. |
69
69
  | C-API-30 | §5.4 | `tool_call` activity carries the tool's input as a serialized `toolInput` and `tool_result` activity carries the tool's output as a serialized `toolOutput`, sourced from the committed transcript for Claude (`tool_use.input`/`tool_result.content`, per C-CLAUDE-15) and from the transcript for Codex (`arguments`/`output`); absent sources leave the field absent. |
70
70
  | C-API-31 | §5.3 | Submitting Enter is a separate settled PTY write. Ordinary recovery permits at most two re-Enters over four one-second observations; initial/replay ergonomic recovery instead wakes on completed renders and retains cancellable ownership, with two paced retries and no ambiguous-frame polling budget. Retry requires live idle staged input; empty retirement requires positive empty input. Both need completed native output after the latest Enter, excluding cached/resize/scroll changes. Working/unknown frames consume no Enter budget. Native user-message activity, including during first Enter, revokes retry; optimistic status does not. |
71
- | C-API-56 | §5.3 | Queued paste, command text, Enter, recovery and non-trust startup writes observe all received PTY output (including arrivals during observation), recheck holds, then write before further output. Ordinary observation timeout (1s) or permanent render failure withholds/retries cancellably. Cancelled drafts retain safe cleanup before successor input/images: clear keys need fresh positive empty input within 1s; failure rejects the successor with `wait_timeout`; raw `sendKeys`/`terminal.sendInput`/`xterm.input` revokes authority and rejects awaited initial/replay ergonomic input with a submission-specific `wait_timeout`; close drops deferred cleanup. Initial/replay ergonomic input retains its slot/draft until fresh completed post-latest-Enter output proves empty input; geometry, missing draft text, activity and write fulfillment cannot acknowledge it. It shares staged/freshness guards, observes the final bounded retry before acceptance or `wait_timeout`, and retains pending physical writes through cancellation/close. Compact recovery/cleanup uses the same guard; successor dispatch cancels compact recovery. `sendKeys` never waits. Codex update skip/retries and Claude browser-tools decline stay silent/retryable when held; Claude disposal permanently cancels write/retry/success/warning (C-CLAUDE-22). Trust responders are exempt. |
71
+ | C-API-56 | §5.3 | Queued paste, command text, Enter, recovery and non-trust startup writes observe all received PTY output (including arrivals during observation), recheck holds, then write before further output. Ordinary observation timeout (1s) or permanent render failure withholds/retries cancellably. Cancelled drafts retain safe cleanup before successor input/images: clear keys need fresh positive empty input within 1s; failure rejects the successor with `wait_timeout`; raw `sendKeys`/`terminal.sendInput`/`xterm.input` revokes authority and rejects awaited initial/replay ergonomic input with a submission-specific `wait_timeout`; close drops deferred cleanup. Initial/replay ergonomic input retains its slot/draft until fresh completed post-latest-Enter output proves empty input; geometry, missing draft text, activity and write fulfillment cannot acknowledge it. It shares staged/freshness guards, observes the final bounded retry before acceptance or `wait_timeout`, and retains pending physical writes through cancellation/close. Compact recovery/cleanup uses the same guard; successor dispatch cancels compact recovery. `sendKeys` never waits. Codex update skip/retries and Claude browser-tools decline stay silent/retryable when held; Claude disposal permanently cancels write/retry/success/warning (C-CLAUDE-22). Model-picker operation keys (navigation, selection, follow-up answers, listing's Escape) likewise observe all received output and recheck the operation's own dialog before writing; one that cannot be observed is withheld and the operation rejects with `model_automation_failed` (a cancelled operation rejects with its cancellation's own error). Trust responders are exempt. |
72
72
  | C-API-40 | §5.3 | `sendPrompt`/`sendMessage`/`sendGuidance` text is sanitized before it is framed as a bracketed paste: the `ESC [ 200~`/`ESC [ 201~` sentinels and all other C0/C1 control characters except tab, newline, and carriage return are removed, so caller/model text cannot escape paste mode and inject live keystrokes (e.g. an Enter that confirms a permission dialog). `sendKeys` is the raw escape hatch and is never sanitized. |
73
73
  | C-API-41 | §5.3 | `listClaudeModels(options)`/`listCodexModels(options)` return the available models WITHOUT a caller-held session: each starts a throwaway session with `autotrust: true` (a documented §5.1 exception — the probe only opens/cancels the picker and never runs a turn), waits for first readiness, calls `listModels`, and ALWAYS removes ALL probe state afterward — on success, on a readiness/picker failure, AND on an adapter start failure that has already allocated a state directory or runtime files — so no probe session, process tree, socket, or state directory is leaked. A readiness/picker error is the error surfaced to the caller; teardown still runs, and when teardown ITSELF fails (which can leave a probe process tree alive) a bounded diagnostic naming the teardown failure — an allowlisted errno or the error's name, never a raw message — is attached to the surfaced error's `cause`, so a leaked probe is never silently invisible. The rows are exactly `listModels`' output and the probe leaves the user's saved MODEL default untouched (the picker is opened and cancelled, never applied), just as `listModels` does on a live session. |
74
74
  | C-API-42 | §5.3 §5.7 | When recording a session's one-shot initial-ready transition throws — on EITHER adapter — Elwood still releases the control queue directly FIRST so queued persona/caller input is never starved, then surfaces a typed, content-free `initial_ready_fallback` warning (`agent: "claude" \| "codex"`) whose delivery is itself isolated so a rogue warning sink cannot re-starve the queue. Codex's returned status/activity Promise rejections from standalone readiness report `initial_ready_observer_failed` without delaying that transition; hook-triggered readiness shares the enclosing C-HOOK-22 warning boundary. |
75
75
  | C-API-43 | §5.3 | `ClaudeSession.login(options)` drives the interactive `/login` re-authentication flow to recover a session whose login lapsed (per C-CLAUDE-18) without a caller-held restart. It submits `/login` as a picker command, selects the requested login `method` (default `claudeai`) from the "Select login method" list if that list renders, and scrapes the authorization URL off screen and reports it via the optional `onAuthUrl` callback. Because the default account flow completes with a human-entered authorization code, Elwood awaits the CLI's "paste code" prompt and, when it renders, calls the required `provideCode` callback and submits the returned code as raw input; a flow that self-completes without prompting for a code never calls `provideCode`. `login` resolves once the CLI reports login success AND the session is usable again, and rejects with `login_failed` on an explicit failure/invalid-code banner or with `login_timeout` after `timeoutMs` (default 300000 ms); like every other command method it rejects with `session_not_running` when the session is terminal. It serializes with other queued commands and never sends raw keystrokes into a blocking dialog. |
76
- | C-API-44 | §5.3 | `sendPrompt`/`sendMessage`/`sendGuidance` accept an optional `images` list (file paths or `{data,format}` bytes) attached to the submission. Images have no position within the text, but the list is ORDERED (array order = attachment/chip order). Elwood attaches every image through the CLI's own native ingestion path, as part of the SAME serialized queue operation that submits the text and BEFORE that text's submitting Enter, so one call yields one agent turn carrying text and all images. Inputs are validated and bounded before any temp file is written or any input reaches the composer: at most 16 images, ≤25 MiB per image and ≤50 MiB total; an unsupported/empty `format`, a `path` that is not a readable regular file (its readability checked, not merely its existence), or any count/size past the limits rejects with `invalid_image`. A non-array `images`, an entry that is not exactly one of `{path}`/`{data,format}`, a non-string `path`, or non-`Uint8Array` `data` also rejects with `invalid_image` (never a raw type error). Every input is defensively SNAPSHOTTED — byte buffers cloned, paths absolutized — SYNCHRONOUSLY at the public send boundary, BEFORE the op is enqueued, so a caller that mutates its buffer (or changes `cwd`) after the call cannot change what is attached; the shape, format, count, and byte-size limits are enforced in that same synchronous pass, so a malformed or over-limit input rejects with `invalid_image` before it ever occupies a queue slot. The remaining filesystem checks that need I/O — a path's readability, regular-file-ness, and size against the per-image and shared aggregate ceilings — run inside the queued op at dispatch (so a path made unreadable after the call still rejects, and no temp file is written for a still-queued submission). The synchronous snapshot completes before the queue insertion, so an image submission is still enqueued in call order and a later plain submission cannot overtake it. Beyond the per-submission 50 MiB ceiling, a PER-SESSION aggregate ceiling (200 MiB) bounds the cloned image bytes held across ALL not-yet-attached queued submissions at once: a submission whose clone would push the session past that ceiling rejects with `invalid_image`, and each submission's reserved bytes are released when it settles (attach done, reject, or the session closing), so a slow paste/confirmation cannot let a caller retain unbounded queued-clone memory. The ceiling is ONE budget per session, shared by the §5.8 facade and the raw session it exposes through `session`, so a caller mixing both surfaces is still bounded by 200 MiB in total. Ergonomic `send`/`stream` facade turns retain their reservation through the real turn boundary to cover acceptance replay (§5.8); raw facade submissions release it when submission settles. Because a send on a TERMINAL session is guarded first, a malformed/over-limit `images` on a stopped/killed/torn-down session rejects with `session_not_running` (the terminal contract), not `invalid_image`. The lifecycle turn transition is deferred until AFTER a successful attach, so an attach rejection leaves readiness untouched and never wedges the queue; a raw materialization failure (e.g. disk-full) is surfaced as the typed `image_attach_failed`, not a raw platform error. Each image's attach is CONFIRMED by its `[Image #N]` chip on the composer prompt-glyph line (so unrelated screen text cannot spoof it); an unconfirmed image (chip not seen within 10 s) or a clipboard read/write failure rejects with `image_attach_failed`, submits no text, and best-effort clears the composer draft so staged images do not leak into a later turn (a session that terminates mid-attach rejects with `session_not_running`). Bytes are written to a short-lived temp file Elwood attempts to remove after the submission is attached (best-effort; a persistently failing removal falls to OS temp reaping). |
76
+ | C-API-44 | §5.3 | `sendPrompt`/`sendMessage`/`sendGuidance` accept an optional `images` list (file paths or `{data,format}` bytes) attached to the submission. Images have no position within the text, but the list is ORDERED (array order = attachment/chip order). Elwood attaches every image through the CLI's own native ingestion path, as part of the SAME serialized queue operation that submits the text and BEFORE that text's submitting Enter, so one call yields one agent turn carrying text and all images. Inputs are validated and bounded before any temp file is written or any input reaches the composer: at most 16 images, ≤25 MiB per image and ≤50 MiB total; an unsupported/empty `format`, a `path` that is not a readable regular file (its readability checked, not merely its existence), or any count/size past the limits rejects with `invalid_image`. A non-array `images`, an entry that is not exactly one of `{path}`/`{data,format}`, a non-string `path`, or non-`Uint8Array` `data` also rejects with `invalid_image` (never a raw type error). Every input is defensively SNAPSHOTTED — byte buffers cloned, paths absolutized — SYNCHRONOUSLY at the public send boundary, BEFORE the op is enqueued, so a caller that mutates its buffer (or changes `cwd`) after the call cannot change what is attached; the shape, format, count, and byte-size limits are enforced in that same synchronous pass, so a malformed or over-limit input rejects with `invalid_image` before it ever occupies a queue slot. The remaining filesystem checks that need I/O — a path's readability, regular-file-ness, and size against the per-image and shared aggregate ceilings — run inside the queued op at dispatch (so a path made unreadable after the call still rejects, and no temp file is written for a still-queued submission). The synchronous snapshot completes before the queue insertion, so an image submission is still enqueued in call order and a later plain submission cannot overtake it. Beyond the per-submission 50 MiB ceiling, a PER-SESSION aggregate ceiling (200 MiB) bounds the cloned image bytes held across ALL not-yet-attached queued submissions at once: a submission whose clone would push the session past that ceiling rejects with `invalid_image`, and each submission's reserved bytes are released when it settles (attach done, reject, or the session closing), so a slow paste/confirmation cannot let a caller retain unbounded queued-clone memory. The ceiling is ONE budget per session, shared by the §5.8 facade and the raw session it exposes through `session`, so a caller mixing both surfaces is still bounded by 200 MiB in total. Ergonomic `send`/`stream` facade turns retain their reservation through the real turn boundary to cover acceptance replay (§5.8); raw facade submissions release it when submission settles. Because a send on a TERMINAL session is guarded first, a malformed/over-limit `images` on a stopped/killed/torn-down session rejects with `session_not_running` (the terminal contract), not `invalid_image`. The lifecycle turn transition is deferred until AFTER a successful attach, so an attach rejection leaves readiness untouched and never wedges the queue; a raw materialization failure (e.g. disk-full) is surfaced as the typed `image_attach_failed`, not a raw platform error. Each image's attach is CONFIRMED by its `[Image #N]` chip on the composer prompt-glyph line (so unrelated screen text cannot spoof it); an unconfirmed image (chip not seen within 10 s) or a clipboard read/write failure rejects with `image_attach_failed`, submits no text, and best-effort clears the composer draft so staged images do not leak into a later turn (a session that terminates mid-attach rejects with `session_not_running`). Bytes are written to a short-lived temp file Elwood attempts to remove after the submission is attached (best-effort; a persistently failing removal falls to OS temp reaping); an attach aborted by its session closing — while its temp files are still being written or once they are — removes them at once, so `stop`, `kill`, or `teardown` resolves with them gone unless that removal fails, in which case the attach retries it once it unwinds. An attach already aborted when it would materialize writes no temp file. |
77
77
  | C-API-45 | §5.3 | Claude attaches an image by bracketed-pasting its ABSOLUTE path into the composer (the delivery a terminal produces on drag-and-drop); Claude reads and encodes the file itself and shows an `[Image #N]` chip. This is pure PTY text and works on every platform. |
78
- | C-API-46 | §5.3 | Codex attaches an image from the OS clipboard: Elwood snapshots the user's clipboard ONCE (rejecting with `image_attach_failed` if the snapshot fails, before mutating anything), then for each image in order writes it onto the macOS `NSPasteboard` as a native image (`public.tiff`), sends Ctrl+V, and waits for the `[Image #N]` chip; after all images it restores the single snapshotted clipboard (best-effort, text contents). The whole snapshot/set/paste/confirm/restore sequence holds a process-wide clipboard lock so concurrent Codex sessions cannot cross-attach. A pasted path is NOT an image on Codex. Codex image attachment is macOS-only: `images` on a non-macOS Codex session rejects with `unsupported_platform` and submits nothing. |
78
+ | C-API-46 | §5.3 | Codex attaches an image from the OS clipboard: Elwood snapshots the user's clipboard ONCE (rejecting with `image_attach_failed` if the snapshot fails, before mutating anything), then for each image in order writes it onto the macOS `NSPasteboard` as a native image (`public.tiff`), sends Ctrl+V, and waits for the `[Image #N]` chip; after all images it restores the single snapshotted clipboard (best-effort, text contents). The whole snapshot/set/paste/confirm/restore sequence holds a process-wide clipboard lock so concurrent Codex sessions cannot cross-attach. A pasted path is NOT an image on Codex. Codex image attachment is macOS-only: `images` on a non-macOS Codex session rejects with `unsupported_platform` and submits nothing. Once the session's CLI has exited, by `stop`, `kill`, `teardown`, or on its own, Elwood removes (best-effort) each `codex-clipboard-*.png` copy Codex saved in its temp directory that the session's transcript names as a submitted `local_image`, and no other file. A copy already gone is not a failure; when any other removal fails, the rest still run and the session emits one `image_copy_removal_failed` warning with the first failure's errno token (else `UnknownError`), and no path, before `terminal:exit`. |
79
79
  | C-API-47 | §5.8 | The ergonomic `ClaudeSession`/`CodexSession` construct synchronously with the same options as `startClaude`/`startCodex` (with `cwd` defaulting to `process.cwd()`), do NOT start the underlying session at construction, and start it lazily on the first `send`/`stream` or on an explicit `start()`. `start()` is idempotent and concurrent-safe (a second `start()`, or a `send`/`stream` during startup, awaits the same in-flight start), and a startup failure rejects the triggering call with the same typed `ElwoodError` the factory would throw. The started underlying session is exposed via a read-only `session` accessor (undefined until started). |
80
80
  | C-API-48 | §5.8 | `stream` yields one turn's ordered text/thinking/tool-call/tool-result events with the §5.8 fields and `toolUseId` correlation. It subscribes before submission and serializes through native completion/drain, beyond consumer settlement. Codex binds only authenticated post-owned-Enter UserPromptSubmit with adapter-normalized payload correlation; pre-acceptance content and prior Stop cannot bind/finish. Tagged content and confirmed Stop match that generation; untagged content relies on serialization. Hookless completion requires its classified native end, fresh post-Enter output and no block; interrupt return is insufficient. Selected loops reserve before staging; persona/loop boundaries drain before caller collection. Claude is untagged. Passive idle completion requires positive UPS/content/Stop acceptance; unaccepted idle waits the quiet window, replays at most twice, then fails `wait_timeout`. Initial/replay input awaits fresh empty evidence; consumer settlement cancels recovery, while slot/images survive until native boundary/drain and physical cleanup. Blocked cleanup precedes successor writes; pre-replay ready cannot release a later running replay after consumer failure. Accepted ready completes when collected text contains its Stop oracle (never displayed), or after bounded quiet without an oracle; terminal status ends immediately. Whole-turn timeout defaults off; optional `timeoutMs` arms once at first physical Enter, before empty confirmation and never while queued. `catchUpMs` (default 10000ms) starts at accepted ready and fails `wait_timeout` if promised text, including nonempty text, never arrives. |
81
81
  | C-API-49 | §5.8 | `send(prompt, options?)` resolves with the turn's assistant text: the `text` of every `type:"text"` stream event, in order, joined by `\n\n` between distinct assistant messages, and NOTHING else (no thinking or tool text). A turn with no assistant text resolves to the empty string. `send` and `stream` share one turn boundary, so a `send` resolves exactly when the equivalent `stream` iterator ends. |
82
82
  | C-API-50 | §5.8 | Ergonomic turns are SERIALIZED: overlapping `send`/`stream` calls queue and run one at a time in call order, so one turn's yielded/collected activity never interleaves with another's. |
83
- | C-API-51 | §5.8 | `close()` stops the underlying session (falling back to `kill` on a stop failure) and is a no-op when the session never started, so it is safe to call in a `finally`. When a lazy start is IN FLIGHT, `close()` awaits that same start and stops the resulting session (never orphaning a session whose launch resolves after `close()` returned); a launch that REJECTS leaves nothing to close. When BOTH stop and kill fail, `close()` throws `termination_failed` carrying BOTH the stop `cause` and the kill `killCause` (neither diagnostic is lost). |
83
+ | C-API-51 | §5.8 | `close()` stops the underlying session (falling back to `kill` on a stop failure) and is a no-op when the session never started, so it is safe to call in a `finally`. When a lazy start is IN FLIGHT, `close()` awaits that same start and stops the resulting session (never orphaning a session whose launch resolves after `close()` returned); a launch that REJECTS leaves nothing to close. When BOTH stop and kill fail, `close()` throws `termination_failed` carrying BOTH the stop `cause` and the kill `killCause` (neither diagnostic is lost), plus each one's runtime-cleanup failure message, if any, as `cleanupError` and `killCleanupError`. |
84
84
  | C-API-52 | §5.8 | `ClaudeSession`/`CodexSession` expose the FULL control surface in addition to `send`/`stream`, in THREE categories. (1) OPERATIONAL methods — `sendMessage`, `sendPrompt`, `sendGuidance`, `sendKeys`, `resize`, `interrupt`, `compact`, `listModels`, `setModel`, `waitForStatus`, `waitForActivity`, and Claude's `login` — lazy-start the underlying session on first use and delegate to it. (2) `on`/`off` BUFFER before start (attached on start, so subscribing never forces a start). (3) SHUTDOWN methods — `stop`/`kill`/`teardown` — only delegate when a live session already exists and are no-ops before start (so `close()` is safe in a `finally`). `status` reads `starting` until the session exists. Only `send`/`stream` serialize; operational control methods go through immediately (not queued behind a running turn). `startClaude`/`startCodex` are deprecated in favor of the class but remain functional. |
85
85
  | C-API-53 | §5.8 | An ergonomic turn's buffered state is BOUNDED even with no whole-turn timeout: the oracle matches a rolling window of recent assistant text (bounded even when the `Stop` hook's expected text is itself large, and large enough BEFORE the oracle is installed to retain text that arrives ahead of a lagging `Stop` hook), and UNCONSUMED events are capped by BOTH count and total UTF-8 bytes. A turn whose pending backlog exceeds either cap fails with `wait_timeout` rather than growing without limit; the byte cap is required because a single event may carry an arbitrarily large agent-controlled payload that the count cap alone would not bound. |
86
86
  | C-API-54 | §5.1 §5.2 §5.5 §5.6 | `highTrust: true` on `startClaude`/`resumeClaude`/`ClaudeSession` expands to `permissionMode: "bypassPermissions"`, and on `startCodex`/`resumeCodex`/`CodexSession` to `sandbox: "danger-full-access"` with `approvalPolicy: "never"`, before the posture is persisted and the process spawned (the record and launch command carry the expanded values). Combining it with an explicit `permissionMode` (Claude) or `sandbox`/`approvalPolicy` (Codex) rejects before spawn with `claude_high_trust_conflict` / `codex_high_trust_conflict`; the lazy session classes reject at construction. |
87
+ | C-API-59 | §5.8 §6.1 §10 | A Claude ergonomic `send`/`stream` turn that observes a `StopFailure` hook rejecting its own submission fails with `turn_failed`: `send` rejects and `stream` throws after its already-collected events. A `StopFailure` before the turn's own `UserPromptSubmit`, or naming another `prompt_id`, is an earlier turn's late hook and does not fail the turn. The message is the §6.1 bounded diagnostic (never assistant text) and `details.agentError` carries the hook's nonblank `error`. The serialized slot is held until the real `ready` or terminal boundary and drain, and the rejected prompt is never replayed. A `Stop` with an empty or null `last_assistant_message` still resolves successfully. |
88
+ | C-API-60 | §5.8 §7A §10 | A Codex ergonomic `send`/`stream` turn that observes a committed transcript `event_msg` `task_complete` whose `error` is an object and whose `turn_id` is the one its own `UserPromptSubmit` reported fails with `turn_failed`, although Codex fires no `Stop`. A failed `task_complete` naming another turn, and one whose `error` is absent or `null`, leave the turn running. The message is the bounded `error.message`, else `Codex rejected the turn: <kind>`, else `Codex rejected the turn.`; and `details.agentError` is the `codex_error_info` string or the single key of its object form. |
89
+ | C-API-61 | §5.1 §5.2 §5.5 §5.6 §10 | `startClaude`, `startCodex`, `resumeClaude`, and `resumeCodex` reject with the adapter's `*_start_failed` when the launch directory is not an existing directory. The launch directory is: on start, `cwd`; on `resumeClaude`, the given `cwd` resolved when resume is called, else the record's `cwd`; on `resumeCodex`, always the record's `cwd`, since a given `cwd` only locates the state store. `details.cwd` is that launch directory and `details.errno` the failed check's code. A relative `cwd` that can't be resolved, because the process working directory was deleted, rejects the same way before any probe, with `details.cwd` as given. The directory is checked before any probe, and again after preflight, before any state write or spawn, and a start or resume that fails after it while the directory is gone, no longer a directory, can't be checked, or is no longer the directory that check found (deleted and made again, with no `details.errno`) rejects the same way, keeping the failed launch's `cause` and any `cleanupErrorCode` and `cleanupProcessGroupId`. A rejection leaves existing session state unchanged. The directory is never created (C-STATE-14). A resumed Claude session launches in the directory that was checked, even if the process working directory changes meanwhile. |
87
90
 
88
91
  #### C-LOOP: Recurring Session Loops (§5.9, §8, §9, §10)
89
92
 
@@ -101,7 +104,7 @@ Each criterion has:
101
104
  | C-LOOP-10 | §5.9 | Live scheduling/submission failure emits redacted failure evidence and rearms fresh unless the session is non-live, when the persisted definition waits for resume. |
102
105
  | C-LOOP-11 | §8.2 | The loop sidecar stores stable identity, cadence, exact prompt, jitter, creation, and expiry but no live timer or transient scheduling state. |
103
106
  | C-LOOP-12 | §5.9 §9.3 | Resume discards missed runs and starts fresh clocks for surviving definitions from restored readiness. |
104
- | C-LOOP-13 | §5.9 | Loops expire seven wall-clock days after original creation; expiry beats pending submission, emits live only, and is pruned silently during resume or stopped/exited management. |
107
+ | C-LOOP-13 | §5.9 | Loops expire seven wall-clock days after original creation; expiry beats pending submission, emits live only, and is pruned silently during resume or stopped/exited management. A resume's prune, at launch or at activation, is part of its startup publication: a resume that then fails restores the sidecar it found unless the earlier launch has written it since. |
105
108
  | C-LOOP-14 | §5.9 §8.4 | Stop, unexpected exit, and process failure preserve loops; kill clears them permanently; teardown removes them with session state. |
106
109
  | C-LOOP-15 | §5.9 | `listLoops` orders immutable snapshots by creation then ID and exposes exact message, cadence, interval when fixed, jitter, times, state, and only a scheduled next-due time. |
107
110
  | C-LOOP-16 | §5.4 §5.9 | `loop` events match the specified `created`/`fired`/`cancelled`/`expired`/`failed` union and never contain a stored message or serialized request. |
@@ -129,10 +132,12 @@ Each criterion has:
129
132
  | ID | PRD | Criterion |
130
133
  |---|---|---|
131
134
  | C-PERF-01 | §9.2 | Version/capability probes run asynchronously and never block the host process's event loop, so a roster of concurrent spawns does not serialize on a single thread. |
132
- | C-PERF-02 | §9.2 | Each adapter's `--version` is read at most once per parent process; concurrent first reads share a single subprocess, later reads reuse the cached result, and the cache is invalidated after every coordinated `autoupdate` attempt, including a failed or peer-owned attempt. |
133
- | C-PERF-03 | §9.2 | Any non-PTY probe (`--version`, `--help`, `update`) that exceeds the default runtime timeout (15s) or per-stream byte cap (1,000,000 bytes) is killed. A killed/failed `--version`/`--help` probe surfaces through `*_start_failed` with `cause`/`errno` (`ETIMEDOUT`/`E2BIG`) preserved in `details`; a killed/failed `update` probe is best-effort and instead surfaces the live `agent_update_failed` warning (same bounded diagnostics) without blocking startup. Abort cleanup retries process-group termination, falls back to the direct child after repeated group-signal failure, and awaits exit within an additional one-second bound. A group id is signaled only while its leader (the direct child) is unreaped; once the leader is reaped the possibly-reissued number is observed, never signaled. Unresolved aborted groups retain an asynchronous in-process reaper without prolonging host shutdown or retaining captured output. Cleanup confirmed within the bound reports no cleanup error; unconfirmed cleanup adds a content-free `cleanupErrorCode` and a diagnostic-only `cleanupProcessGroupId` to the probe's typed error details. Probes that exit or fail to spawn on their own are not signaled. |
134
- | C-PERF-04 | §9.2 | With `autoupdate`, concurrent callers in one parent process share one update, and separate Elwood parent processes for the same macOS user and adapter serialize through an atomic lease in a stable account cache independent of `TMPDIR`. A contender waits asynchronously for the active updater, skips its duplicate attempt, invalidates its local caches, and validates the installed version; none races a second global update. The lease records owner identity and a unique generation: a live owner is never evicted solely because the stale bound elapsed, dead-owner recovery is serialized, and cleanup cannot remove a successor generation. The lease is published atomically by renaming a staged directory that already holds the complete owner record, so a claimant killed mid-claim cannot leave an ownerless or partially written lease that suppresses later updates; the lease is retired atomically on release — renamed away before deletion — so a contender polling during release sees a whole lease or none, never an ownerless one it would recover as stale and follow with a duplicate update. A release that cannot rename removes the lease in place instead of leaving a live owner's lease standing. Each lease holder removes a bounded number of that adapter's leftovers of both kinds, claim staging and retired lease directories, under one shared budget. Contenders stop waiting after 60 seconds with an `update_active` warning and never evict a still-live lease. An aborted update probe's process group that cannot be confirmed gone is observed for one further second without being signaled, and if still live is recorded as the lease's only holder: contenders skip immediately with `update_active`, and recovery removes the lease only once every recorded group has exited, whatever its age. A group record that cannot be written is retried and the lease released once the groups exit, so it never holds exclusion for the owner's lifetime. |
135
+ | C-PERF-02 | §9.2 | Version and Codex capability probes run in the validated launch workspace and share cached results only for matching adapter, directory path and observed device/inode. Each cache retains at most 32 successful settled LRU entries; in-flight reads for a key share one run. Version retention requires both output streams at most 65,536 UTF-16 code units; larger output is returned unchanged without retention, and capability caches retain only booleans. Failed reads and mismatched before/after directory identities are not retained. These bounded checks do not identify executable or PATH changes within a directory and can miss replacements restored between checks. Every coordinated update attempt, including failed and peer-owned attempts, clears the relevant version and capability caches; verify/update probes use the owning caller's captured workspace while coordination remains once per adapter. |
136
+ | C-PERF-03 | §9.2 | Any non-PTY probe (`--version`, `--help`, `update`) whose command exceeds the default runtime timeout (15s, measured from the probe shell's start line; C-PERF-05) or per-stream byte cap (1,000,000 bytes) is killed. A killed/failed `--version`/`--help` probe surfaces through `*_start_failed` with `cause`/`errno` (`ETIMEDOUT`/`E2BIG`) preserved in `details`; a killed/failed `update` probe is best-effort and instead surfaces the live `agent_update_failed` warning (same bounded diagnostics) without blocking startup. Abort cleanup retries process-group termination, falls back to the direct child after repeated group-signal failure, and awaits exit within an additional one-second bound. A group id is signaled only while its leader (the direct child) is unreaped; once the leader is reaped the possibly-reissued number is observed, never signaled. Unresolved aborted groups retain an asynchronous in-process reaper without prolonging host shutdown or retaining captured output. Cleanup confirmed within the bound reports no cleanup error; unconfirmed cleanup adds a content-free `cleanupErrorCode` and a diagnostic-only `cleanupProcessGroupId` to the probe's typed error details. Probes that exit or fail to spawn on their own are not signaled. Once its command has started, a probe whose process dies of a signal Elwood did not send reports status 128+n, as a shell would (a login shell that dies before its start line is a login-shell failure, C-PERF-05). A failed probe's reported status reaches `*_start_failed` details and the `agent_update_failed` warning as `exitStatus`. |
137
+ | C-PERF-04 | §9.2 | With `autoupdate`, concurrent callers in one parent process share one update, and separate Elwood parent processes for the same macOS user and adapter serialize through an atomic lease in a stable account cache independent of `TMPDIR`. A contender waits asynchronously for the active updater, skips its duplicate attempt, invalidates its local caches, and validates the installed version; none races a second global update. The lease records owner identity and a unique generation: a live owner is never evicted solely because the stale bound elapsed, dead-owner recovery is serialized, and cleanup cannot remove a successor generation. The lease is published atomically by renaming a staged directory that already holds the complete owner record, so a claimant killed mid-claim cannot leave an ownerless or partially written lease that suppresses later updates; the lease is retired atomically on release — renamed away before deletion — so a contender polling during release sees a whole lease or none, never an ownerless one it would recover as stale and follow with a duplicate update. A release that cannot rename removes the lease in place instead of leaving a live owner's lease standing. Each lease holder removes a bounded number of that adapter's leftovers of both kinds, claim staging and retired lease directories, under one shared budget. Contenders stop waiting after 165 seconds, beyond the owner's version and update probes at their full launch and command bounds, with an `update_active` warning and never evict a still-live lease. An aborted update probe's process group that cannot be confirmed gone is observed for one further second without being signaled, and if still live is recorded as the lease's only holder: contenders skip immediately with `update_active`, and recovery removes the lease only once every recorded group has exited, whatever its age. A group record that cannot be written is retried and the lease released once the groups exit, so it never holds exclusion for the owner's lifetime. |
138
+ | C-PERF-05 | §4.2 §9.2 §12A.9 | A non-PTY probe's interactive login shell (including `elwood interactive`'s PATH lookup) has a 60-second launch bound to load the user's startup files and write Elwood's start line on a private channel that is neither stdout nor stderr (so a startup file's redirection of either cannot hide it) or, from a shell that closed that channel at startup (macOS bash 3.2 as an interactive login shell), on stdout (so a startup file's redirection of stderr cannot hide it), separate from the probe command's 15-second bound, which starts at that line. The line ends in a random token chosen once per parent process, so startup-file output that resembles it does not end the launch phase. A profile slower than the command bound therefore does not fail a CLI that answers within it. A shell that never writes the line is killed at the launch bound with `ETIMEDOUT`; a shell that exits without writing it reports no exit status. Either is a login-shell failure: its cause names the login shell, its typed details carry `phase: "login_shell"` with an empty `stderr` (the startup output is withheld), and an update probe's `agent_update_failed` warning carries `errorCode: "login_shell_failed"` with an empty `raw`. Captured stdout and stderr never contain the start line, and the probe command does not inherit the private channel. The probe script removes zsh's `command_not_found_handler` and bash's `command_not_found_handle` before its command, so a CLI missing from the login shell's PATH exits 127 and none of a handler's output is captured. A version probe marks failed command lookup explicitly on the private channel or stdout fallback; only that proof or native `ENOENT` yields `*_not_found`. A resolved CLI exiting 127 remains `*_start_failed`, retaining stderr/exitStatus. Lookup markers are stripped from captured output. Confirmed lookup failures carry `exitStatus: 127`, the fixed cause of §10, and no `stderr`; native `ENOENT` retains its native cause and diagnostic fields. |
135
139
  | C-PERF-06 | §4.1 | PTY rendering preserves byte order without a per-chunk timer delay; adjacent chunks may be coalesced into batches of at most 64 KiB with a 4 ms flush window and one completed-frame observation per batch. Real PTYs pause at 1 MiB unrendered UTF-8 data and resume below 512 KiB, without dropping bytes; one delivered chunk may exceed the threshold, and a paused PTY resumes at child exit and is not paused again. Dispose settles pending render promises and suppresses subsequent render notifications. |
140
+ | C-PERF-07 | §9.2 | At most 8 non-PTY probes run at once per parent process. Further probes wait in arrival order and start, with their launch bound, only when a running probe settles, whether it succeeded or failed. |
136
141
 
137
142
  #### C-CLAUDE: Claude Startup And Settings (§4, §7, §9)
138
143
 
@@ -147,7 +152,7 @@ Each criterion has:
147
152
  | C-CLAUDE-07 | §5.1 | `autoupdate: true` runs `claude update` before spawning Claude. |
148
153
  | C-CLAUDE-08 | §5.2 | `resumeClaude` fails explicitly when Elwood has not persisted a Claude resume id. |
149
154
  | C-CLAUDE-09 | §9.2 | `autoupdate: true` rechecks the Claude version after running `claude update`. |
150
- | C-CLAUDE-10 | §5.1 | `autotrust: true` answers Claude's workspace trust prompt through PTY input and emits `startup_prompt` activity, supporting both older numbered-option layouts and current cursor-selectable layouts. |
155
+ | C-CLAUDE-10 | §5.1 | `autotrust: true` answers Claude's workspace trust prompt through PTY input and emits `startup_prompt` activity, supporting both older numbered-option layouts and current cursor-selectable layouts. The activity is emitted once per answered gate, when the composer verifies the answer, including when Claude shows a blank or banner-only screen between the erased dialog and its composer (C-TRUST-01). |
151
156
  | C-CLAUDE-11 | §5.1 | Claude's browser tools onboarding prompt is declined through PTY input regardless of `autotrust`, with `startup_prompt` activity emitted under the `browser_tools` label. |
152
157
  | C-CLAUDE-12 | §5.1 | `startClaude` forwards `model` to Claude's `--model` launch flag. |
153
158
  | C-CLAUDE-13 | §4.3 | `tools` emits Claude's `--tools` allowlist flag as one comma-separated value, with an empty array encoding `--tools ""` (all tools disabled); it is forwarded across resume like the other tool options. |
@@ -161,6 +166,7 @@ Each criterion has:
161
166
  | C-CLAUDE-21 | §5.1 §5.4 | Claude's one-time bypass-permissions acceptance dialog (header wording `Claude Code running in Bypass Permissions mode`, affirmative `Yes, I accept`, decline `No, exit`) is an allowlisted trust prompt: under `autotrust` it is recognized by its header wording outside the option region, answered once by selecting the affirmative in numbered or cursor layouts, and reported as `startup_prompt` activity with label `bypass_permissions`; with `autotrust` off it is a blocking prompt. The persistent `bypass permissions on` footer of a running session never matches, and a CLI refusal of bypass mode surfaces as the ordinary start failure. |
162
167
  | C-CLAUDE-22 | §5.1 §5.4 §9 | Non-trust Claude startup automation (the browser-tools `Esc` decline) is bound to session disposal, exactly as trust automation already is. After the session begins closing (`stop()`, `kill()`, or PTY exit) no decline is attempted, and a decline already in flight settles as a cancellation. The PHYSICAL write is bound too, not just its reported outcome: the non-trust automation barrier parks on render settlement, so it re-checks disposal after that await and abandons `sendInput` rather than delivering a stale key to a closed PTY. For the browser-tools decline, neither `startup_prompt` nor an undelivered `startup_prompt_write_failed` is emitted after close; each warning is checked at delivery even if an earlier warning listener starts shutdown. |
163
168
  | C-CLAUDE-23 | §5.7 | Native Claude login-renewal notices emit `login_expiring` warning and activity with `expiresInDays` and `/login`, including startup and resume. Welcome banners and empty-composer footers support wrapped rows; conversation/draft text is excluded. Each countdown value emits once per live session. Early warnings reach immediate post-start subscribers. Status and input remain usable; no automatic login or persistence occurs. |
169
+ | C-CLAUDE-24 | §5.7 | A Claude transcript path that does not exist yet emits no `transcript_read_error`: a fresh real session stays free of the warning from `SessionStart` through its first turn. ENOENT after the file has existed, and every non-ENOENT read failure, still emit `transcript_read_error` with that code. |
164
170
 
165
171
  #### C-CODEX: Codex Startup And Config (§4, §7A, §9)
166
172
 
@@ -174,20 +180,23 @@ Each criterion has:
174
180
  | C-CODEX-06 | §4.4 | Codex hooks are trusted by `hookTrust="trust-all"`, the bypass flag when supported, or by answering the allowlisted `Hooks need review` TUI prompt once regardless of `autotrust` (the bypass trusts all configured hooks, including third-party hooks; see C-CODEX-15). |
175
181
  | C-CODEX-07 | §5.6 | `resumeCodex` fails explicitly when Elwood has not persisted a Codex resume id. |
176
182
  | C-CODEX-08 | §5.5 | `autoupdate: true` runs `codex update` before spawning Codex. |
177
- | C-CODEX-09 | §5.7 | Codex MCP startup warnings are parsed from terminal output into typed warning events with server names and recovery commands. |
183
+ | C-CODEX-09 | §5.7 | Codex MCP startup warnings are parsed from terminal output into typed warning events with server names and recovery commands. Only the canonical startup banners are a source: the count Codex 0.154 and later show instead (0.164's `⚠ N warning(s) · f2 to view` composer badge) yields no warning and reads as part of the idle composer, and Elwood never opens the CLI's detail view. |
178
184
  | C-CODEX-10 | §9.2 | `autoupdate: true` rechecks the Codex version after running `codex update`. |
179
185
  | C-CODEX-11 | §5.5 | `autotrust: true` answers Codex's directory trust prompt through PTY input and emits `startup_prompt` activity. |
180
- | C-CODEX-12 | §5.5 | If Codex still shows an interactive update prompt inside the TUI, Elwood selects the skip/continue-without-updating option by label only when a validated first-party banner remains in the current frame before every automated write. Bannerless frames never authorize selection, including previously observed option bindings; retained input blocking remains until positive native composer clearance. Current bounded first-party option snapshots replace old bindings rather than accumulate them. Removal or renumbering of a safe binding invalidates the old retry before frame observation; a valid bannered replacement starts a fresh generation. When the update action is visible, the selected safe option must follow it and must not itself perform the update; without that action, no ordering constraint applies. Choices belong to one current native numbered block; numbered pre-banner prefixes, duplicate or restarted numbering, unknown option labels, and unrelated surrounding rows withhold automation while a recognized banner remains blocking. Such recognized ambiguity permanently revokes its active attempt; bannerless choices cannot restore it, and fresh unambiguous first-party banner evidence with a valid current safe choice starts a new attempt. After revocation, banner-only or action-only repaints cannot reauthorize later bannerless choices or renew grace. An unknown or blank replacement permanently revokes its attempt; bannerless choices cannot reauthorize it, and fresh first-party banner with a current safe choice is required. Authoritative positive native composer clearance preserves the completed attempt’s settlement; live sessions decide after the current retained input hold is classified, never from composer text alone. One unambiguous changed first-party version banner supersedes the prior appearance without needing an intervening clear; old retries reject it before writing, while the new appearance may start its own bounded attempt. Generation-tagged `codex-update-prompt` attention renews CLI grace once for an unambiguous changed banner or a fresh unambiguous banner with a current safe choice after revocation, including the same version pair; redraws and duplicate or stale generations never extend it. The skip is EDGE-triggered: a persistent update screen starts one bounded response operation (not one per rendered frame), but that operation re-reads the current rendered frame before every retry and writes only when the original first-party prompt generation remains active and the frame exposes its safe numbered choice. It stops without writing when the generation clears or changes and fails boundedly if that prompt loses its safe choice or exhausts retries; an exhausted operation stays latched until the update screen clears, and a completion from an earlier generation never re-arms the current one — once superseded by a later appearance, its late success or rejected write settles as a quiet cancellation (no `startup_prompt`, no `startup_prompt_write_failed`), as does a write rejected after its own screen has already cleared; the CLI converts an unanswerable blocked update into `blocked_prompt` even without a whole-run timeout. The skip RE-ARMS after authoritative positive native composer clearance or a fresh first-party banner with a current safe choice, so an update prompt that REAPPEARS after Codex restarts (e.g. the update did not take and the same screen returns) is skipped again rather than leaving the session stuck on it. Every recognized update screen is input-blocking until its rendered frame clears: a captured first-party banner blocks before its options render, and bannerless continuation choices remain withheld even if they match previously observed bindings. Bannerless frames cannot introduce bindings or authorize input. Evidence is bounded to 32 options and 200 characters per label; overflow withholds automation for that appearance, while a later validated first-party banner with a safe option block within both limits may supersede it once; redraws cannot restart that fresh attempt. Changed bindings invalidate captured retries; a valid bannered replacement with a safe choice owns a fresh generation and grace. Generic agent prose containing "update available" and Codex's passive installation notice do not activate the blocker. Readiness and queued persona/caller input therefore cannot select the default "Update now" action. After a recognized update is replaced, input remains held through unknown or partial frames until completed live native-composer clearance. A continuously visible pre-update composer cannot clear replacement text above it; after it disappears, a newly appearing native composer may clear with resumed history. The retained hold does not start without a prior recognized update and reports `codex-unidentified-dialog` only when no specific blocking rule matches. Closing cancels retry timers and pending automated keys; disposal prevents new attempts and late success or write-failure telemetry. A fulfilled skip write reports answered only after positive native composer clearance; missing readers and unknown replacements cancel quietly. |
186
+ | C-CODEX-12 | §5.5 | If Codex still shows an interactive update prompt inside the TUI, Elwood selects the skip/continue-without-updating option by label only when a validated first-party banner remains in the current frame before every automated write. Bannerless frames never authorize selection, including previously observed option bindings; retained input blocking remains until positive native composer clearance. Current bounded first-party option snapshots replace old bindings rather than accumulate them. Removal or renumbering of a safe binding invalidates the old retry before frame observation; a valid bannered replacement starts a fresh generation. When the update action is visible, the selected safe option must follow it and must not itself perform the update; without that action, no ordering constraint applies. Choices belong to one current native numbered block; numbered pre-banner prefixes, duplicate or restarted numbering, unknown option labels, and unrelated surrounding rows withhold automation while a recognized banner remains blocking. Such recognized ambiguity permanently revokes its active attempt; bannerless choices cannot restore it, and fresh unambiguous first-party banner evidence with a valid current safe choice starts a new attempt. After revocation, banner-only or action-only repaints cannot reauthorize later bannerless choices or renew grace. An unknown or blank replacement permanently revokes its attempt; bannerless choices cannot reauthorize it, and fresh first-party banner with a current safe choice is required. Authoritative positive native composer clearance preserves the completed attempt’s settlement; live sessions decide after the current retained input hold is classified, never from composer text alone. One unambiguous changed first-party version banner supersedes the prior appearance without needing an intervening clear; old retries reject it before writing, while the new appearance may start its own bounded attempt. Generation-tagged `codex-update-prompt` attention renews CLI grace once for an unambiguous changed banner or a fresh unambiguous banner with a current safe choice after revocation, including the same version pair; redraws and duplicate or stale generations never extend it. The skip is EDGE-triggered: a persistent update screen starts one bounded response operation (not one per rendered frame), but that operation re-reads the current rendered frame before every retry and writes only when the original first-party prompt generation remains active and the frame exposes its safe numbered choice. It stops without writing when the generation clears or changes and fails boundedly if that prompt loses its safe choice or exhausts retries; an exhausted operation stays latched until the update screen clears, and a completion from an earlier generation never re-arms the current one — once superseded by a later appearance, its late success or rejected write settles as a quiet cancellation (no `startup_prompt`, no `startup_prompt_write_failed`), as does a write rejected after its own screen has already cleared; the CLI converts an unanswerable blocked update into `blocked_prompt` even without a whole-run timeout. The skip RE-ARMS after authoritative positive native composer clearance or a fresh first-party banner with a current safe choice, so an update prompt that REAPPEARS after Codex restarts (e.g. the update did not take and the same screen returns) is skipped again rather than leaving the session stuck on it. Every recognized update screen is input-blocking until its rendered frame clears: a captured first-party banner blocks before its options render, and bannerless continuation choices remain withheld even if they match previously observed bindings. Bannerless frames cannot introduce bindings or authorize input. Evidence is bounded to 32 options and 200 characters per label; overflow withholds automation for that appearance, while a later validated first-party banner with a safe option block within both limits may supersede it once; redraws cannot restart that fresh attempt. Changed bindings invalidate captured retries; a valid bannered replacement with a safe choice owns a fresh generation and grace. Generic agent prose containing "update available" and Codex's passive installation notice do not activate the blocker. Readiness and queued persona/caller input therefore cannot select the default "Update now" action. After a recognized update is replaced, input remains held through unknown or partial frames until completed live native-composer clearance. A continuously visible pre-update composer cannot clear replacement text above it; after it disappears, a newly appearing native composer may clear with resumed history. The retained hold does not start without a prior recognized update and reports `codex-unidentified-dialog` only when no specific blocking rule matches. Closing cancels retry timers and pending automated keys; disposal prevents new attempts and late success or write-failure telemetry. A fulfilled skip write reports answered only after positive native composer clearance; missing readers and unknown replacements cancel quietly. Raw caller input (`sendKeys`, `terminal.sendInput`, `xterm.input`) ends a running skip attempt before the caller's keys are written, so no retry of it follows them; an update prompt on screen, or one that left without positive native composer clearance, then receives no skip write, including after a blank or redrawn frame, until that clearance, and a prompt appearing after it is handled as usual. |
181
187
  | C-CODEX-13 | §10 | An immediately failing or unusable Codex process fails with `codex_start_failed` or a more specific typed error. |
182
- | C-CODEX-14 | §5.3 | `setModel` on Codex restores the user's prior `config.toml` default via compare-and-swap after the CLI persists its picker selection, skipping with the `codex_default_model_persisted` warning instead of clobbering concurrent edits. The restore is written atomically (sibling temp file + rename, mode preserved, a symlinked config written through to its target) so a crash mid-restore can never leave the user's config truncated. When no `config.toml` existed before the switch and the CLI created one, there is no snapshot to restore: the new file is left in place and the same warning code carries a message saying so (not a false "changed in other ways"). Raw input taking over the picker revokes restore authority; a changed config stays intact with the same warning explaining the takeover. When the session terminates during the switch, the call rejects at once while the restore and the configuration lock wait for the CLI process to exit (bounded at five seconds) so a dying CLI cannot persist its selection after the restore. |
183
- | C-CODEX-15 | §5.5 | Codex directory trust is automated only under `autotrust`; hook trust remains enabled for all configured hooks regardless of `autotrust`. Successful bounded automation does not produce a human block. Each native header and affirmative belong to the same current dialog; partial rendering stays retryable. Unsupported or expired gates use C-TRUST-01's recoverable blocked fallback; no generic affirmative or stale input may answer another gate. |
188
+ | C-CODEX-14 | §5.3 | `setModel` on Codex restores the user's prior `config.toml` default, in the `CODEX_HOME` the session's Codex process runs with (C-CODEX-24), via compare-and-swap after the CLI persists its picker selection, skipping with the `codex_default_model_persisted` warning instead of clobbering concurrent edits. The transaction holds a process-local lock for that config file, keyed by the file the session's launch reported and shared by every path naming the file, a dangling symlink to a config not yet created included, with every key compared without regard to Unicode normalization and with letter case fully folded (`ß` and `ss` alike), so a key never changes when its file appears (a path that cannot be resolved rejects with `model_automation_failed`), so two switches on one file never interleave and sessions whose config paths have different keys never wait on each other (paths differing only in case share a key on every volume). The restore is written atomically (sibling temp file + rename, mode preserved, a symlinked config written through to its target) so a crash mid-restore can never leave the user's config truncated. When no `config.toml` existed before the switch and the CLI created one, there is no snapshot to restore: the new file is left in place and the same warning code carries a message saying so (not a false "changed in other ways"). Raw input taking over the picker revokes restore authority; a changed config stays intact with the same warning explaining the takeover. When the session terminates during the switch, the call rejects at once while the restore and the configuration lock wait for the CLI process to exit (bounded at five seconds) so a dying CLI cannot persist its selection after the restore. |
189
+ | C-CODEX-15 | §5.5 | Codex directory trust is automated only under `autotrust`; hook trust remains enabled for all configured hooks regardless of `autotrust`. Successful bounded automation does not produce a human block. Each native header and affirmative belong to the same current dialog; partial rendering stays retryable. Unsupported or expired gates use C-TRUST-01's recoverable blocked fallback; no generic affirmative or stale input may answer another gate. Codex 0.164's gate-bound footers (`enter continue · esc back`, `enter confirm · esc skip`) end a dialog only under the folder-access or hook-review gate that draws each; under any other header or options, including conversation text, they make no frame a trust gate. |
184
190
  | C-CODEX-16 | §5.1 | Codex `assistant_message`, `tool_call`, and `tool_result` activities are sourced only from the committed transcript, never re-projected from the `Stop`/`PreToolUse`/`PostToolUse` hook payloads; those hooks emit plain `hook` activity, so a single reply or tool step is surfaced exactly once (mirrors C-CLAUDE-15). |
185
191
  | C-CODEX-17 | §5.4 §5.5 §5.7 | A Codex startup prompt Elwood auto-answers (directory/hook trust, `update` skip) is marked settled and emits its `startup_prompt` activity only after its PTY `sendInput` write fulfills; trust automation with live frames also confirms the native dialog cleared. A rejected write emits NO `startup_prompt` activity, leaves the prompt un-settled so a later frame re-attempts it, and surfaces a bounded, content-free `startup_prompt_write_failed` warning carrying only the prompt label (mirrors C-CLAUDE-16). For the Codex `update` skip this applies only while that update screen is still the current generation (C-CODEX-12). |
186
192
  | C-CODEX-18 | §5.4 §7A.4 | A committed Codex `reasoning` transcript item surfaces its human-readable text on the `reasoning` activity's `text` field: the `text` of every `summary[]` entry of type `summary_text`, or — when the reasoning is un-summarized — every `content[]` entry of type `reasoning_text`, joined by newlines. The always-present `encrypted_content` blob is never readable and is never surfaced. When neither carries prose (the common case with reasoning summaries disabled), the activity carries no `text`, exactly as a bare reasoning marker. |
187
193
  | C-CODEX-19 | §5.4 §7A.4 | A committed Codex shell/exec transcript item surfaces the ACTUAL command it ran as `tool_call` activity with its `toolInput`, and its output as `tool_result` activity with its `toolOutput`, across every representation the CLI emits — and the command is UNWRAPPED from the CLI's invocation machinery so consumers see the bare command, not the harness. The modern `exec` tool is a `custom_tool_call` whose `input` is a JavaScript snippet wrapping the call (e.g. `const r = await tools.exec_command({"cmd":"echo hi","workdir":…,"yield_time_ms":…}); text(r.output);`): Elwood extracts the inner `cmd`/`command` string (a `command` array is joined with spaces) and surfaces THAT as `toolInput`, discarding the JS wrapper and the non-command fields (`workdir`, `yield_time_ms`, `max_output_tokens`). A `function_call` (`shell`/`exec_command`) whose command is a JSON string in `arguments` is likewise unwrapped to its `command`/`cmd`. When the wrapper cannot be parsed, the raw `input`/`arguments` is surfaced unchanged rather than dropped. A `custom_tool_call_output` is classified as `tool_result` (not `other`), and a result's `output` — a plain string OR an array of `{ type: "input_text", text }` entries — is surfaced as `toolOutput` with the array entries' text joined; the `tool_result` `label` carries the `call_id` correlating it to its `tool_call`. So the real command and its output are surfaced as a clean paired call/result, never the JS harness and never a content-free `other` row. |
188
194
  | C-CODEX-21 | §5.5 §5.6 | `reasoningEffort`, when supplied to `startCodex`/`resumeCodex`, is validated against the `CodexReasoningEffort` enum (`none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`) before spawn — an out-of-enum value rejects with `codex_invalid_reasoning_effort` (message listing the valid values) and no process is spawned — and a valid value is forwarded as the reserved `-c model_reasoning_effort=<value>` override, applied AFTER caller `configOverrides` so it wins over a hand-rolled duplicate. It is independent of `model`, applies to the launched session only, and is not persisted across resume (a resume must re-supply it). |
189
- | C-CODEX-20 | §5.4 | The Codex transcript reader is BOUNDED like Claude's: it reads in fixed-size chunks, discards any un-terminated record past a max-pending ceiling as an `"oversized"` drop, streams a large backlog across poll ticks under a per-scan chunk budget, and at PTY-exit drains only within a bounded chunk budget and wall-clock slice — accounting leftover bytes as an `"unread_backlog"` drop — so a hundreds-of-MiB transcript never OOMs or blocks the event loop. An unblocked `Stop` reads the committed turn with the per-pass scan budget, never the PTY-exit drain budget, so an arbitrarily long session (hundreds of turns) never exhausts that budget into false `unread_backlog` drops. Lost data is surfaced as the content-free, count-free `transcript_records_dropped` warning (a live event, cause-tagged, not persisted or counted) and a scan that throws is contained as `transcript_poll_stopped` (both `agent: "codex" | "claude"`). `finish()` is idempotent and terminal — a late `observe()` never restarts polling past `terminal:exit`. Codex transcript listener exceptions are isolated per delivery channel: raw and projected activity delivery are both attempted, later records and polls continue, and only the first failure per channel per watcher emits a content-free `transcript_listener_error` with `channel: "codex:transcript" | "activity"` (at most two). Diagnostics run in a microtask after synchronous transcript delivery, and final-flush diagnostics may follow `terminal:exit`. A warning listener failure cannot stop delivery. |
195
+ | C-CODEX-20 | §5.4 | The Codex transcript reader is BOUNDED like Claude's: it reads in fixed-size chunks, discards any un-terminated record past a max-pending ceiling as an `"oversized"` drop, streams a large backlog across poll ticks under a per-scan chunk budget, and at PTY-exit drains only within a bounded chunk budget and a wall-clock slice that can end the drain only after its first unread chunk — accounting leftover bytes, and a record they cut off, as one `"unread_backlog"` drop, never an `"unparseable"` one — so a hundreds-of-MiB transcript never OOMs or blocks the event loop. An unblocked `Stop` reads the committed turn with the per-pass scan budget, never the PTY-exit drain budget, so an arbitrarily long session (hundreds of turns) never exhausts that budget into false `unread_backlog` drops. Lost data is surfaced as the content-free, count-free `transcript_records_dropped` warning (a live event, cause-tagged, not persisted or counted) and a scan that throws is contained as `transcript_poll_stopped` (both `agent: "codex" | "claude"`). `finish()` is idempotent and terminal — a late `observe()` never restarts polling past `terminal:exit`. Codex transcript listener exceptions are isolated per delivery channel: raw and projected activity delivery are both attempted, later records and polls continue, and only the first failure per channel per watcher emits a content-free `transcript_listener_error` with `channel: "codex:transcript" | "activity"` (at most two). Diagnostics run in a microtask after synchronous transcript delivery, and final-flush diagnostics may follow `terminal:exit`. A warning listener failure cannot stop delivery. |
190
196
  | C-CODEX-22 | §5.4 §7A.4 | Committed Codex commentary response messages expose normalized `assistant_progress` with readable text, including ordered `output_text` arrays. Explicit non-final legacy agent-message mirrors remain `other` without text, preventing duplicate progress or false final replies. Final-answer behavior is preserved; analysis/unknown phases and encrypted reasoning do not become progress. Multiple progress updates precede the final reply without ending working state or entering ergonomic response text. |
197
+ | C-CODEX-23 | §5.7 §7A | A Codex transcript that does not exist yet emits no `transcript_read_error`. Once it has existed (at registration or any later poll), its removal emits `transcript_read_error` with `lastErrorCode: "ENOENT"` on each poll while it is missing, and from the final flush if it is still missing then, instead of being read as a truncation. Every non-ENOENT read failure warns whether or not the file existed. |
198
+ | C-CODEX-24 | §4.2 §5.3 §8.1 | Elwood uses the `CODEX_HOME` the session's Codex process runs with, as its launch script reports it immediately before `exec codex`, never Elwood's own environment or another shell's answer. The report carries only exported values, so it follows startup files that derive the home from `$PWD`, the terminal, or the session's environment. A non-empty home is canonicalized as Codex does: a relative one from the directory the launch shell starts Codex in, following a symlink before a later `..`. An empty or unset `CODEX_HOME` falls back to `$HOME/.codex`, a relative `HOME` taken from that same directory. The configured default (`isDefault`), the `setModel` snapshot and restore, and the `codex_default_model_persisted` warning all use that home's `config.toml`. Codex starts only if the report was written; otherwise the launch shell exits with status 73 and the start rejects with `codex_start_failed`, `details.phase: "codex_home_report"`, and the report's `details.path`. The report lives in the session's socket home until its launch's PTY exits. A model operation that starts before the report is complete waits for it within its deadline, and a deadline that ends the wait rejects with `details.path` naming the report. A report that cannot be read, or a home that does not exist, fails `listModels` and `setModel` with `model_automation_failed` naming the report, and `setModel` fails before opening the picker. |
199
+ | C-CODEX-25 | §5.3 | Elwood reads a Codex `config.toml` only if the opened file is a regular file of at most 1 MiB (1,048,576 bytes), and it opens the file without waiting for a writer, so a FIFO in its place never blocks the process. A FIFO, socket, or device (`EFTYPE`), a directory (`EISDIR`), or a larger file (`EFBIG`) fails `listModels` with `model_automation_failed` and `details: { path, code }`, and fails `setModel` the same way before it opens the picker. A target that cannot even be opened is classified the same way, so a Unix socket, which macOS refuses to open, is `EFTYPE`; any other open failure keeps its own code. When the config cannot be read at `setModel`'s restore, the call rejects the same way, or, if it has already settled, the `codex_default_model_persisted` warning's `raw` carries that code. A symlinked config is read through to its target, and a missing one is no config. |
191
200
 
192
201
  #### C-HOOK: Hook Bridge Coverage And Semantics (§6)
193
202
 
@@ -246,12 +255,13 @@ Each criterion has:
246
255
  | C-STATE-08 | §8.4 | `teardown()` removes Elwood-owned session metadata and generated files. |
247
256
  | C-STATE-09 | §8.4 | `teardown()` does not remove Claude-owned auth, transcripts, or user/project settings. |
248
257
  | C-STATE-10 | §5.2, §5.6 | Resume APIs reject session records whose persisted adapter does not match the requested adapter. |
249
- | C-STATE-11 | §8.1 | Elwood does not overwrite an existing `.elwood/.gitignore` or create gitignore files in custom `stateDir` directories. |
258
+ | C-STATE-11 | §8.1 | Elwood does not overwrite an existing `.elwood/.gitignore` or create gitignore files in custom `stateDir` directories. Default ignore installation publishes complete contents, preserves a concurrent creation, and leaves no partial default file on failure. |
250
259
  | C-STATE-13 | §8.2 | The session record persists the resolved launch posture (privilege and tool policy) at start, validates it on read, and resume updates it to the effective values. |
251
- | C-STATE-12 | §8.1 | Sessions start successfully with arbitrarily long `stateDir` paths because the hook bridge socket binds in a short Elwood-owned temp home. The home is STABLE per session (a bounded fingerprint of the session's full identity — `stateDir`, adapter, and id), so every start/resume resolves the same one while a shared explicit id in a different state dir resolves a DISTINCT home; each launch binds a fresh socket FILE inside it, and teardown removes the whole home so no per-launch socket leaks across restart/resume. |
260
+ | C-STATE-12 | §8.1 | Sessions start successfully with arbitrarily long `stateDir` paths because the hook bridge socket binds in a short Elwood-owned temp home. The home is STABLE per session (a bounded fingerprint of the session's full identity — `stateDir`, adapter, and id), so every start/resume resolves the same one while a shared explicit id in a different state dir resolves a DISTINCT home; each launch binds a fresh socket FILE inside it, and teardown removes the whole home so no per-launch socket leaks across restart/resume. A launch that ends without teardown (`stop`, `kill`, the agent exiting, or a failed start) removes its own socket file and then the home if that left it empty, keeping a home that still holds an overlapping launch's socket; the next start or resume recreates it. |
261
+ | C-STATE-14 | §8.1 | A state directory is made only while the session's `cwd` exists, and `cwd` itself is never made: one at or under `cwd`, the default `.elwood/` or a caller-provided `stateDir`, however either is spelled (another letter case on a case-insensitive volume, or a symlink), is made only below it, and a caller-provided `stateDir` elsewhere is created with its missing parents, which are removed again when `cwd` is gone once they are made, except one another start has put its session directory in; a directory that can't be removed is reported as `cleanupErrorCode` on the start's failure, and a directory another start's removal took meanwhile is made again while `cwd` exists. The `sessions` directory, each session directory, and every state file are made only inside their existing parent. A start whose `cwd` was deleted therefore fails before spawning and makes neither the worktree nor a state directory elsewhere; a resume whose state directory is removed while it checks the CLI fails without recreating it; and a running session whose state directory was in its removed worktree fails its later state writes (a new loop rejects with `loop_persistence_failed`) instead of recreating it, while one whose state directory is elsewhere keeps writing there. A failed setup removes eligible empty owned scaffolding, but retains the default shared root, its sessions directory, and any complete published default ignore because another setup may already rely on them without a named session. Only its own empty named directory is removed from that shared tree; custom-state empty scaffolding remains eligible. Cleanup attempts every eligible directory and preserves the primary error and first cleanup code, including typed error details. |
252
262
  | C-LIFE-09 | §9.2 | `autoupdate` runs the adapter's update command at most once per parent process per adapter, so fleet spawns do not race N concurrent same-binary updates. Separate Elwood parent processes for the same macOS user and adapter also hold an atomic cross-process lease around that global update; contenders wait asynchronously, skip the duplicate update, invalidate their own caches, and validate the installed binary after the owner settles. The stable per-account lease records owner identity and generation, never evicts a live owner solely for age, serializes dead-owner recovery, and cannot be removed by an old owner after a successor claims it. A FAILED in-process shared update is shared once too — concurrent callers observe the same failure and proceed through the compatibility gate — and is not retried per start nor retained as a rejected result that poisons later starts. |
253
263
  | C-LIFE-11 | §9.2 | Autoupdate is BEST-EFFORT: when the update command fails but the installed CLI still meets the minimum version, startup emits a live `agent_update_failed` warning (safe diagnostics only) and reaches `ready` from the installed binary; startup fails only when the installed version is below the minimum. Version and capability caches are invalidated even after a failed attempt because the installer may have partially changed the binary. No per-process cached probe (version read, capability detection, update) retains a rejected result — a later caller re-attempts rather than inheriting a prior failure. |
254
- | C-ERR-08 | §10 | Startup failures carry the underlying cause, and errno/syscall/path details when the underlying error provides them. |
264
+ | C-ERR-08 | §10 | Startup failures carry the underlying cause, and errno/syscall/path details when the underlying error provides them. When startup cleanup cannot confirm the PTY termination or process-group reap (§9.1), the typed startup error keeps its code and message and adds a content-free `cleanupErrorCode` and a diagnostic-only `cleanupProcessGroupId`; a confirmed cleanup adds neither. |
255
265
 
256
266
  #### C-LIFE: Lifecycle Controls (§5, §9)
257
267
 
@@ -262,9 +272,10 @@ Each criterion has:
262
272
  | C-LIFE-03 | §5.3 | `kill()` force-terminates the Claude process and updates status. |
263
273
  | C-LIFE-04 | §9.3 | `resumeClaude` restores hook routing and PTY control for a saved Elwood session. |
264
274
  | C-LIFE-05 | §5.3 | A live `ClaudeSessionApi` (the started session) owns exactly one main Claude process; the lazy `ClaudeSession` wrapper (§5.8) owns none before start and that one after. |
265
- | C-LIFE-10 | §5.3 | `stop()`, `kill()`, and `teardown()` reap the full PTY process tree by SIGKILLing the leader's process group, so a descendant reparented to PID 1 after the leader exits (e.g. the CLI-spawned `hook-bridge.mjs`) is still killed and no process tree leaks across launch/quit cycles. |
275
+ | C-LIFE-10 | §5.3 | `stop()`, `kill()`, and `teardown()` reap the full PTY process tree, but Codex's shared app-server daemon, its updater, and what they started, so no process tree leaks across launch/quit cycles. A descendant that stays in the leader's process group is reaped by SIGKILLing that group, even after the leader's exit reparents it to PID 1. A descendant in a process group of its own, such as the CLI-spawned `hook-bridge.mjs` the CLI starts with `setsid()`, is reaped by the detached walk: before Elwood SIGKILLs a live leader (`kill()`, `teardown()`, or a `stop()`/startup-cleanup escalation), it freezes the leader's group, walks the process table by parent pid, and SIGKILLs every other process group holding a live descendant, while never signaling a non-descendant, and never killing Codex's shared app-server daemon (a group leader named `codex`, child of a `codex` process or PID 1, run from a path with a `/packages/app-server-daemon/` directory in it) or anything it started: a group it stopped that the latest read shows holding the daemon, or a process not stopped that could still become it, is resumed. A group is stopped only once a parent of one of its members read stopped, inside a group Elwood stopped, in two consecutive reads, so a group id reused after the table read is never signaled; the walk succeeds only on a read that shows the leader live and every process in every stopped group, the leader's included, stopped in that read and the one before, with no live group unreached; a stopped group in which a read shows a live process not stopped (one forked as its SIGSTOP went out) is stopped again; and stopped groups are killed deepest first, but those resumed as holding the daemon. After the leader's exit is observed, no further SIGSTOP or SIGKILL is sent to it and no further group is stopped. A walk with no such read within its ten-second deadline or before the leader's exit is observed, or whose leader's group refuses its SIGSTOP while the leader lives, or that cannot resume a group held as the daemon's (a SIGCONT refused for any reason but the group being gone), still kills (unless exited) and reaps the leader, then rejects (`termination_failed`, naming up to 16 of the groups in `details.unconfirmedProcessGroupIds`; `teardown_failed` from `teardown()`), and every later `stop()`, `kill()`, or `teardown()` of that session rejects too. Exception: detached descendants of a leader that exits on its own before Elwood signals it, or on a `stop()`'s SIGTERM within the graceful window, are not traced (so a `stop()` during Codex's shell-environment snapshot leaves that shell running), nor is a descendant whose own parent exited before the walk. Native table scans and daemon-path probes share at most two in-flight subprocesses per parent process, after request coalescing; admission is FIFO, waiting consumes the existing timeout, and queued expiry starts no subprocess. A short coalesced consumer cannot cancel a longer consumer’s read. A leader-group SIGSTOP returning ESRCH, or EPERM followed by a bounded fresh read showing the entire group gone, permanently suppresses later PTY and group signals for that session, including queued sweeps and final reaps; earlier unresolved descendant failures still reject. A failed start or resume ends its PTY through that same session reaper (§9.1): a PTY whose exit the session already saw is never signaled again, and its group is SIGKILLed once. `stop()` and `kill()` run runtime cleanup after every attempt, even when termination, the reap, or the walk failed, and still reject with that failure, any cleanup failure's message alongside it in `details.cleanupError`. |
266
276
  | C-LIFE-11 | §5.3 | A `teardown()` that rejects in-flight control-queue operations still completes: the rejected `sendMessage`/`compact`/model operation does not skip the process-group reap or remaining teardown steps. |
267
- | C-LIFE-12 | §9.4 | Runtime cleanup on stop, kill, teardown, and unsolicited exit drains received terminal output before disposing the renderer, so the final `terminal:data` tail is delivered (possibly after `terminal:exit`) instead of dropped. The drain ends only when a settle pass completes with no new PTY output received, shares one one-second budget after which a stalled renderer or never-quiet PTY is disposed anyway, never delays process-exit notification, terminal status, or the process-group reap, always disposes even when draining fails, and readiness cancelled at exit cannot be re-armed or submitted by a drained frame. |
277
+ | C-LIFE-12 | §9.4 | Runtime cleanup on stop, kill, teardown, and unsolicited exit drains received terminal output before disposing the renderer, so the final `terminal:data` tail is delivered (possibly after `terminal:exit`) instead of dropped. The drain ends only when a settle pass completes with no new PTY output received, shares one one-second budget after which a stalled renderer or never-quiet PTY is disposed anyway, never delays process-exit notification, terminal status, or the process-group reap, always disposes even when draining fails, and readiness cancelled at exit cannot be re-armed or submitted by a drained frame. Runtime cleanup also stops the hook bridge and finishes the transcript watcher with one bounded final flush, so no transcript activity follows it, even from a PTY that outlived a failed shutdown. |
278
+ | C-LIFE-13 | §5.7 | Once a Codex `Stop` hook with a `transcript_path` arrives, `stop()` and `teardown()`, including calls from that hook's observers, handler, or `ready` listeners, signal the CLI only at the first of: a transcript record of that turn's end carrying its `turn_id`, a blocking hook result, the CLI's exit, or 2 seconds. When the 2 seconds end it, one content-free `turn_commit_wait_expired` warning precedes the signal, even for overlapping calls; a throwing listener of it does not stop the shutdown. They reject queued input and pause loops at once, `kill()` does not wait, and with no turn pending shutdown starts in the same tick. |
268
279
 
269
280
  #### C-ERR: Error Model (§10)
270
281
 
@@ -306,25 +317,26 @@ Each criterion has:
306
317
  | C-CLI-06 | §12A.2 | Built-in and selected per-agent permission, sandbox, approval, model, effort, and persona settings validate against the effective adapter before launch. |
307
318
  | C-CLI-07 | §12A.2 | An optional duration bounds launch, setup, and turn; first and repeated `SIGINT` perform interrupt then force-kill behavior; timeout and interruption exit 124 and 130 respectively. |
308
319
  | C-CLI-08 | §12A.2 §12A.5 | New and resumed headless state preserves by default; explicit `--keep` selects preservation, `--ephemeral` selects teardown for either, the two flags conflict, exact resume never falls back, teardown does not undo workspace/agent-owned state, and persona is one output-discarded but side-effect-capable new-session setup turn that is rejected on resume. |
309
- | C-CLI-09 | §12A.2 | Premature agent exit and cleanup failure retain collected response data; cleanup runs once, never replaces a primary failure, and changes otherwise-successful execution to status 1. |
320
+ | C-CLI-09 | §12A.2 | Premature agent exit and cleanup failure retain collected response data; cleanup runs once, never replaces a primary failure, and changes otherwise-successful execution to status 1. The cleanup failure's diagnostic names each typed cause by its error code, plus its valid errno and reap errno tokens and up to 16 positive-integer unconfirmed process-group ids when present (`Cleanup failed (termination_failed EPERM).`, `Cleanup failed (termination_failed EAGAIN process groups 4242).`), never their messages or paths. In text mode, a cleanup failure behind a different primary failure is its own stderr line after the primary error, in `run`, `models`, and combined `models`. |
310
321
  | C-CLI-10 | §12A.3 | Default text output contains only the combined observed assistant messages with exact separator/newline semantics; text/JSON live session warnings (including model probes) are silent unless verbose or debug is enabled, while JSONL keeps ordered warning records and fatal errors remain visible; verbose emits concise elapsed progress without assistant/thinking duplication; debug detail stays on stderr; and streamed text is emitted once with partial output retained on failure. |
311
322
  | C-CLI-11 | §12A.3 | JSON emits one version-1 terminal document and JSONL emits ordered normalized records plus exactly one terminal record, including structured validation errors when explicitly selected; every JSONL record has `elapsedMs` and tool records carry `toolCallId` when available. An error record emitted before any adapter was selected reports `agent: null`; otherwise `agent` is the selected or running adapter. |
312
323
  | C-CLI-12 | §12A.3 | Output excludes terminal and secret-bearing internals, honors backpressure, and treats downstream `EPIPE` as graceful consumer closure followed by cleanup. |
313
324
  | C-CLI-13 | §12A.4 | Config path resolution follows explicit, absolute XDG, then home fallback order; config is strict version 1 with only documented keys, and project-local config is ignored. |
314
325
  | C-CLI-14 | §12A.4 | Flag, environment, config, and built-in precedence is deterministic, environment booleans are strict, negative flags reverse inherited stream/verbose/high-trust values, `--no-defaults` bypasses Elwood run defaults, and config path/show/get/set/unset/effective never launches an agent (agent auto-detection only asks the login shell whether a command resolves). |
315
- | C-CLI-15 | §12A.4 §12A.5 | Config and state reads and writes enforce private ownership, regular-file, symlink-safety, and atomicity constraints. |
326
+ | C-CLI-15 | §12A.4 §12A.5 | Config and state reads and writes enforce private ownership, regular-file, symlink-safety, and atomicity constraints. A workspace another user could replace before launch is a usage error: resolving its path as given, through every symlink, each directory a name is looked up in must be owned by root or the user and not group- or other-writable unless sticky (group write is allowed for the user's private group, gid equal to the uid and the primary gid, never group 0), and each directory and symlink passed must be owned by root or the user. Mode bits only: ACLs and private-group membership are not checked. A new session's state root is made only while its workspace exists and never recreates it: a workspace removed after validation fails the launch with the adapter's `*_start_failed`. |
316
327
  | C-CLI-16 | §12A.5 | CLI state defaults to the absolute XDG state base or `~/.local/state/elwood`, never the workspace, and one session identity has at most one live owner. |
317
328
  | C-CLI-17 | §12A.5 | Success, agent/cleanup failure, usage/config failure, timeout, and interruption map to statuses 0, 1, 2, 124, and 130 without cleanup masking a primary status. |
318
329
  | C-CLI-18 | §12A.6 | `--head` fails before launch without terminal stdin/stderr and rejects stream/verbose/debug/JSONL; otherwise it mirrors ordered raw PTY bytes (including full-screen VT/ANSI control sequences) only to terminal stderr, uses and follows its size, keeps final text/JSON stdout clean, treats raw Ctrl-C like SIGINT, stays attached through session cleanup so output received before the run ended (including the §9.4 drained tail) is mirrored, and then restores terminal input/display modes before final output on every handled outcome. Outstanding mirror work is capped at 4 MiB or 1,024 frames; overflow fails the run after draining accepted bytes and restoring the terminal instead of growing memory without bound. A terminal-gone error from an input handle already closed by its host is contained because only that host can then restore the terminal, and it does not replace a completed result. |
319
330
  | C-CLI-19 | §12A.4 | `config effective` prints validated effective launch/output values (including `head` and the full Claude tool posture) with config location/load state and per-setting provenance (including `auto-detected` for a probed agent), supports exact stored resume inspection using the same posture merge as launch, reads no prompt input, and starts no agent. |
320
331
  | C-CLI-20 | §12A.5 | Text validation errors use user-facing language, identify relevant paths and inherited-setting sources, suggest an unambiguous nearby long option, and retain the stable structured error code. |
321
- | C-CLI-21 | §12A.1 §12A.4 | When no flag, environment variable, or config key selects an agent, a new session (and `config effective`) probes the user's login shell for `claude` and `codex` concurrently and uses the first in that order whose command resolves, reporting source `auto-detected`; the probe never runs an agent, resume never probes, and when neither resolves the invocation fails before launch with code `no_agent_found` and status 2, naming both agents, an install hint, and the `--agent`/`ELWOOD_AGENT`/config selection paths. A probe that cannot run (missing or broken login shell, probe timeout) fails with the same code and status but names the shell and underlying error rather than claiming no agent is installed; adapter-option conflicts with an auto-detected agent say so and offer `--agent` for the other adapter. |
332
+ | C-CLI-21 | §12A.1 §12A.4 | When no flag, environment variable, or config key selects an agent, a new session (and `config effective`) validates its effective workspace under §12A.5 before probing, whether selected by `--cwd` or inherited from the invocation directory; path-specific workspace usage errors precede `no_agent_found` and probe failures, while explicit-agent and resume validation order is unchanged. It probes the user's login shell from that workspace for `claude` and `codex` concurrently and uses the first in that order whose command resolves, reporting source `auto-detected`; the probe never runs an agent, resume never probes, and when neither resolves the invocation fails before launch with code `no_agent_found` and status 2, naming both agents, an install hint, and the `--agent`/`ELWOOD_AGENT`/config selection paths. A probe that cannot run (missing or broken login shell, probe timeout) fails with the same code and status but names the shell and underlying error rather than claiming no agent is installed; adapter-option conflicts with an auto-detected agent say so and offer `--agent` for the other adapter. |
322
333
  | C-CLI-22 | §12A.2 §12A.4 | `--high-trust` / `--no-high-trust`, `ELWOOD_HIGH_TRUST` (strict boolean), and the `highTrust` config key layer with normal precedence and expand, for the effective adapter, to Claude `bypassPermissions` or Codex `danger-full-access` + `never`; an effective flag/environment `true` combined with an explicit per-agent posture flag or variable is a usage error naming both sources on new and resumed runs; saved per-agent posture keys are overridden by high trust while a saved `highTrust` yields field by field to explicit per-agent flags/variables; and `config effective` reports `highTrust` plus posture values whose source is the deciding high-trust source. |
323
334
  | C-CLI-23 | §12A.7 | `elwood resume <id> [options] [prompt...]` is a parse-time rewrite of `elwood run --resume <id>` sharing one resolution/execution path: stored adapter and workspace loading, `--cwd`/explicit-adapter/`--keep`/persona/`--ephemeral` rules, and positional-plus-piped prompt composition are identical; a missing id or an additional `--resume` is a usage error. |
324
335
  | C-CLI-24 | §12A.8 | `elwood sessions` lists private, owner-matched session records under the effective state directory without launching an agent, reporting id, adapter, workspace, ISO-8601 `createdAt`/`lastUsedAt`, `resumable`, and socket-presence `live`, ordered by most recent use; unreadable records are skipped with a stderr warning, an empty or absent state directory is a status-0 empty listing (stderr notice in text, empty array in JSON), text is an aligned header table, JSON is one version-1 `sessions` document, and JSONL, prompt words, or other run options (including `--show-session-id`) are usage errors. |
325
336
  | C-CLI-25 | §12A.9 | `elwood interactive [id]` spawns the selected agent's own CLI in the foreground with inherited stdio, no PTY/bridge/automation/state, `SIGINT` passed through, and the agent's exit status; it fails with status 2 before spawning without terminal stdin and stdout or with `--output json|jsonl`, `--stream`, `--verbose`, `--debug`, `--head`, `--timeout`, `--persona`, `--image`, `--keep`, `--show-session-id`, `--ephemeral`, `--resume`, or extra positionals; explicitly selected agent, model, effort, workspace, and posture map through the adapters' shared launch-argument builders while built-in posture defaults are omitted; and `[id]` loads the stored record with `--resume` semantics, merges stored posture field by field, and passes the agent's own conversation id or fails as `resume_unavailable`. |
326
337
  | C-CLI-26 | §12A.10 | `elwood models` without explicit `--agent` probes Claude then Codex sequentially despite environment/config agent defaults, associates each catalog with its adapter, applies each adapter’s settings and one shared timeout budget, retains partial successes alongside per-agent errors in one aggregate JSON document or an AGENT-column table, exits nonzero on incomplete results, and stops before launching another adapter after SIGINT. Explicit `--agent` preserves the single-agent contract: it starts a fresh headless session with normal setting precedence, lists models through the public picker operation without changing the model, always tears the session and its state down, prints an aligned table (`*` current, `(default)`) or one version-1 `models` document of `AgentModelOption` rows, maps timeout/interruption/blocked-prompt/agent-exit/picker failures to the existing error conventions and statuses, and rejects stream, head, persona, image, keep, show-session-id, resume, ephemeral, positional, and JSONL inputs as usage errors. |
327
338
  | C-CLI-27 | §12A.1 §12A.3 | Successful text runs print only the agent response by default, while retaining sessions. `--show-session-id` opts into the retained ID footer on stderr after successful cleanup; ephemeral runs emit none. JSON/JSONL retain their existing sessionId field. The flag is accepted by run/resume and rejected by interactive/models/sessions. |
339
+ | C-CLI-28 | §12A.2 §12A.5 | A headless Claude or Codex run whose user or persona turn the agent rejects reports `turn_failed` with exit status 1: text mode writes the error to stderr, and JSON/JSONL end in an error record with that code instead of a result record. |
328
340
 
329
341
  #### C-E2E: Real Adapter Flows (§12)
330
342
 
@@ -341,14 +353,19 @@ Each criterion has:
341
353
  | C-E2E-10 | §5.1 | A real committed Codex turn surfaces exactly one `assistant_message` (with `source: "transcript"`), never a second copy from the `Stop` hook (C-CODEX-16). |
342
354
  | C-E2E-11 | §5.3 | A real Codex session with a queued initial persona delivers that message to Codex (a `UserPromptSubmit` with the persona text is observed) rather than swallowing it — the queue is released on `SessionStart`, not on the boot-time composer placeholder (C-API-28). |
343
355
  | C-E2E-12 | §5.3 | A real Claude session attaches an image supplied via `sendMessage(message, { images })` — pasting the absolute path drives the CLI to show its `[Image #N]` chip in the rendered composer — verified against the installed CLI (C-API-45). |
344
- | C-E2E-13 | §5.3 | A real Codex session on macOS attaches an image supplied via `sendMessage(message, { images })` — the clipboard-injection + Ctrl+V path drives the CLI to show its `[Image #N]` chip in the rendered composer, and the user's prior clipboard is restored afterward — verified against the installed CLI (C-API-46). |
345
- | C-E2E-09 | §5.1 | The trust-prompt allowlist recognizes and answers the REAL folder-trust frame the installed Claude CLI renders in a fresh untrusted directory (header-anchored recognition + numbered or cursor affirmative selection), verifies the trust screen clears before readiness, and fails if a fully rendered trust frame is visible but unanswerable; it skips loudly only when no trust frame renders (for example, an already trusted directory). |
356
+ | C-E2E-13 | §5.3 | A real Codex session on macOS attaches an image supplied via `sendMessage(message, { images })` — the clipboard-injection + Ctrl+V path drives the CLI to show its `[Image #N]` chip in the rendered composer, and the user's prior clipboard is restored afterward, and once the session is torn down the copy Codex saved of the pasted image is gone — verified against the installed CLI (C-API-46). |
357
+ | C-E2E-09 | §5.1 | The trust-prompt allowlist recognizes and answers the REAL folder-trust frame the installed Claude CLI renders in a fresh untrusted directory (header-anchored recognition + numbered or cursor affirmative selection), verifies the trust screen clears before readiness, and fails if a fully rendered trust frame is visible but unanswerable; it skips loudly only when no trust frame renders (for example, an already trusted directory). Separately, the native Codex run, launched without the hook-trust bypass, answers the folder and hook gates, and on Codex 0.164 finds the MCP failure only as the composer's warning badge, from which no warning is parsed (C-CODEX-09). |
346
358
  | C-E2E-14 | §5.8 | Against a REAL Claude (or Codex) CLI, a lazily-started `ClaudeSession`/`CodexSession` answers two sequential `send` calls: the first returns non-empty assistant text, and the second — referring back to the first — returns text consistent with retained conversation context, proving `send` collects a turn's assistant text and the ergonomic layer preserves multi-turn context (C-API-47, C-API-49). |
347
359
  | C-E2E-15 | §5.8 | Against a REAL CLI, `stream(prompt)` for a task that DETERMINISTICALLY uses a workspace tool (reading a planted file) yields the turn's simplified typed events in arrival order — a `tool_call` FOLLOWED by its `tool_result`, and at least one `text` — and ends when the turn settles, verified against the installed CLI (C-API-48). |
348
360
  | C-E2E-16 | §5.1 §5.5 | Against the REAL CLIs, a session started with `highTrust: true` reaches `ready` on both adapters: Claude's rendered composer shows its bypass-permissions footer with no acceptance dialog left visible (answered when the installed CLI renders one), and Codex's persisted launch posture is `danger-full-access` / `never` (C-API-54, C-CLAUDE-21). |
349
361
  | C-E2E-17 | §5.4 §7A.4 | An isolated real Codex turn emits readable `assistant_progress` before its final `assistant_message`, retains running/ready lifecycle, and returns only final text through ergonomic `send()`, without tools or changes to parent configuration. |
350
- | C-E2E-18 | §5.10 §7 | Two isolated native Claude public-API turns accept grouped single/multiple choices and multiline alternative text, confirm complete native answers and answered closures, return only final replies, exclude submitted answer markers from question telemetry and Elwood-owned state, remove private artifacts and homes, and preserve parent configuration/authentication/environment. |
362
+ | C-E2E-18 | §5.10 §7 | Two isolated native Claude public-API turns accept grouped single/multiple choices and multiline alternative text, confirm complete native answers and answered closures, return only final replies, exclude submitted answer markers from question telemetry and Elwood-owned state, remove private artifacts and homes, leave the parent's settings, hooks, login, and environment unchanged, and never name their private paths, or any path inside them, in the parent's global config, which other Claude sessions may rewrite meanwhile. Claude keeps the proof's temp directory under the suite's private `CLAUDE_CODE_TMPDIR`, never under the shared `/tmp/claude-<uid>`. The proof checks the default Claude config dir and refuses to run when the user sets `CLAUDE_CONFIG_DIR`. |
351
363
  | C-E2E-19 | §5.10 §7A.5 | In two isolated native Codex public-API turns, one asynchronous call raises three independently answerable queued questions and one Plan-mode call raises a three-question modal. `answerQuestions()` submits the queued long note, literal `Type your answer`, then color choice while the original turn remains running; the modal accepts None of the above, a named choice with long notes, and literal `Add notes`. Full committed native free-text answers and notes must match newline-normalized, outer-whitespace-trimmed values while preserving interior tabs, trailing spaces, multiline indentation, and more than 700 characters; queued selected labels are trimmed and modal selected labels remain exact, as specified in §5.10. Question events and normalized activities exclude submitted answers; Elwood-owned state stays answer-free, and private artifacts, native homes, and parent configuration are cleaned up or preserved as appropriate. |
364
+ | C-E2E-20 | §5.8 §12A.2 | Against the REAL Claude CLI, a bogus `--model` (refused before any model quota is consumed) fires `StopFailure` instead of `Stop`; `elwood run` exits 1 with a `turn_failed` error document, and `ClaudeSession.send` rejects with `turn_failed` and `details.agentError: "model_not_found"`. |
365
+ | C-E2E-21 | §5.8 §12A.2 | Against the REAL Codex CLI, a bogus `--model` (refused before any model quota is consumed) fires `UserPromptSubmit` but no `Stop`, and commits a failed `task_complete`; `elwood run` exits 1 with a `turn_failed` error document, and `CodexSession.send` rejects with `turn_failed` whose message names `model_not_found`, twice in a row on one session. |
366
+ | C-E2E-22 | §12 | A real Codex e2e file run the way a developer runs one, with `CODEX_HOME` unset, leaves the invoking user's Codex home with the same files, contents, and mtimes, while Codex writes the trust entry for the run's scratch project into the suite's private `CODEX_HOME`. That home starts with only a 0600 `auth.json`, whatever else the user's Codex home holds, and is gone once the run's process exits. |
367
+ | C-E2E-23 | §12 | A real Claude e2e run with `CLAUDE_CONFIG_DIR` unset reaches `ready` from the suite's private Claude config dir, whose login copy holds no refresh token. Claude receives neither `ANTHROPIC_API_KEY` nor `ANTHROPIC_AUTH_TOKEN` to log it in: not from Elwood's environment, and not from the launch shell's startup files, even when they export one. Claude records the run's scratch project in that dir's global config, while the invoking user's global config and `projects/` never name it. The private dir is gone once the run's process exits, and the user's config dir is unchanged by its removal. Claude keeps the scratch project's temp directory under the process's private `CLAUDE_CODE_TMPDIR`, never under the shared `/tmp/claude-<uid>`, and that private root is gone once the process exits too. |
368
+ | C-E2E-24 | §12 | While `ELWOOD_E2E_PRIVATE_HOMES` lists registered homes and Elwood's environment sets `CODEX_HOME`, `CLAUDE_CONFIG_DIR`, or `CLAUDE_CODE_TMPDIR` to one, every login shell Elwood starts runs its command only if its startup files leave each such variable exported with a real path that is a registered home, and otherwise exits 97 before the command. A startup file that replaces, unsets, or unexports it, points it at an unregistered directory beside the registered ones, at a link to another directory, or through `..`, or moves it only when attached to a terminal, stops the launch; one that moves it to another registered home, even by a relative name under a `CDPATH`, a `cd` function, or a zsh `chpwd` hook, does not. A list that is not a JSON array of absolute paths stops every launch. With the list unset or empty, or no home set to a registered one, the launch command is unchanged. The guard writes a stop line that starts with a per-process token, on a session's terminal or a probe's stdout, never on stderr. A probe the guard stops, whether it captures its command's output or only its status, and even when a startup file redirected its stderr, or its stdout in a shell that keeps the probe's private channel, still announces its start and fails as a login-shell failure with shell status 97, the guard's fixed reason, and its stderr withheld, and auto-detection reports it as a failed check, not a missing agent; a probe command that itself exits 97, with the list unset, guarding no home, or under a guard that passes, is the command's own failure, and a login shell that exits before the start line is still the login shell's failure. An e2e process discards an inherited list, so a startup file that moves a guarded home to a directory only that list named stops the launch. A session's stop line reaches its terminal even when a startup file redirected the shell's stdin, stdout, and stderr, and a probe's guard never writes to a terminal. A session whose login shell exits 97 during startup after writing the stop line fails with `*_start_failed` naming the guard; any other status, or 97 without the stop line, fails as before. The stop line counts after any amount of earlier startup output, and when it reaches the PTY up to one second after the check sees the exit; a status-97 exit of a launch the guard does not check fails at once. The conformance test runs each shell case in a real PTY or probe, in zsh and in bash. |
352
369
 
353
370
  #### C-REVIEW: Maintainer automation (§16)
354
371
 
@@ -364,8 +381,8 @@ Each criterion has:
364
381
 
365
382
  | ID | Spec | Criterion |
366
383
  | --- | --- | --- |
367
- | C-API-55 | §5.3 | Model-picker operations own the input queue until they settle. Overlapping model calls and messages cannot interleave; the queue wait and the operation share `timeoutMs`, so a timed-out operation still queued never opens a picker; the deadline and termination prevent later writes. A failed operation's own dialog is cancelled within one additional second before the slot is released: one Escape per stage (a cancelled follow-up returns to the picker), never repeated, none after termination. A dialog that survives keeps queued input, commands, and `/login` held while visible. Only a bottom-most dialog region counts; dialog text above a conversation or composer row is never cancelled or held on. A partially painted Claude switch dialog holds input and is never written to, and `setModel` does not settle while one is visible. When a submitted picker command has not rendered its dialog by the failure, or an accepted stage can still paint its follow-up, the slot is held for that same second and a dialog appearing in it is cancelled; a dialog repainting after a clear frame gets its own Escape. Model picker text already visible when the operation begins rejects it with `model_automation_failed` and no writes or hold. Initial command dispatch revalidates foreign-picker absence; public raw input revokes the active operation and its cleanup authority. Foreign-picker holds survive partial repaints and require five consecutive positive native-composer observations to release ordinary input. |
368
- | C-TRUST-01 | §5.4 | Automatic trust writes require one current native dialog with allowlisted header, native explanatory/path/option rows, and exact affirmative; where the native dialog has explanatory copy, a header without it never authorizes a write. Candidate trust headers hold readiness and queued input even when unsupported; transcript provenance is retained. Human-owned candidates retain their hold through partial or unknown replacements until positive composer/successor clearance. Live Codex clearance requires the visible native input cursor, text, and title from one completed render; newly received output invalidates that proof until rendered, and native approval headers override a stale visible composer cursor. One session-owned coordinator gives generations separate reservations and a five-second deadline from first candidate. Every numbered/cursor write revalidates generation, header, and choice; partial native painting and cursor movement remain supported. A fulfilled confirmation settles answered only on verified native composer/successor clearance. Replacements and stale completions cannot settle or release a newer generation. Expiry produces existing recoverable blocked/attention without waiting for a frame, keeps input held, and does not warn as a PTY failure. New validity/generation re-arms bounded automation; unchanged redraws cannot reset expiry; raw human input can clear the gate. Stop/kill/teardown/startup cleanup/PTY exit cancel attempts and timers quietly. Only a live matching rejected write emits startup_prompt_write_failed. |
384
+ | C-API-55 | §5.3 | Model-picker operations own the input queue until they settle, and a successful one settles only after no stage of its dialog, complete or partly painted, has been the bottom-most region across five readings ≥40 ms apart: a blank frame the dialog repaints over keeps the slot held, dialog text above the composer does not, and each reading first renders all received output, failing the operation when it cannot. Overlapping model calls and messages cannot interleave; the queue wait and the operation share `timeoutMs`, so a timed-out operation still queued never opens a picker; the deadline and termination prevent later writes. A failed operation's own dialog is cancelled within one additional second before the slot is released: one Escape per stage (a cancelled follow-up returns to the picker), never repeated, none after termination. A dialog that survives keeps queued input, commands, and `/login` held while visible, and is forgotten only on the same positive clearance as a foreign-picker hold: a blank or half-painted frame that neither grammar recognizes never counts. Only a bottom-most dialog region counts; dialog text above a conversation or composer row is never cancelled or held on. A partially painted Claude switch dialog holds input and is never written to, and `setModel` does not settle while one is visible. When a submitted picker command has not rendered its dialog by the failure, or an accepted stage can still paint its follow-up, the slot is held for that same second and a dialog appearing in it is cancelled; a dialog repainting after a clear frame gets its own Escape. Model picker text already visible when the operation begins rejects it with `model_automation_failed` and no writes or hold. Initial command dispatch revalidates foreign-picker absence; public raw input revokes the active operation and its cleanup authority. Foreign-picker holds survive partial repaints and require five consecutive positive native-composer observations to release ordinary input; observations count at least 40 ms apart, so readings bunched into one instant or a burst of repaints count once. |
385
+ | C-TRUST-01 | §5.4 | Automatic trust writes require one current native dialog with allowlisted header, native explanatory/path/option rows, and exact affirmative; where the native dialog has explanatory copy, a header without it never authorizes a write. Candidate trust headers hold readiness and queued input even when unsupported; transcript provenance is retained. Human-owned candidates retain their hold through partial or unknown replacements until positive composer/successor clearance. Live Codex clearance requires the visible native input cursor, text, and title from one completed render; newly received output invalidates that proof until rendered, and native approval headers override a stale visible composer cursor. One session-owned coordinator gives generations separate reservations and a five-second deadline from first candidate that counts only frames showing the gate: a frame without it pauses the deadline and the same gate's return resumes the remaining time, so expiry needs five cumulative visible seconds and a gate absent from its departure through verified clearance never expires. Every numbered/cursor write revalidates generation, header, and choice; partial native painting and cursor movement remain supported. A numbered answer's first write to a gate types its number, and every later attempt at that gate retries as after it, even when that write or a retry was rejected, until another complete dialog replaces the gate; a Codex gate whose rendered caret is on the affirmative is retried with Enter alone, and any other numbered gate has its number typed again only after a frame rendered after the last write still shows it, so no retry submits the number from a composer. A cursor layout receives only absolute selections of the affirmative (Home plus one Down per preceding option), and Enter only in the same write as that selection while the rendered cursor is on the affirmative; no relative arrow or bare Enter is written, so a reset, delayed, or retried key cannot confirm another option. A fulfilled confirmation settles answered only on verified native composer/successor clearance; dialog-free frames after it neither settle nor cancel it while a complete off-allowlist gate cancels it unreported, so a gate the CLI erases on the answer is reported once when its composer or successor is verified, and a gate that returns is answered again within its remaining budget. Replacements and stale completions cannot settle or release a newer generation. Expiry produces existing recoverable blocked/attention without waiting for a frame, keeps input held, and does not warn as a PTY failure. New validity/generation re-arms bounded automation; unchanged redraws cannot reset expiry; raw human input can clear the gate. Stop/kill/teardown/startup cleanup/PTY exit cancel attempts and timers quietly. Only a live matching rejected write emits startup_prompt_write_failed. Raw caller input (`sendKeys`, `terminal.sendInput`, `xterm.input`) during a trust dialog generation cancels its attempt before those keys are written, and no further automatic write reaches that generation, including after it repaints or expires; a later generation is answered as usual. |
369
386
 
370
387
  #### C-SITE: Documentation Site Controls (§13)
371
388
 
@@ -73,3 +73,14 @@ Default-branch pushes and manual cache preparation warm the shared SDK,
73
73
  reviewer tools, and installed caller dependencies. Exact cache matches skip
74
74
  installation; changed manifests, toolchains, or cache versions rebuild the
75
75
  necessary layer. Cache preparation does not call a model or publish a review.
76
+
77
+ ## Secret scanning
78
+
79
+ Secret-scan results are advisory for merging into `main`. PR, merge-queue, and
80
+ pushed-range scans fail their jobs on findings. Full-history audits on new tag
81
+ pushes, first default-branch pushes, and manual runs warn on findings during
82
+ historical triage; scanner, Git, and configuration errors fail the job.
83
+ The release workflow separately scans its selected source before publication;
84
+ findings and errors remain blocking there. Repository checkout hooks and native
85
+ exceptions are documented in the
86
+ [maintainer guide](https://github.com/with-logic/elwood/blob/main/.github/secret-scanning.md).
@@ -1,2 +0,0 @@
1
- export declare function claudeModelComposerClearance(frame: string): boolean;
2
- //# sourceMappingURL=model-composer.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"model-composer.d.ts","sourceRoot":"","sources":["../../src/claude/model-composer.ts"],"names":[],"mappings":"AAGA,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAOnE"}
@@ -1,12 +0,0 @@
1
- /** The last native Claude composer proves post-picker clearance (C-API-55). */
2
- import { claudeTrustClearance } from "./screen-table.js";
3
- export function claudeModelComposerClearance(frame) {
4
- if (/esc to interrupt/i.test(frame))
5
- return false;
6
- const rows = frame.split("\n");
7
- const composer = rows.findLastIndex((row) => /^\s*❯/.test(row));
8
- // /model is retained as an earlier transcript caret after Escape. Only the last
9
- // composer, its enclosing rules, and its own native footer establish clearance.
10
- return composer > 0 && claudeTrustClearance(rows.slice(composer - 1).join("\n"));
11
- }
12
- //# sourceMappingURL=model-composer.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"model-composer.js","sourceRoot":"","sources":["../../src/claude/model-composer.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAEzD,MAAM,UAAU,4BAA4B,CAAC,KAAa;IACxD,IAAI,mBAAmB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAClD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAChE,gFAAgF;IAChF,gFAAgF;IAChF,OAAO,QAAQ,GAAG,CAAC,IAAI,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AACnF,CAAC"}
@@ -1,33 +0,0 @@
1
- /**
2
- * Bounded, self-yielding terminal drain for the transcript watcher (retire/finish).
3
- * Implements PRD §5.4/§9.2 (C-CLAUDE-15): flushing a stopped subagent (retire) or
4
- * the whole watcher at teardown (finish) shares ONE watcher-wide chunk budget
5
- * across every cursor AND every call, so aggregate terminal work is watcher-
6
- * bounded — a retire never gets a fresh full budget. Each call is bounded further
7
- * by a small wall-clock slice, so no single call runs a hundreds-of-MiB synchronous
8
- * loop that would stall the event loop. Any backlog still unread when the shared
9
- * budget OR the per-call slice is spent is accounted as a content-free drop, never
10
- * read in an unbounded synchronous loop, so the caller returns to the event loop
11
- * promptly and termination/retirement always completes in bounded work.
12
- */
13
- import type { TranscriptCursor } from "./cursor.ts";
14
- import type { DropReporter } from "./drops.ts";
15
- import type { LineEmitter } from "./emit.ts";
16
- /** A mutable chunk budget shared across every cursor drained across a watcher's life. */
17
- export type ChunkBudget = {
18
- chunks: number;
19
- };
20
- /** One shared budget for a watcher's whole terminal-drain life (retire + finish). */
21
- export declare function newTerminalBudget(): ChunkBudget;
22
- /** The watcher collaborators a drain needs: the fs guard, emitter, and drop sink. */
23
- export type DrainContext = {
24
- readFs<T>(path: string, read: () => T): T | undefined;
25
- readonly lines: LineEmitter;
26
- readonly drops: DropReporter;
27
- /** Per-call wall-clock ms cap (defaults to `drainSliceMs`); tests may override. */
28
- readonly sliceMs?: number;
29
- /** Monotonic clock (injectable for tests); defaults to `Date.now`. */
30
- readonly now?: () => number;
31
- };
32
- export declare function drainToBudget(context: DrainContext, cursors: readonly TranscriptCursor[], budget: ChunkBudget): void;
33
- //# sourceMappingURL=drain.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"drain.d.ts","sourceRoot":"","sources":["../../../src/claude/transcript/drain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C,yFAAyF;AACzF,MAAM,MAAM,WAAW,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAO7C,qFAAqF;AACrF,wBAAgB,iBAAiB,IAAI,WAAW,CAE/C;AAED,qFAAqF;AACrF,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;IACtD,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,mFAAmF;IACnF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,sEAAsE;IACtE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CAC7B,CAAC;AAOF,wBAAgB,aAAa,CAC3B,OAAO,EAAE,YAAY,EACrB,OAAO,EAAE,SAAS,gBAAgB,EAAE,EACpC,MAAM,EAAE,WAAW,GAClB,IAAI,CAYN"}