elanous 0.2.4 → 0.2.5

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 (412) hide show
  1. package/README.md +3 -3
  2. package/apps/pwa/out/404/index.html +1 -1
  3. package/apps/pwa/out/404.html +1 -1
  4. package/apps/pwa/out/_next/static/chunks/06ef2eba-d1b99ac6d2ea663c.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/1413-7179a49332db5a80.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/150-25c277137a9638ab.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/1640-bc99ec23bbe29d12.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/{2415.ed6428ff334c7ce2.js → 1751.50b96ea9c37b49b6.js} +1 -1
  9. package/apps/pwa/out/_next/static/chunks/2300-de059d0ab2a16630.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/2684-05b0a81e75298c16.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/3026-2ed7e317d9b02102.js +25 -0
  12. package/apps/pwa/out/_next/static/chunks/3611-77496ada38cd6358.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/3717-68957c4dc9ceec90.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/3929-46ba9af6f9d895cc.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/4015-62c5a3744698a8da.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/4217-3c4317a7905f9eeb.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/4240-3230f230fae2d7d5.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/4271-e53e075ebd48593b.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/4377.3b125fef79576dfd.js +5 -0
  20. package/apps/pwa/out/_next/static/chunks/4440-18bb160d95819f4e.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/4853-35fdbe6bdbcdc0fc.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/{2ea96958-e8c7e112de50b067.js → 4daa2798-1363d725c79f3593.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/5007-6a3b30e23ff68a6e.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/5244-12b424c42070f5ff.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/{5791-bb82743fa6299562.js → 5348-bbf838e119661eee.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/5391-8793f88b03b30161.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/5611-df00af3e4e4a61bb.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/5890-dca2321773be829a.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/6123-37c9dc57ddaa7ab0.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/6588-b7975304a302ab91.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/6604-c03961962b6273b6.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/7109-cf8c87e968e83a65.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/7178-30f77877525a2efb.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/7262-871563fc9663730b.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/7294-f0eeebb92aa93459.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/7429-881af44deed6958f.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/7527.943753b6ed829f09.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/764-a01e858525a190c0.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/7868-e35d5f7985ecd3f1.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/8320-51484af7b01fdfa5.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/{4323.c5dfb6f503304775.js → 8617.465ca22a319c95b3.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 8918-482b30ce2a8ebf4c.js} +3 -3
  43. package/apps/pwa/out/_next/static/chunks/9131-52f5fad51cceca04.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/9238.2cd5f47f4bf7a2c3.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/{6062-73e68343a9814f6a.js → 9704-3221c102623c8d73.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-70c404c7bb6d7f0f.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/approvals/page-3ffd80166b1050aa.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f89e9a303515a033.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/board/page-4f403cd060442c95.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-9fa879ebf5959096.js → page-0ac5d053f0444d21.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/bots/page-4bcc63e272ae8205.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/chat/page-880734bd07d62882.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/control/{page-4472ddf1e2458e69.js → page-72bd384898d88955.js} +1 -1
  54. package/apps/pwa/out/_next/static/chunks/app/design-check/page-1018bbc9586ecae5.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/editor/page-de86f8c38076dd7b.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/healthz/route-70c404c7bb6d7f0f.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/intake/page-cd6447196d8bafe6.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/layout-e733630c47fa102b.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/live/page-d1c1a8357b161875.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/market/page-60c514128e81d787.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8b637b7e325e13c1.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/morning/{page-112f7e477bda77fd.js → page-bccce0babceae30f.js} +1 -1
  63. package/apps/pwa/out/_next/static/chunks/app/not-found-63301d11dbe73836.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/observatory/page-a8cf0dc472060db2.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/page-02fba54881ccfa49.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/reflection/page-d5e8e90f0038f8de.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-b1ef6824f494466a.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/sessions/page-d589bb0f233b1009.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-c9bbaebef4cda059.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/settings/page-3639ce7916b92fed.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-7450ae7478955653.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/setup/layout-70c404c7bb6d7f0f.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/setup/page-b41ca4abfd618452.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/share/page-b333dd79ebee3fa9.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/showroom/page-8be1227492793655.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/tasks/page-6e03e9371b552e0d.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/term/page-911411e9fca18d03.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/trace/page-792170e649b37088.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/vault/page-c40463466937bc91.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-509c798b80384536.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/workflows/page-1cac2aa21d77c417.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/workspace/page-9626799d9b4a2cd1.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-84cd419494e16b0f.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/{8046cadf-211ca3c0e00f2b72.js → f6f3850d-7f324c6566002113.js} +1 -1
  85. package/apps/pwa/out/_next/static/chunks/framework-361487d2da603917.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/main-app-607f5beb94cb2e07.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/main-d0222d219e29dddf.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/pages/_app-a26980a31126decc.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/pages/_error-edb2b37decf23e4d.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/{webpack-e5bea36f183b25e6.js → webpack-2bf59c720fe9542c.js} +1 -1
  91. package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +1 -0
  92. package/apps/pwa/out/_next/static/{zoQX5iTvXOsIvCjxCe1Ji → ktMqfrBbf8BMGac0jqLrb}/_buildManifest.js +1 -1
  93. package/apps/pwa/out/approvals/index.html +1 -1
  94. package/apps/pwa/out/approvals/index.txt +29 -27
  95. package/apps/pwa/out/autopilot/index.html +1 -1
  96. package/apps/pwa/out/autopilot/index.txt +29 -27
  97. package/apps/pwa/out/board/index.html +1 -1
  98. package/apps/pwa/out/board/index.txt +29 -27
  99. package/apps/pwa/out/botlab/index.html +1 -1
  100. package/apps/pwa/out/botlab/index.txt +26 -24
  101. package/apps/pwa/out/bots/index.html +1 -1
  102. package/apps/pwa/out/bots/index.txt +26 -24
  103. package/apps/pwa/out/chat/index.html +1 -1
  104. package/apps/pwa/out/chat/index.txt +26 -24
  105. package/apps/pwa/out/control/index.html +1 -1
  106. package/apps/pwa/out/control/index.txt +29 -27
  107. package/apps/pwa/out/design-check/index.html +1 -1
  108. package/apps/pwa/out/design-check/index.txt +29 -27
  109. package/apps/pwa/out/editor/index.html +1 -1
  110. package/apps/pwa/out/editor/index.txt +29 -27
  111. package/apps/pwa/out/healthz +1 -1
  112. package/apps/pwa/out/icons/elanous-16.png +0 -0
  113. package/apps/pwa/out/icons/elanous-180.png +0 -0
  114. package/apps/pwa/out/icons/elanous-192.png +0 -0
  115. package/apps/pwa/out/icons/elanous-32.png +0 -0
  116. package/apps/pwa/out/icons/elanous-512.png +0 -0
  117. package/apps/pwa/out/index.html +1 -1
  118. package/apps/pwa/out/index.txt +26 -24
  119. package/apps/pwa/out/intake/index.html +1 -1
  120. package/apps/pwa/out/intake/index.txt +29 -27
  121. package/apps/pwa/out/live/index.html +1 -1
  122. package/apps/pwa/out/live/index.txt +29 -27
  123. package/apps/pwa/out/manifest.webmanifest +5 -2
  124. package/apps/pwa/out/market/index.html +1 -1
  125. package/apps/pwa/out/market/index.txt +26 -24
  126. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  127. package/apps/pwa/out/missions/placeholder/index.txt +26 -24
  128. package/apps/pwa/out/morning/index.html +1 -1
  129. package/apps/pwa/out/morning/index.txt +29 -27
  130. package/apps/pwa/out/observatory/index.html +1 -1
  131. package/apps/pwa/out/observatory/index.txt +29 -27
  132. package/apps/pwa/out/reflection/index.html +1 -1
  133. package/apps/pwa/out/reflection/index.txt +27 -25
  134. package/apps/pwa/out/scheduler/index.html +1 -1
  135. package/apps/pwa/out/scheduler/index.txt +29 -27
  136. package/apps/pwa/out/sessions/index.html +1 -1
  137. package/apps/pwa/out/sessions/index.txt +26 -24
  138. package/apps/pwa/out/settings/devices/index.html +1 -1
  139. package/apps/pwa/out/settings/devices/index.txt +29 -27
  140. package/apps/pwa/out/settings/index.html +1 -1
  141. package/apps/pwa/out/settings/index.txt +29 -27
  142. package/apps/pwa/out/setup/done/index.html +1 -1
  143. package/apps/pwa/out/setup/done/index.txt +29 -27
  144. package/apps/pwa/out/setup/index.html +1 -1
  145. package/apps/pwa/out/setup/index.txt +29 -27
  146. package/apps/pwa/out/share/index.html +1 -1
  147. package/apps/pwa/out/share/index.txt +29 -27
  148. package/apps/pwa/out/showroom/index.html +1 -1
  149. package/apps/pwa/out/showroom/index.txt +26 -24
  150. package/apps/pwa/out/sw-strategies.js +44 -0
  151. package/apps/pwa/out/sw.js +6 -44
  152. package/apps/pwa/out/tasks/index.html +1 -1
  153. package/apps/pwa/out/tasks/index.txt +29 -27
  154. package/apps/pwa/out/term/index.html +1 -1
  155. package/apps/pwa/out/term/index.txt +29 -27
  156. package/apps/pwa/out/trace/index.html +1 -1
  157. package/apps/pwa/out/trace/index.txt +29 -27
  158. package/apps/pwa/out/vault/index.html +1 -1
  159. package/apps/pwa/out/vault/index.txt +29 -27
  160. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  161. package/apps/pwa/out/workflows/chat-ui/index.txt +29 -27
  162. package/apps/pwa/out/workflows/index.html +1 -1
  163. package/apps/pwa/out/workflows/index.txt +29 -27
  164. package/apps/pwa/out/workspace/index.html +1 -1
  165. package/apps/pwa/out/workspace/index.txt +29 -27
  166. package/apps/pwa/out/worktrees/index.html +1 -1
  167. package/apps/pwa/out/worktrees/index.txt +29 -27
  168. package/catalog/resources.yaml +6 -6
  169. package/docs/goal-context/steward-directives-2026-09-28.md +35 -37
  170. package/graphs/landing/landing-heal.yaml +1 -1
  171. package/graphs/release/recipes.yaml +8 -1
  172. package/graphs/release/release-loop.yaml +6 -1
  173. package/graphs/video/README.md +1 -0
  174. package/graphs/video/explainer-line.yaml +58 -0
  175. package/package.json +3 -1
  176. package/plugins/job-coach/.codex-plugin/plugin.json +18 -1
  177. package/plugins/job-coach/README.md +12 -0
  178. package/plugins/job-coach/connectors/ncs/server.ts +4 -3
  179. package/plugins/job-coach/graphs/gap-judge.ts +44 -0
  180. package/plugins/job-coach/graphs/recipes.yaml +27 -0
  181. package/plugins/job-coach/graphs/report-enterprise.yaml +28 -0
  182. package/plugins/job-coach/graphs/run-step.ts +211 -17
  183. package/plugins/job-coach/plugin.json +16 -3
  184. package/plugins/job-coach/skills/enterprise-ai-fit/SKILL.md +8 -0
  185. package/plugins/job-coach/skills/enterprise-competency/SKILL.md +8 -0
  186. package/plugins/job-coach/skills/enterprise-needs/SKILL.md +8 -0
  187. package/plugins/job-coach/skills/enterprise-roadmap/SKILL.md +8 -0
  188. package/plugins/job-coach/skills/enterprise-task-analysis/SKILL.md +8 -0
  189. package/plugins/job-coach/skills/interview-to-profile/SKILL.md +18 -1
  190. package/scripts/backup/elanous-backup.sh +4 -1
  191. package/scripts/build-docs-site.ts +4 -3
  192. package/scripts/coord-channel-watch.sh +23 -4
  193. package/scripts/coord-post.sh +49 -12
  194. package/scripts/coord-tracks.json +5 -5
  195. package/scripts/eval-bot-vs-grok.ts +79 -0
  196. package/scripts/eval-grok-live-baseline.ts +36 -0
  197. package/scripts/fixtures/front-route-corpus.sample.json +17 -0
  198. package/scripts/fleet/node-setup.sh +46 -7
  199. package/scripts/landing-heal/must-fix-match.ts +26 -0
  200. package/scripts/landing-heal/run-mission.ts +114 -20
  201. package/scripts/lib/front-route-measurement.ts +131 -0
  202. package/scripts/lib/tui-sim-keys.ts +42 -0
  203. package/scripts/measure-front-route-classifier.ts +56 -0
  204. package/scripts/media/ocr-leak-scan.sh +21 -0
  205. package/scripts/public-export.ts +2 -3
  206. package/scripts/public-import-pr.ts +80 -6
  207. package/scripts/publish-internal-dist.ts +2 -3
  208. package/scripts/red-sweep-diff.ts +1 -1
  209. package/scripts/release-loop/auto-approve-node.ts +72 -0
  210. package/scripts/release-loop/gate-node.ts +332 -44
  211. package/scripts/release-loop/publish-node.ts +7 -3
  212. package/scripts/release-loop/release-docs.ts +20 -13
  213. package/scripts/release-loop/test-durations.ts +63 -0
  214. package/scripts/release-loop/upgrade-node.ts +3 -2
  215. package/scripts/test-audit-coverage-per-file.sh +16 -0
  216. package/scripts/test-audit-meaningless.ts +88 -0
  217. package/scripts/test-audit-overlap-and-weak.ts +73 -0
  218. package/scripts/test-deterministic.ts +27 -5
  219. package/scripts/tui-sim.ts +59 -14
  220. package/scripts/ux/bump-installed.sh +20 -0
  221. package/scripts/ux/lib/cdp.ts +72 -0
  222. package/scripts/ux/record-page.ts +50 -0
  223. package/skills/explainer-video/SKILL.md +99 -0
  224. package/skills/explainer-video/engine/brand/elanous-mark-on-dark.svg +1 -0
  225. package/skills/explainer-video/engine/build.mjs +431 -0
  226. package/skills/explainer-video/engine/vo.ts +37 -0
  227. package/skills/explainer-video/examples/elanous-0.2.5/script.json +155 -0
  228. package/src/agent/nested-agent-env.ts +1 -0
  229. package/src/agent-mission/driver.ts +95 -16
  230. package/src/agent-mission/mission-cli.ts +6 -0
  231. package/src/agent-mission/resource-ladder.ts +73 -25
  232. package/src/auth/acp-token.ts +16 -0
  233. package/src/auth/github-app-token.ts +112 -0
  234. package/src/autopilot/gh-rest-fallback.ts +177 -0
  235. package/src/autopilot/pr-manager.ts +51 -4
  236. package/src/boot/acp-server.ts +2 -16
  237. package/src/boot/daemon-tools/web-search.ts +2 -1
  238. package/src/boot/ws-bridge.ts +11 -0
  239. package/src/chat/index.ts +37 -8
  240. package/src/chat/log-scroll-key.ts +12 -0
  241. package/src/chat/submit-trace.ts +30 -0
  242. package/src/cli/agent-cli.ts +7 -2
  243. package/src/cli/decisions-cli.ts +94 -0
  244. package/src/cli/doctor-cli.ts +40 -54
  245. package/src/cli/doctor-readiness.ts +16 -24
  246. package/src/cli/intake-cli.ts +1 -1
  247. package/src/cli/mcp-call.ts +9 -0
  248. package/src/cli/mcp-gateway-cli.ts +7 -3
  249. package/src/cli/plugin-cli.ts +109 -11
  250. package/src/cli/pr-lineage.ts +2 -1
  251. package/src/cli/pwa-start.ts +3 -0
  252. package/src/cli/release-cli.ts +94 -7
  253. package/src/cli/self-update.ts +7 -4
  254. package/src/cli/start-cli.ts +33 -14
  255. package/src/dashboard/acp-boot.ts +3 -0
  256. package/src/dashboard/index.ts +62 -13
  257. package/src/dashboard/input/no-provider-reply.ts +13 -0
  258. package/src/dashboard/slash-runtime/unknown-slash-reply.ts +78 -0
  259. package/src/dashboard/stale-install-check.ts +77 -0
  260. package/src/decisions/decision-ledger.ts +404 -0
  261. package/src/domains/outbound-alert.ts +8 -2
  262. package/src/domains/telegram-kind-route.ts +21 -0
  263. package/src/execution-loop/dispatch-task.ts +139 -0
  264. package/src/execution-loop/gate-decision-summary.ts +29 -0
  265. package/src/execution-loop/gates.ts +197 -0
  266. package/src/graph-kinds/installed-plugin-nodes.ts +95 -0
  267. package/src/graph-kinds/plugin-nodes.ts +10 -2
  268. package/src/graph-runner/runner.ts +35 -5
  269. package/src/grounding/token.ts +12 -6
  270. package/src/harness/harness-cli-command.ts +68 -9
  271. package/src/harness/harness-pod-dispatch.ts +8 -2
  272. package/src/harness/harness-stop.ts +147 -18
  273. package/src/harness/self-send-decide.ts +26 -17
  274. package/src/harness/self-send-target.ts +22 -1
  275. package/src/harness/worktree-branch-prefix.ts +4 -3
  276. package/src/index.ts +56 -13
  277. package/src/instance/resolve.ts +6 -0
  278. package/src/intake-plane/collect-telegram-saved.ts +5 -0
  279. package/src/intake-plane/digest.ts +11 -2
  280. package/src/live/detail-switch.ts +8 -3
  281. package/src/llm/usable-llm.ts +32 -0
  282. package/src/loops/registry.ts +27 -5
  283. package/src/market/publish.ts +59 -3
  284. package/src/native-tool-catalog.ts +2 -2
  285. package/src/nexus/api/graph-approvals.ts +125 -0
  286. package/src/nexus/api/http-server.ts +60 -6
  287. package/src/nexus/api/log-fabric.ts +10 -1
  288. package/src/nexus/api/mcp-oauth-hint.ts +10 -9
  289. package/src/nexus/api/merge-approvals.ts +2 -1
  290. package/src/nexus/api/meta-api.ts +14 -0
  291. package/src/nexus/api/plugins-credentials-api.ts +47 -0
  292. package/src/nexus/api/plugins-market.ts +87 -3
  293. package/src/nexus/api/pod-credential-api.ts +68 -14
  294. package/src/nexus/api/public-routes.ts +7 -0
  295. package/src/nexus/api/trace.ts +57 -6
  296. package/src/nexus/boot/register-mcp-clients.ts +99 -14
  297. package/src/nexus/index.ts +33 -32
  298. package/src/nexus/outbound/router.ts +1 -1
  299. package/src/onboarding.ts +25 -2
  300. package/src/plugins/core/host.ts +14 -0
  301. package/src/plugins/install/market-fetch.ts +4 -3
  302. package/src/plugins/install/plugin-credentials.ts +106 -0
  303. package/src/plugins/install/plugin-install.ts +163 -32
  304. package/src/prompt-library/universal-preamble.ts +8 -2
  305. package/src/release-loop/checklist.ts +191 -0
  306. package/src/release-loop/release-note.ts +6 -3
  307. package/src/self-dev/dev-cli.ts +40 -20
  308. package/src/self-dev/dev-pipeline.ts +77 -1
  309. package/src/self-implement/gate-baseline.ts +3 -1
  310. package/src/self-implement/goal-author.ts +18 -1
  311. package/src/self-implement/host-regate.ts +6 -2
  312. package/src/self-implement/orchestrator.ts +7 -2
  313. package/src/self-implement/seams.ts +4 -2
  314. package/src/skills/tools/dev-harness.ts +42 -0
  315. package/src/steward/steward-cards.ts +45 -0
  316. package/src/steward/triage.ts +50 -14
  317. package/src/task-orchestrator/surfaces/pod-artifact-return.ts +61 -3
  318. package/src/task-orchestrator/surfaces/pod-command-job.ts +27 -6
  319. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +39 -9
  320. package/src/task-orchestrator/surfaces/pod-source-bundle.ts +16 -1
  321. package/src/task-orchestrator/surfaces/pod-source-receive.ts +51 -13
  322. package/src/task-orchestrator/surfaces/self-implement-pod.ts +172 -28
  323. package/src/telegram-report.ts +12 -6
  324. package/src/tui-client/acp-transport-ws-client.ts +72 -5
  325. package/src/tui.ts +3 -2
  326. package/src/user-config.ts +28 -2
  327. package/src/util/spawn-sync-output.ts +15 -0
  328. package/src/version/code-revision.ts +1 -1
  329. package/src/version/packed-revision.json +1 -1
  330. package/src/web-search/free.ts +77 -0
  331. package/src/web-search/index.ts +2 -0
  332. package/src/workflow-runtime/deps-bridge.ts +4 -3
  333. package/src/workflow-runtime/plugin-kind-node.ts +32 -4
  334. package/src/workflow-runtime/types.ts +2 -1
  335. package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/1191-7b13a24117a689fa.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/2375-3fcae5186c64fe6b.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +0 -25
  346. package/apps/pwa/out/_next/static/chunks/319-d37df1b6e0cbeff4.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +0 -5
  348. package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
  349. package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +0 -1
  350. package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +0 -1
  351. package/apps/pwa/out/_next/static/chunks/4836-3c93795ab834381e.js +0 -1
  352. package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +0 -1
  353. package/apps/pwa/out/_next/static/chunks/6090-1bd6077591603947.js +0 -1
  354. package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +0 -1
  355. package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
  356. package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +0 -1
  357. package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +0 -1
  358. package/apps/pwa/out/_next/static/chunks/7457-f5cb1cc8db9f7453.js +0 -1
  359. package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +0 -1
  360. package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +0 -1
  361. package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +0 -1
  362. package/apps/pwa/out/_next/static/chunks/8067-f74bf14d67ad98f1.js +0 -1
  363. package/apps/pwa/out/_next/static/chunks/8142-011e1f474efb8b33.js +0 -1
  364. package/apps/pwa/out/_next/static/chunks/8519-0059d82bdb4812bf.js +0 -1
  365. package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +0 -1
  366. package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +0 -1
  367. package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +0 -1
  368. package/apps/pwa/out/_next/static/chunks/9740-52467f244755917f.js +0 -1
  369. package/apps/pwa/out/_next/static/chunks/9804-64c02c3c127d5014.js +0 -1
  370. package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +0 -1
  371. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +0 -1
  372. package/apps/pwa/out/_next/static/chunks/app/approvals/page-21c3261af6ab377c.js +0 -1
  373. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-1991445b8ea86274.js +0 -1
  374. package/apps/pwa/out/_next/static/chunks/app/board/page-4ebfaee2369fe248.js +0 -1
  375. package/apps/pwa/out/_next/static/chunks/app/bots/page-ca7c3cc305c3285a.js +0 -1
  376. package/apps/pwa/out/_next/static/chunks/app/chat/page-9951930add3d2051.js +0 -1
  377. package/apps/pwa/out/_next/static/chunks/app/design-check/page-4de08d4b766f134b.js +0 -1
  378. package/apps/pwa/out/_next/static/chunks/app/editor/page-7a975e2b9db1dfb7.js +0 -1
  379. package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +0 -1
  380. package/apps/pwa/out/_next/static/chunks/app/intake/page-49270714ef35bb96.js +0 -1
  381. package/apps/pwa/out/_next/static/chunks/app/layout-33fbdf353e2a5982.js +0 -1
  382. package/apps/pwa/out/_next/static/chunks/app/live/page-259f122e2dd64317.js +0 -1
  383. package/apps/pwa/out/_next/static/chunks/app/market/page-65b82726753658e1.js +0 -1
  384. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-4b8df3748dc06285.js +0 -1
  385. package/apps/pwa/out/_next/static/chunks/app/not-found-542a5c0006da637c.js +0 -1
  386. package/apps/pwa/out/_next/static/chunks/app/observatory/page-63ffb347831262ea.js +0 -1
  387. package/apps/pwa/out/_next/static/chunks/app/page-98e4e359d97263b4.js +0 -1
  388. package/apps/pwa/out/_next/static/chunks/app/reflection/page-97e24ac675ce576e.js +0 -1
  389. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-ac6391f26391d89d.js +0 -1
  390. package/apps/pwa/out/_next/static/chunks/app/sessions/page-9f45c9d45ffc43bd.js +0 -1
  391. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-36995b9cb5d98218.js +0 -1
  392. package/apps/pwa/out/_next/static/chunks/app/settings/page-0c795b577988d63f.js +0 -1
  393. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b4311e2f71c6735a.js +0 -1
  394. package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +0 -1
  395. package/apps/pwa/out/_next/static/chunks/app/setup/page-eb2381074df20dba.js +0 -1
  396. package/apps/pwa/out/_next/static/chunks/app/share/page-b01336e64eba01ae.js +0 -1
  397. package/apps/pwa/out/_next/static/chunks/app/showroom/page-7af7f21c1f2858ef.js +0 -1
  398. package/apps/pwa/out/_next/static/chunks/app/tasks/page-a98ecd364586c89b.js +0 -1
  399. package/apps/pwa/out/_next/static/chunks/app/term/page-57f0502dfbfa089b.js +0 -1
  400. package/apps/pwa/out/_next/static/chunks/app/trace/page-3467d2e1abd8a14a.js +0 -1
  401. package/apps/pwa/out/_next/static/chunks/app/vault/page-7842ca23d53ba682.js +0 -1
  402. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-ae9ac882912b0198.js +0 -1
  403. package/apps/pwa/out/_next/static/chunks/app/workflows/page-3a0d72e3f783bcef.js +0 -1
  404. package/apps/pwa/out/_next/static/chunks/app/workspace/page-f3b39fe8358c2a97.js +0 -1
  405. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-5a22af3181ac2afb.js +0 -1
  406. package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +0 -1
  407. package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +0 -1
  408. package/apps/pwa/out/_next/static/chunks/main-app-1457627bb302a22c.js +0 -1
  409. package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +0 -1
  410. package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +0 -1
  411. package/apps/pwa/out/_next/static/css/e66b7f5efc8df3f3.css +0 -1
  412. /package/apps/pwa/out/_next/static/{zoQX5iTvXOsIvCjxCe1Ji → ktMqfrBbf8BMGac0jqLrb}/_ssgManifest.js +0 -0
@@ -3,25 +3,106 @@
3
3
  // 🩸 2026-09-27: 멈추려면 사람이 PID 둘(harness ask 부모 · self orchestrate 자식)과 Job 이름을 손으로 찾아야 했고,
4
4
  // 그 사이 떠 있던 조각이 main 에 병합됐다(#21048). 부모를 죽이면 자식이 PPID 1 고아로 2h36m 더 돌았다(🅞 보고).
5
5
  // B 조각(#21171)이 오케스트레이터에 `self-dev-runs/<runId>/pid.json` ⊕ SIGTERM 정리(새 잡 멈춤 · 자기 Job 삭제 · cancelled)를 넣었다.
6
- // 이 파일(A 조각)은 그 기록을 «읽고», 소유가 확인될 때만 신호를 보내고, `elanous.run=<runId>` 라벨 Job 을 지운다.
6
+ // 이 파일은 그 기록과 런 원장·프로세스 표를 읽고, 시작 시각이 확인된 후보에만 신호를 보내며, `elanous.run=<runId>` 라벨 Job 을 지운다.
7
7
  //
8
8
  // ⛔ 불변식: 기록된 시작 시각과 실제 프로세스 시작 시각이 다르면 신호를 보내지 않는다(PID 재사용 방지).
9
9
  // ⛔ 불변식: `elanous.run=<runId>` 라벨이 붙은 Job 만 지운다.
10
10
 
11
- import { readFileSync } from 'node:fs';
12
- import { join } from 'node:path';
11
+ import { readFileSync, readlinkSync } from 'node:fs';
12
+ import { isAbsolute, join, resolve } from 'node:path';
13
13
  import { spawnSync } from 'node:child_process';
14
14
  import { debug } from '../debug/log.js';
15
15
  import { selfDevRunsDir } from '../self-dev/run-store.js';
16
+ import { loadRunLedger, runLedgerDir } from '../self-implement/run-ledger.js';
16
17
 
17
18
  const RUN_ID = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
18
19
  /** `pid.json` 의 `startedAt` 은 `Date.now() − uptime` 근삿값이다 — 이만큼의 차이는 같은 프로세스로 본다. */
19
20
  export const START_TOLERANCE_MS = 5_000;
20
21
 
21
22
  export interface PidRecord { pid: number; startedAt: number; argv0?: string }
23
+ export interface StopProcessCandidate { pid: number; startedAt: number; via: 'pid.json' | 'argv-runId' | 'argv-goal' }
24
+ export type StopProcessScan = { status: 'ok'; candidates: StopProcessCandidate[] } | { status: 'failed'; reason: string };
25
+
26
+ /** The start event, not later bookkeeping, owns the goal document path. */
27
+ export function stopGoalPathFromLedger(runId: string, dir = runLedgerDir()): string | null {
28
+ try {
29
+ const start = loadRunLedger(runId, dir)?.find((entry) => entry.event === 'start');
30
+ const goal = start?.data.goalFile;
31
+ const root = start?.data.targetRoot;
32
+ if (typeof goal !== 'string' || !goal.trim()) return null;
33
+ return isAbsolute(goal) ? resolve(goal) : typeof root === 'string' && isAbsolute(root) ? resolve(root, goal) : goal;
34
+ } catch { return null; }
35
+ }
36
+
37
+ /** Parse ps rows without treating a partial or failed measurement as an empty table. Relative goal arguments require the candidate's own cwd, never the stop caller's cwd. */
38
+ export function discoverStopProcesses(runId: string, goalPath: string | null, table: string, processCwd: (pid: number) => string | null = () => null): StopProcessScan {
39
+ const candidates: StopProcessCandidate[] = [];
40
+ for (const line of table.split(/\r?\n/)) {
41
+ if (!line.trim() || /^\s*PID\s/.test(line)) continue;
42
+ const match = /^\s*(\d+)\s+((?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s+\w+\s+\d+\s+\d\d:\d\d:\d\d\s+\d{4})\s+(.+)$/.exec(line);
43
+ if (!match) return { status: 'failed', reason: 'unparseable process table row' };
44
+ const pid = Number(match[1]);
45
+ const startedAt = Date.parse(match[2]!);
46
+ if (!Number.isSafeInteger(pid) || !Number.isFinite(startedAt)) return { status: 'failed', reason: 'invalid process table row' };
47
+ if (pid <= 1) continue;
48
+ const args = match[3]!.match(/"[^"]*"|'[^']*'|\S+/g)?.map((part) => part.replace(/^(['"])(.*)\1$/, '$2')) ?? [];
49
+ const entry = args.findIndex((arg) => /(?:^|\/)elanous(?:\.mjs)?$/.test(arg));
50
+ const executable = args[0] ?? '';
51
+ const runtime = /(?:^|\/)(?:bun|node)$/.test(executable);
52
+ if (entry < 0 || (entry !== 0 && (!runtime || entry !== 1))) continue;
53
+ const command = args.slice(entry + 1);
54
+ const launcher = command[0] === 'harness' && ['ask', 'say'].includes(command[1] ?? '')
55
+ || command[0] === 'dev' && command.some((arg) => arg === '--ask' || arg === '--say' || arg.startsWith('--ask=') || arg.startsWith('--say='))
56
+ || command[0] === 'self' && command[1] === 'orchestrate';
57
+ if (!launcher) continue;
58
+ const namedRunIds = command.flatMap((arg, i) => arg === '--run-id' || arg === '--runId'
59
+ ? [command[i + 1] ?? '']
60
+ : arg.startsWith('--run-id=') || arg.startsWith('--runId=') ? [arg.slice(arg.indexOf('=') + 1)] : []);
61
+ const runMatch = namedRunIds.includes(runId) || command.includes(runId);
62
+ // An explicitly different run takes precedence over a shared goal document.
63
+ if (namedRunIds.some((id) => id !== runId)) continue;
64
+ // A goal path is a positional or option value, not text embedded in a prompt.
65
+ const goalArgs = command.flatMap((arg, i) => {
66
+ if (arg === '--ask' || arg === '--say' || arg === '--goal-file' || arg === '--goal') return [command[i + 1] ?? ''];
67
+ if (arg.startsWith('--ask=') || arg.startsWith('--say=') || arg.startsWith('--goal-file=') || arg.startsWith('--goal=')) return [arg.slice(arg.indexOf('=') + 1)];
68
+ if (i > 0 && (command[i - 1] === '--ask' || command[i - 1] === '--say' || command[i - 1] === '--goal-file' || command[i - 1] === '--goal')) return [];
69
+ if (command[0] === 'harness' && i === 2 && (command[1] === 'ask' || command[1] === 'say')) return [arg];
70
+ if (command[0] === 'self' && command[1] === 'orchestrate' && i === 2) return [arg];
71
+ return [];
72
+ });
73
+ const goalMatch = goalPath !== null && goalArgs.some((arg) => {
74
+ if (isAbsolute(arg)) return isAbsolute(goalPath) && resolve(arg) === resolve(goalPath);
75
+ if (!isAbsolute(goalPath)) return false;
76
+ let cwd: string | null;
77
+ try { cwd = processCwd(pid); } catch { return false; }
78
+ return cwd !== null && isAbsolute(cwd) && resolve(cwd, arg) === resolve(goalPath);
79
+ });
80
+ if (runMatch || goalMatch) candidates.push({ pid, startedAt, via: runMatch ? 'argv-runId' : 'argv-goal' });
81
+ }
82
+ return { status: 'ok', candidates };
83
+ }
84
+
85
+ function stopProcessCwd(pid: number): string | null {
86
+ try {
87
+ if (process.platform === 'linux') return readlinkSync(`/proc/${pid}/cwd`);
88
+ const r = spawnSync('lsof', ['-a', '-p', String(pid), '-d', 'cwd', '-Fn'], { encoding: 'utf8', timeout: 5_000 });
89
+ if (r.error || r.status !== 0 || typeof r.stdout !== 'string') return null;
90
+ return r.stdout.split(/\r?\n/).find((line) => line.startsWith('n/'))?.slice(1) ?? null;
91
+ } catch { return null; }
92
+ }
93
+
94
+ function scanStopProcessTable(runId: string, goalPath: string | null): StopProcessScan {
95
+ let r: ReturnType<typeof spawnSync>;
96
+ try { r = spawnSync('ps', ['-eo', 'pid=,lstart=,args='], { encoding: 'utf8', timeout: 5_000, maxBuffer: 16 * 1024 * 1024 }); }
97
+ catch (error) { return { status: 'failed', reason: String(error) }; }
98
+ if (r.error || r.status !== 0 || typeof r.stdout !== 'string') return { status: 'failed', reason: String(r.error ?? r.stderr ?? `ps status ${r.status}`) };
99
+ return discoverStopProcesses(runId, goalPath, r.stdout, stopProcessCwd);
100
+ }
22
101
 
23
102
  export interface HarnessStopDeps {
24
103
  readPidRecord: (runId: string) => PidRecord | null;
104
+ readGoalPath: (runId: string) => string | null;
105
+ scanProcesses: (runId: string, goalPath: string | null) => StopProcessScan;
25
106
  /** 살아 있는 프로세스의 시작 시각(ms) · 없으면 null. */
26
107
  processStartMs: (pid: number) => number | null;
27
108
  kill: (pid: number, signal: NodeJS.Signals) => void;
@@ -34,8 +115,12 @@ export interface HarnessStopDeps {
34
115
 
35
116
  export interface HarnessStopResult {
36
117
  runId: string;
37
- process: 'stopped' | 'killed' | 'absent' | 'owner-mismatch';
118
+ process: 'stopped' | 'killed' | 'absent' | 'owner-mismatch' | 'unmeasured' | 'dry-run';
38
119
  pid?: number;
120
+ candidates: StopProcessCandidate[];
121
+ scanned: boolean;
122
+ pidRecordFound: boolean;
123
+ dryRun: boolean;
39
124
  jobsDeleted: number;
40
125
  contexts: string[];
41
126
  }
@@ -69,6 +154,8 @@ export function defaultHarnessStopDeps(overrides: Partial<HarnessStopDeps> = {})
69
154
  };
70
155
  return {
71
156
  readPidRecord: (runId) => readPidRecordFrom(selfDevRunsDir(), runId),
157
+ readGoalPath: stopGoalPathFromLedger,
158
+ scanProcesses: scanStopProcessTable,
72
159
  processStartMs: psProcessStartMs,
73
160
  kill: (pid, signal) => { process.kill(pid, signal); },
74
161
  sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
@@ -83,49 +170,91 @@ export function defaultHarnessStopDeps(overrides: Partial<HarnessStopDeps> = {})
83
170
  };
84
171
  }
85
172
 
86
- export async function stopHarnessRun(runId: string, deps: HarnessStopDeps, contexts?: readonly string[]): Promise<HarnessStopResult> {
173
+ export async function stopHarnessRun(runId: string, deps: HarnessStopDeps, contexts?: readonly string[], dryRun = false): Promise<HarnessStopResult> {
87
174
  if (!RUN_ID.test(runId)) throw new Error(`invalid run id: ${runId}`);
88
175
  // ── ⓐ 프로세스 ────────────────────────────────────────────
89
176
  let processState: HarnessStopResult['process'] = 'absent';
90
177
  const record = deps.readPidRecord(runId);
178
+ let scan: StopProcessScan;
179
+ let goalPath: string | null = null;
180
+ try { goalPath = deps.readGoalPath(runId); }
181
+ catch { /* 원장 판독 실패 — runId 단독 대조 */ }
182
+ try { scan = deps.scanProcesses(runId, goalPath); }
183
+ catch (error) { scan = { status: 'failed', reason: String(error) }; }
184
+ const candidates: StopProcessCandidate[] = [];
185
+ const seen = new Set<number>();
186
+ const add = (candidate: StopProcessCandidate) => {
187
+ if (!seen.has(candidate.pid)) { seen.add(candidate.pid); candidates.push(candidate); }
188
+ };
189
+ let recordMismatch = false;
91
190
  if (record) {
92
- const actual = deps.processStartMs(record.pid);
93
- if (actual === null) processState = 'absent';
94
- else if (Math.abs(actual - record.startedAt) > START_TOLERANCE_MS) processState = 'owner-mismatch';
95
- else {
96
- try { deps.kill(record.pid, 'SIGTERM'); } catch { /* 방금 끝났다 */ }
191
+ const recordedStart = deps.processStartMs(record.pid);
192
+ if (recordedStart !== null && Math.abs(recordedStart - record.startedAt) <= START_TOLERANCE_MS) {
193
+ add({ pid: record.pid, startedAt: recordedStart, via: 'pid.json' });
194
+ } else if (recordedStart !== null) { processState = 'owner-mismatch'; recordMismatch = true; }
195
+ }
196
+ if (scan.status === 'ok') {
197
+ for (const candidate of scan.candidates) {
198
+ if (candidate.pid !== record?.pid || !recordMismatch) add(candidate);
199
+ }
200
+ }
201
+ debug.log('harness.stop', 'process-candidates', { runId, found: candidates.map(({ via, pid }) => ({ via, pid })), scanned: scan.status === 'ok' });
202
+ if (dryRun) processState = 'dry-run';
203
+ else {
204
+ const verified: StopProcessCandidate[] = [];
205
+ for (const candidate of candidates) {
206
+ if (candidate.via === 'pid.json') { verified.push(candidate); continue; }
207
+ const actual = deps.processStartMs(candidate.pid);
208
+ if (actual === null) continue;
209
+ if (Math.abs(actual - candidate.startedAt) > START_TOLERANCE_MS) { processState = 'owner-mismatch'; continue; }
210
+ verified.push(candidate);
211
+ }
212
+ for (const candidate of verified) {
213
+ try { deps.kill(candidate.pid, 'SIGTERM'); } catch { /* 방금 끝났다 */ }
214
+ }
215
+ for (const candidate of verified) {
97
216
  processState = 'stopped';
98
217
  const step = 250;
99
218
  for (let waited = 0; waited < deps.graceMs; waited += step) {
100
219
  await deps.sleep(step);
101
- if (deps.processStartMs(record.pid) === null) break;
220
+ if (deps.processStartMs(candidate.pid) === null) break;
102
221
  }
103
- const still = deps.processStartMs(record.pid);
104
- if (still !== null && Math.abs(still - record.startedAt) <= START_TOLERANCE_MS) {
105
- try { deps.kill(record.pid, 'SIGKILL'); processState = 'killed'; } catch { /* 끝났다 */ }
222
+ const still = deps.processStartMs(candidate.pid);
223
+ const expectedStart = candidate.via === 'pid.json' ? record!.startedAt : candidate.startedAt;
224
+ if (still !== null && Math.abs(still - expectedStart) <= START_TOLERANCE_MS) {
225
+ try { deps.kill(candidate.pid, 'SIGKILL'); processState = 'killed'; } catch { /* 끝났다 */ }
106
226
  }
107
227
  }
228
+ if (processState === 'absent' && (scan.status === 'failed' || candidates.length > 0)) processState = 'unmeasured';
108
229
  }
109
230
  // ── ⓑ Pod Job (라벨로만) ───────────────────────────────────
110
- const targets = contexts && contexts.length > 0 ? [...contexts] : deps.kubeContexts();
231
+ const targets = dryRun ? [] : contexts && contexts.length > 0 ? [...contexts] : deps.kubeContexts();
111
232
  let jobsDeleted = 0;
112
233
  for (const context of targets) {
113
234
  const r = deps.kubectl(['--context', context, '-n', deps.namespace, 'delete', 'job', '-l', `elanous.run=${runId}`, '--wait=false']);
114
235
  if (r.status === 0) jobsDeleted += r.stdout.split('\n').filter((line) => line.startsWith('job.batch/')).length;
115
236
  else debug.log('harness.stop', 'job-delete-failed', { runId, context, error: r.stderr.slice(0, 200) }, { level: 'warn' });
116
237
  }
117
- const result: HarnessStopResult = { runId, process: processState, ...(record ? { pid: record.pid } : {}), jobsDeleted, contexts: targets };
238
+ const result: HarnessStopResult = { runId, process: processState, ...(record ? { pid: record.pid } : {}), candidates, scanned: scan.status === 'ok', pidRecordFound: record !== null, dryRun, jobsDeleted, contexts: targets };
118
239
  debug.log('harness.stop', 'stopped', { runId, process: processState, jobsDeleted, contexts: targets.length });
119
240
  return result;
120
241
  }
121
242
 
122
243
  export function formatHarnessStop(r: HarnessStopResult): string {
123
244
  const label: Record<HarnessStopResult['process'], string> = {
124
- stopped: '멈춤(SIGTERM)', killed: '멈춤(SIGKILL — 제한 시간 안에 안 끝났다)', absent: '없음', 'owner-mismatch': '소유 불일치(건드리지 않음 — PID 가 다른 프로세스로 재사용됐다)',
245
+ stopped: '멈춤(SIGTERM)', killed: '멈춤(SIGKILL — 제한 시간 안에 안 끝났다)',
246
+ absent: `못 찾음(pid.json ${r.pidRecordFound ? '확인' : '없음'} · 프로세스 표에서 runId·골 경로 0)`,
247
+ unmeasured: '못 잼(프로세스 표 또는 후보 시작 시각 확인 실패 — 없음이 아님)',
248
+ 'dry-run': 'dry-run(신호·Pod Job 삭제 없음)',
249
+ 'owner-mismatch': '소유 불일치(건드리지 않음 — PID 가 다른 프로세스로 재사용됐다)',
125
250
  };
126
251
  return [
127
252
  `harness stop ${r.runId}`,
128
253
  ` 프로세스: ${label[r.process]}${r.pid ? ` · pid ${r.pid}` : ''}`,
129
- ` Pod Job: ${r.jobsDeleted}개 지움 (문맥 ${r.contexts.length}개 · 라벨 elanous.run=${r.runId})`,
254
+ ...(!r.scanned && r.process !== 'unmeasured' ? [' 프로세스 표: 못 잼(기록 밖 발사 프로세스는 확인하지 못함)'] : []),
255
+ ...r.candidates.map(({ pid, startedAt, via }) => ` 후보: pid ${pid} · 시작 ${new Date(startedAt).toISOString()} · ${via}`),
256
+ r.dryRun
257
+ ? ` Pod Job: dry-run(조회·삭제하지 않음 · 라벨 elanous.run=${r.runId})`
258
+ : ` Pod Job: ${r.jobsDeleted}개 지움 (문맥 ${r.contexts.length}개 · 라벨 elanous.run=${r.runId})`,
130
259
  ].join('\n');
131
260
  }
@@ -16,7 +16,7 @@ export interface SelfSendCandidateDisplay {
16
16
  readonly hiddenStaleCount: number;
17
17
  }
18
18
 
19
- const SELF_SEND_GOAL_ATTEMPT_SUFFIX = /^(.*)-[0-9a-f]{8}$/;
19
+ const SELF_SEND_GOAL_ATTEMPT_SUFFIX = /^(.*)-[0-9a-f]{8}(?:-r[a-z0-9]{6})?$/;
20
20
 
21
21
  function selfSendGoalPrefix(spaceId: string): string | undefined {
22
22
  return SELF_SEND_GOAL_ATTEMPT_SUFFIX.exec(spaceId)?.[1];
@@ -104,6 +104,7 @@ export interface SelfSendDecisionInput {
104
104
 
105
105
  export interface SelfSendDecisionDeps {
106
106
  resolveRunScreen(runId: string): SelfSendRunScreen;
107
+ podFragmentsForRun(runId: string): readonly { spaceId: string }[];
107
108
  resolveTarget(space: string): SelfSendTargetResolution;
108
109
  isPodFragment(spaceId: string): boolean;
109
110
  screens(): readonly SelfSendCandidate[];
@@ -122,26 +123,34 @@ export function decideSelfSend(input: SelfSendDecisionInput, deps: SelfSendDecis
122
123
  if (space !== undefined && opts.run !== undefined) return refuse('--run 과 space 는 함께 사용할 수 없습니다.\n', 2);
123
124
  if (opts.run !== undefined && opts.run.trim() === '') return refuse('--run 에 빈 runId 를 줄 수 없습니다.\n', 2);
124
125
  let requestedSpace = space;
126
+ let podRunSpace: string | undefined;
125
127
  if (opts.run !== undefined) {
126
128
  const resolved = deps.resolveRunScreen(opts.run);
127
- if (resolved.logStoreStatus !== 'read') {
129
+ if (resolved.logStoreStatus !== 'read' && resolved.screenKey) {
128
130
  return refuse(`run 화면 해석 불가: 로그 스토어 ${resolved.logStoreStatus} (${resolved.logStorePath})\n`, 1);
129
131
  }
130
132
  if (!resolved.screenKey) {
131
- const last = resolved.lastEvent;
132
- const lastDetail = last
133
- ? ` 마지막 이벤트: ${last.category}/${last.event} (${Number.isFinite(Date.parse(last.timestamp)) ? `${Math.max(0, Math.floor((now - Date.parse(last.timestamp)) / 60_000))}분 전` : '시각 알 수 없음'})`
134
- : ' 마지막 이벤트: 없음';
135
- const status = resolved.missingStatus;
136
- const guidance = status === 'awaiting-start'
137
- ? '아직 화면을 띄우기 전입니다. 되묻기에 답하거나 저작이 끝날 때까지 기다리세요.'
138
- : status === 'pipeline-failed' ? '파이프라인이 오류로 멈췄습니다. 해당 error를 읽어 원인을 수리하세요.'
139
- : status === 'cleaned' ? '하니스가 정리되어 화면이 없습니다. 필요하면 새 런을 시작하세요.'
140
- : status === 'not-found' ? '이 runId의 이벤트가 없습니다. runId와 인스턴스 우주를 확인하세요.'
141
- : '화면을 아직 분류할 수 없습니다. 마지막 이벤트를 조사하세요.';
142
- return refuse(`run 화면 해석 불가: ${guidance}${lastDetail} (${opts.run})\n`, 1);
143
- }
144
- requestedSpace = normalizeSpaceId(resolved.screenKey);
133
+ const fragments = deps.podFragmentsForRun(opts.run);
134
+ if (fragments.length === 1) requestedSpace = podRunSpace = fragments[0]!.spaceId;
135
+ else {
136
+ if (fragments.length > 1) return refuse(`run 의 Pod 조각이 여러 개입니다: ${opts.run}. space 이름으로 다시 보내세요:\n${fragments.map((fragment) => ` ${fragment.spaceId}`).join('\n')}\n`, 2);
137
+ if (resolved.logStoreStatus !== 'read') {
138
+ return refuse(`run 화면 해석 불가: 로그 스토어 ${resolved.logStoreStatus} (${resolved.logStorePath}) · Pod 조각 기록도 0\n`, 1);
139
+ }
140
+ const last = resolved.lastEvent;
141
+ const lastDetail = last
142
+ ? ` 마지막 이벤트: ${last.category}/${last.event} (${Number.isFinite(Date.parse(last.timestamp)) ? `${Math.max(0, Math.floor((now - Date.parse(last.timestamp)) / 60_000))}분 전` : '시각 알 수 없음'})`
143
+ : ' 마지막 이벤트: 없음';
144
+ const status = resolved.missingStatus;
145
+ const guidance = status === 'awaiting-start'
146
+ ? '아직 화면을 띄우기 전입니다. 되묻기에 답하거나 저작이 끝날 때까지 기다리세요.'
147
+ : status === 'pipeline-failed' ? '파이프라인이 오류로 멈췄습니다. 해당 error를 읽어 원인을 수리하세요.'
148
+ : status === 'cleaned' ? '하니스가 정리되어 화면이 없습니다. 필요하면 새 런을 시작하세요.'
149
+ : status === 'not-found' ? '이 runId의 이벤트가 없습니다. runId와 인스턴스 우주를 확인하세요.'
150
+ : '화면을 아직 분류할 수 없습니다. 마지막 이벤트를 조사하세요.';
151
+ return refuse(`run 화면 해석 불가: ${guidance}${lastDetail} (${opts.run}) · Pod 조각 기록도 0\n`, 1);
152
+ }
153
+ } else requestedSpace = normalizeSpaceId(resolved.screenKey);
145
154
  }
146
155
  const hasMemo = opts.memo !== undefined;
147
156
  if (opts.stop && hasMemo) return refuse('self send에서는 --stop 과 --memo를 함께 사용할 수 없습니다.\n', 2);
@@ -158,7 +167,7 @@ export function decideSelfSend(input: SelfSendDecisionInput, deps: SelfSendDecis
158
167
  }
159
168
  requestedSpace = resolution.spaceId;
160
169
  }
161
- if (requestedSpace !== undefined && deps.isPodFragment(requestedSpace)) {
170
+ if (requestedSpace !== undefined && (podRunSpace !== undefined || deps.isPodFragment(requestedSpace))) {
162
171
  return { kind: 'send', spaceId: requestedSpace, channel: 'pod', warnings: [] };
163
172
  }
164
173
  const screens = deps.screens();
@@ -1,12 +1,15 @@
1
- import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
1
+ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { dirname } from 'node:path';
4
+ import { normalizeRunId } from './harness-space.js';
4
5
  import { controlInboxPath, type ControlMemoPayload } from './control-inbox.js';
5
6
  import { sendPodControl } from './pod-control-send.js';
6
7
  import type { Kubectl } from '../task-orchestrator/surfaces/self-implement-pod.js';
7
8
 
8
9
  export interface PodFragmentRecord {
9
10
  spaceId: string;
11
+ runId?: string;
12
+ parentRunId?: string;
10
13
  context: string;
11
14
  namespace: string;
12
15
  job: string;
@@ -29,6 +32,24 @@ export function podFragmentFinished(spaceId: string, env?: NodeJS.ProcessEnv): b
29
32
  return existsSync(`${podRecordPath(spaceId, env)}.finished`);
30
33
  }
31
34
 
35
+ export function listPodFragmentsForRun(runId: string, env?: NodeJS.ProcessEnv): PodFragmentRecord[] {
36
+ if (!runId || runId !== normalizeRunId(runId)) return [];
37
+ const dir = dirname(podRecordPath('fragment', env));
38
+ let names: string[];
39
+ try { names = readdirSync(dir); } catch { return []; }
40
+ const suffix = '.inbox.pod.json';
41
+ const matches: PodFragmentRecord[] = [];
42
+ for (const name of names) {
43
+ if (!name.endsWith(suffix)) continue;
44
+ const spaceId = name.slice(0, -suffix.length);
45
+ if (podFragmentFinished(spaceId, env)) continue;
46
+ const record = readPodFragment(spaceId, env);
47
+ if (record && ((record.runId === runId && normalizeRunId(record.runId) === runId)
48
+ || (record.parentRunId === runId && normalizeRunId(record.parentRunId) === runId))) matches.push(record);
49
+ }
50
+ return matches;
51
+ }
52
+
32
53
  export function writePodFragment(record: PodFragmentRecord, env?: NodeJS.ProcessEnv): void {
33
54
  const path = podRecordPath(record.spaceId, env);
34
55
  mkdirSync(dirname(path), { recursive: true });
@@ -22,11 +22,12 @@ export function slugifyFeature(feature: string): string {
22
22
  /** 하니스가 만드는 `self-impl/<슬러그>-<8자리 16진수>` 브랜치 이름.
23
23
  * goalId 가 있으면 읽는 자(`branchGoalId`, `/(?:^|-)goalid-([^/-]+)-/`)와 호환되는
24
24
  * `goalid-<id>-` 구간을 슬러그 앞에 싣는다. 없으면 기존과 바이트 단위로 같다. */
25
- export function plannedSelfImplBranch(feature: string, goalId?: string): string {
25
+ export function plannedSelfImplBranch(feature: string, goalId?: string, runId?: string): string {
26
26
  const slug = slugifyFeature(feature);
27
27
  const id = goalId?.trim() ?? '';
28
- if (!id) return `${WORKTREE_BRANCH_PREFIX}${slug}`;
28
+ const runSuffix = runId === undefined ? '' : `-r${runId.replace(/^run-/i, '').replace(/[^a-z0-9]/gi, '').slice(0, 6).toLowerCase()}`;
29
+ if (!id) return `${WORKTREE_BRANCH_PREFIX}${slug}${runSuffix}`;
29
30
  const readable = slug.replace(/-[0-9a-f]{8}$/, '');
30
31
  const digest = createHash('sha256').update(id).digest('hex').slice(0, 8);
31
- return `${WORKTREE_BRANCH_PREFIX}goalid-${id}-${readable}-${digest}`;
32
+ return `${WORKTREE_BRANCH_PREFIX}goalid-${id}-${readable}-${digest}${runSuffix}`;
32
33
  }
package/src/index.ts CHANGED
@@ -115,7 +115,7 @@ import { DEFAULT_REVIEW_BACKEND } from './agent-substrate/acp-reviewer.js';
115
115
  import type { ReviewResult } from './agent-substrate/pr-reviewer.js';
116
116
  import type { ReviewVerdict } from './agent-mission/review-loop.js';
117
117
  import type { SyncMode } from './types.js';
118
- import { runOnboarding, runOnboardingStep, runOnboardingNonInteractive, needsOnboarding, type OnboardingStepId } from './onboarding.js';
118
+ import { runOnboarding, runOnboardingStep, runOnboardingNonInteractive, needsOnboarding, handleOnboardingRefusal, type OnboardingStepId } from './onboarding.js';
119
119
  import {
120
120
  getUserConfig, reloadUserConfig, userConfigPath, saveUserConfig,
121
121
  backupUserConfig, backupConfigPath, addRotationEntry, type RotationEntry,
@@ -2088,13 +2088,14 @@ harnessCmd
2088
2088
  });
2089
2089
  harnessCmd
2090
2090
  .command('stop <runId>')
2091
- .description('런 하나를 멈춘다 — 오케스트레이터 프로세스(pid.json 의 시작 시각이 맞을 때만) ⊕ 라벨 elanous.run=<runId> Pod Job')
2091
+ .description('런 하나를 멈춘다 — pid.json 시작 시각 대조 ⊕ 런 발사 프로세스 표 조회 ⊕ 라벨 elanous.run=<runId> Pod Job')
2092
2092
  .option('--context <ctx...>', 'kube 문맥(여러 번 · 기본 = 알려진 문맥 전부)')
2093
2093
  .option('--json', 'JSON 출력')
2094
- .action(async (runId: string, opts: { context?: string[]; json?: boolean }) => {
2094
+ .option('--dry-run', '프로세스 후보만 보고 신호·Pod Job 삭제는 하지 않는다')
2095
+ .action(async (runId: string, opts: { context?: string[]; json?: boolean; dryRun?: boolean }) => {
2095
2096
  const { stopHarnessRun, defaultHarnessStopDeps, formatHarnessStop } = await import('./harness/harness-stop.js');
2096
2097
  try {
2097
- const result = await stopHarnessRun(runId, defaultHarnessStopDeps(), opts.context);
2098
+ const result = await stopHarnessRun(runId, defaultHarnessStopDeps(), opts.context, opts.dryRun === true);
2098
2099
  process.stdout.write(`${opts.json ? JSON.stringify(result) : formatHarnessStop(result)}\n`);
2099
2100
  } catch (error) {
2100
2101
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
@@ -4238,8 +4239,9 @@ const selfOrchestrateCmd = selfCmd
4238
4239
  if (!opts.json) ui.info(`[pod] 이미지 다시 굽기 — ${image.reason} (~1분)`);
4239
4240
  const { spawnSync } = await import('node:child_process');
4240
4241
  const top = spawnSync('git', ['rev-parse', '--show-toplevel'], { encoding: 'utf8' }).stdout.trim() || process.cwd();
4241
- const b = spawnSync('bash', [`${top}/docker/harness/build.sh`], { encoding: 'utf8', timeout: 900_000 });
4242
- if (b.status !== 0) { ui.error(`--substrate pod: 이미지 굽기 실패 rc=${b.status}: ${(b.stdout + b.stderr).slice(-400)}`); process.exit(2); }
4242
+ // bun 1.4 applies the 1 MiB default maxBuffer: a docker build log past it kills the child (ENOBUFS · status null).
4243
+ const b = spawnSync('bash', [`${top}/docker/harness/build.sh`], { encoding: 'utf8', timeout: 900_000, maxBuffer: 64 * 1024 * 1024 });
4244
+ if (b.status !== 0) { ui.error(`--substrate pod: 이미지 굽기 실패 rc=${b.status}${b.error ? ` (${(b.error as NodeJS.ErrnoException).code ?? b.error.message})` : ''}: ${(b.stdout + b.stderr).slice(-400)}`); process.exit(2); }
4243
4245
  image = podImageFreshness();
4244
4246
  }
4245
4247
  }
@@ -4410,7 +4412,7 @@ const selfOrchestrateCmd = selfCmd
4410
4412
  ...results.map((r) => {
4411
4413
  const icon = r.status === 'done' ? '✅' : r.status === 'cancelled' ? '⛔' : '❌';
4412
4414
  const disp = r.merged ? ` → merged ${r.prUrl}` : r.prUrl ? ` → PR ${r.prUrl}` : r.stage ? ` [${r.stage}]` : '';
4413
- return ` ${icon} ${r.status} · ${r.feature.slice(0, 56)}${disp}${r.error ? ` — ${r.error.code}` : ''}`;
4415
+ return ` ${icon} ${r.status} · ${r.feature.slice(0, 56)}${disp}${r.error ? ` — ${r.error.code}${r.error.code === 'pod-job-failed' ? `: ${r.error.message}` : ''}` : ''}`;
4414
4416
  }),
4415
4417
  ].join('\n'));
4416
4418
  await reportRun(results);
@@ -4608,7 +4610,7 @@ selfCmd
4608
4610
  .action(async (space: string | undefined, opts: { stop?: boolean; memo?: string; run?: string; includeStale?: boolean; readWait?: string }) => {
4609
4611
  const { classifyRunScreenMissing, queryRunScreenKey, listRunLedgers } = await import('./self-implement/run-ledger.js');
4610
4612
  const { getPtyManifest, listPtyManifestRows } = await import('./pty-shell/pty-manifest.js');
4611
- const { readPodFragment, podFragmentFinished, dispatchPodSelfSend } = await import('./harness/self-send-target.js');
4613
+ const { readPodFragment, podFragmentFinished, listPodFragmentsForRun, dispatchPodSelfSend } = await import('./harness/self-send-target.js');
4612
4614
  const { listHarnessScreens, readHarnessHeartbeat } = await import('./harness/harness-screen.js');
4613
4615
  const decision = decideSelfSend({ space, opts, now: Date.now() }, {
4614
4616
  resolveRunScreen: (runId) => {
@@ -4616,6 +4618,7 @@ selfCmd
4616
4618
  return { ...resolved, missingStatus: resolved.screenKey ? undefined : classifyRunScreenMissing(resolved.lastEvent) };
4617
4619
  },
4618
4620
  resolveTarget: (target) => resolveSelfSendTarget(target, { getPtyManifest, listPtyManifestRows }),
4621
+ podFragmentsForRun: (runId) => listPodFragmentsForRun(runId),
4619
4622
  isPodFragment: (spaceId) => Boolean(readPodFragment(spaceId) || podFragmentFinished(spaceId)),
4620
4623
  screens: () => listHarnessScreens().map((screen): SelfSendCandidate => {
4621
4624
  const heartbeat = readHarnessHeartbeat(screen.spaceId);
@@ -5426,6 +5429,30 @@ program.command('ad [input...]')
5426
5429
  });
5427
5430
 
5428
5431
  registerDocsCommands(program);
5432
+ // Private local decision ledger follows the same JS-only dispatch path as directives.
5433
+ if (existsSync(resolve(import.meta.dir, 'cli/decisions-cli.ts')) || existsSync(resolve(import.meta.dir, 'cli/decisions-cli.js'))) {
5434
+ program.command('decisions').description('대표 결정 로컬 원장').allowUnknownOption().allowExcessArguments(true)
5435
+ .action(async (_opts: unknown, command: Command) => {
5436
+ try { await (await import('./domains/standalone-log-sink.js')).registerStandaloneLogSink('decisions'); }
5437
+ catch { /* Logging must not prevent recording a decision. */ }
5438
+ const privateDecisionsModule: string = './cli/decisions-cli.js';
5439
+ const { registerDecisionsCommands } = await import(privateDecisionsModule) as { registerDecisionsCommands: (program: Command) => void };
5440
+ const privateProgram = new Command();
5441
+ registerDecisionsCommands(privateProgram);
5442
+ await privateProgram.parseAsync(['decisions', ...command.args], { from: 'user' });
5443
+ });
5444
+ }
5445
+ // Private command is absent from the public export; JS-only distributions still register it.
5446
+ if (existsSync(resolve(import.meta.dir, 'cli/directives-cli.ts')) || existsSync(resolve(import.meta.dir, 'cli/directives-cli.js'))) {
5447
+ program.command('directives').description('대표 지시 로컬 색인·검색').allowUnknownOption().allowExcessArguments(true)
5448
+ .action(async (_opts: unknown, command: Command) => {
5449
+ const privateDirectivesModule: string = './cli/directives-cli.js';
5450
+ const { registerDirectivesCommands } = await import(privateDirectivesModule) as { registerDirectivesCommands: (program: Command) => void };
5451
+ const privateProgram = new Command();
5452
+ registerDirectivesCommands(privateProgram);
5453
+ await privateProgram.parseAsync(['directives', ...command.args], { from: 'user' });
5454
+ });
5455
+ }
5429
5456
  registerFleetCommands(program);
5430
5457
 
5431
5458
  // ── ops (운영 관측 — 지금 뭐 도나·이상 없나·상태 전이) ──
@@ -5592,6 +5619,7 @@ agentCmd
5592
5619
  .option('--backend <id>', `외부 에이전트 backend (${agentBackendHelpList}). 각 CLI auto-approve 모드로 PTY 구동. 미등록 지정 시 명시 에러(조용한 codex 폴백 없음·애그노스틱)`)
5593
5620
  .option('--chain <backends>', '한 미션의 backend 사슬 (예: codex,claude,elanous)')
5594
5621
  .option('--plugin <name@market>', '에이전트 화면 안에서 플러그인을 설치하고 그 스킬로 미션 실행 (codex/claude)')
5622
+ .addOption(new Option('--resources <mode>', '미션 전 자원 판단 사다리 (on|off, 기본 on)').choices(['on', 'off']).default('on'))
5595
5623
  .option('--headless', 'Claude 구독 CLI를 비대화형 stream-json 모드로 실행 (claude backend 전용)')
5596
5624
  .action(async (textParts: string[], opts: Record<string, any>) => {
5597
5625
  // 구독 모드 보장 — 브레인(streamLLM)·codex 둘 다 ChatGPT 구독으로 (API 과금 회피).
@@ -7815,6 +7843,16 @@ sessionCmd
7815
7843
  console.log(res.formatted ?? '(no context)');
7816
7844
  });
7817
7845
 
7846
+ async function runOnboardingForCli(): Promise<boolean> {
7847
+ try {
7848
+ await runOnboarding();
7849
+ return true;
7850
+ } catch (error) {
7851
+ if (handleOnboardingRefusal(error, 'other')) return false;
7852
+ throw error;
7853
+ }
7854
+ }
7855
+
7818
7856
  function announceChatToolsCompatibility(): void {
7819
7857
  process.stderr.write('`chat --tools` is a compatibility entrypoint; use `elanous agent` for tool-loop calls.\n');
7820
7858
  debug.log('chat.tools-compatibility', 'invoked', { toolLoopEnabled: true });
@@ -7841,7 +7879,7 @@ program
7841
7879
  const cfg = getUserConfig();
7842
7880
  if (needsOnboarding(cfg)) {
7843
7881
  ui.info('No config yet — launching setup wizard first.');
7844
- await runOnboarding();
7882
+ if (!await runOnboardingForCli()) return;
7845
7883
  }
7846
7884
  const refreshed = reloadUserConfig();
7847
7885
  // ★ 관측갭 수리(2026-07-21·제1원칙·트랙A) — self-implement 자식 goal-loop(`chat --goal-loop`)은 데몬과
@@ -7885,7 +7923,7 @@ program
7885
7923
  .option('--new', 'Force a new session at boot instead of resuming the active one')
7886
7924
  .option('--session <id>', 'Resume an explicit session (id or unique prefix)')
7887
7925
  .option('--scenario <path>', 'Run a YAML multi-turn scenario before handing back to interactive (or exit)')
7888
- .option('--replay <session-id>', 'Re-execute the user prompts from a previous session in a fresh REPL run (BACKLOG #5). User prompts are extracted in order and fed through the same dispatcher as --scenario; assistant/tool messages and attachments are dropped. Mutually exclusive with --scenario.')
7926
+ .option('--replay <session-id>', 'Re-execute the user prompts from a previous session in a fresh REPL run. User prompts are extracted in order and fed through the same dispatcher as --scenario; assistant/tool messages and attachments are dropped. Mutually exclusive with --scenario.')
7889
7927
  .option('--exit-after-scenario', 'Exit after the scenario / replay completes (default true when stdin is not a TTY)')
7890
7928
  .option('--no-exit-after-scenario', 'Stay in the interactive prompt after the scenario / replay completes (TTY default)')
7891
7929
  .option('--json', 'Emit one JSON line per turn (sessionId/provider/model/reply/...) instead of streaming text')
@@ -7908,7 +7946,7 @@ program
7908
7946
  const cfg = getUserConfig();
7909
7947
  if (needsOnboarding(cfg)) {
7910
7948
  ui.info('No config yet — launching setup wizard first.');
7911
- await runOnboarding();
7949
+ if (!await runOnboardingForCli()) return;
7912
7950
  }
7913
7951
  const refreshed = reloadUserConfig();
7914
7952
  const { runRepl } = await import('./repl/index.js');
@@ -8471,7 +8509,7 @@ program
8471
8509
  'ACP client for the running elanous daemon. Three modes: handshake-only (default), one-shot (--message), interactive REPL (--interactive). Local (unix socket) or remote (--host / --url) over Tailscale.',
8472
8510
  )
8473
8511
  .option('--socket <path>', 'Override the unix socket path (default: ~/.elanous/elanous.sock)')
8474
- .option('--host <hostport>', 'Remote daemon host:port (e.g. mbp.tailnet:31415). Coerced to ws://<hostport>/v1/acp.')
8512
+ .option('--host <hostport>', 'Remote daemon host:port (e.g. laptop.tailnet:31415). Coerced to ws://<hostport>/v1/acp.')
8475
8513
  .option('--url <wsurl>', 'Remote daemon WS URL (e.g. ws://host:31415/v1/acp). Overrides --host.')
8476
8514
  .option('-r, --remote [name]', 'Bookmark name (`-r` alone = default). Fills host/token-file; explicit --url/--host/--token/--token-file win.')
8477
8515
  .option('--token <token>', 'Bearer token for remote auth. Overrides --token-file and ELANOUS_TOKEN.')
@@ -8945,6 +8983,7 @@ const nexusRunCmd = nexusCmd
8945
8983
  .option('--watch', 'Static-mode: fs.watch loop inside daemon — source edits auto-rebuild apps/pwa. Default on for `nexus run` (no args), off for `--test`.')
8946
8984
  .option('--no-watch', 'Disable the static-mode fs.watch loop (canonical entry default-on).')
8947
8985
  .option('--no-mcp', 'Skip the entire MCP-client boot wire (every `mcp.servers[]` in user-config). Use when a misbehaving server (e.g. xcrun mcpbridge tools/list hang) drags every startup through its 8s timeout.')
8986
+ .option('--no-auth', 'Explicitly disable Nexus HTTP and ACP websocket bearer authentication (unsafe).')
8948
8987
  .option('--dispatch', '§5-③ — enable the autonomous idle-continuation scheduler for this run (drives an active auto-mode goal on idle). Same as user-config `dispatch.enabled`. Default off — ignites self-firing turns, so opt-in.')
8949
8988
  .option('--force', 'Take the lock even if another nexus instance is recorded as running.')
8950
8989
  .option('--status', 'Alias for `elanous nexus status` (or test-mode status when combined with --test).')
@@ -8980,6 +9019,7 @@ const nexusRunCmd = nexusCmd
8980
9019
  autoRestart?: boolean;
8981
9020
  watch?: boolean;
8982
9021
  mcp?: boolean;
9022
+ auth?: boolean;
8983
9023
  fresh?: boolean;
8984
9024
  force?: boolean;
8985
9025
  dispatch?: boolean;
@@ -9129,6 +9169,7 @@ const nexusRunCmd = nexusCmd
9129
9169
  preflightStaleServe: true,
9130
9170
  verifyListen: true,
9131
9171
  ...(opts.mcp === false ? { mcpEnabled: false } : {}),
9172
+ ...(opts.auth === false ? { noAuth: true } : {}),
9132
9173
  ...(opts.https ? { https: true } : {}),
9133
9174
  ...(resolvedPort !== undefined ? { httpPort: resolvedPort } : {}),
9134
9175
  ...(opts.force ? { force: true } : {}),
@@ -9156,6 +9197,7 @@ const nexusRunCmd = nexusCmd
9156
9197
  // it — service-manager production posture stays watch-off.
9157
9198
  ...((bgChild ? opts.watch === true : watchOn) ? { pwaWatch: true } : {}),
9158
9199
  ...(opts.mcp === false ? { mcpEnabled: false } : {}),
9200
+ ...(opts.auth === false ? { noAuth: true } : {}),
9159
9201
  ...(opts.force ? { force: true } : {}),
9160
9202
  ...(opts.dispatch ? { dispatch: true } : {}),
9161
9203
  ...(opts.toolCwd ? { toolCwd: opts.toolCwd } : {}),
@@ -9187,6 +9229,7 @@ const nexusRunCmd = nexusCmd
9187
9229
  ...(opts.rebuild ? { rebuild: true } : {}),
9188
9230
  ...(watchOnTty ? { watch: true } : {}),
9189
9231
  ...(opts.mcp === false ? { mcpEnabled: false } : {}),
9232
+ ...(opts.auth === false ? { noAuth: true } : {}),
9190
9233
  });
9191
9234
  process.exit(result.exitCode);
9192
9235
  });
@@ -10560,7 +10603,7 @@ async function main(): Promise<void> {
10560
10603
  // gates on config completeness — no entry-mode branch.)
10561
10604
  const cfg = getUserConfig();
10562
10605
  if (needsOnboarding(cfg)) {
10563
- await runOnboarding();
10606
+ if (!await runOnboardingForCli()) return;
10564
10607
  }
10565
10608
 
10566
10609
  // Active-provider banner so users know WHICH model/auth is about to
@@ -32,6 +32,12 @@ export function prodInstanceRoot(): string {
32
32
  return normRoot(join(homedir(), '.elanous'));
33
33
  }
34
34
 
35
+ /** Release records are machine-wide except in an explicitly selected config universe. */
36
+ export function releaseLedgerRoot(): string {
37
+ const { getElanousConfigDirOverride } = require('../elanous-config-dir.js') as typeof import('../elanous-config-dir.js');
38
+ return getElanousConfigDirOverride() ? effectiveInstanceRoot() : prodInstanceRoot();
39
+ }
40
+
35
41
  export function resolveInstance(deps: ResolveDeps = {}): InstanceResolution {
36
42
  const prodRoot = normRoot(deps.prodRoot ?? prodInstanceRoot());
37
43
  if (deps.explicitFlagRoot?.trim()) {
@@ -27,6 +27,11 @@ export function readSavedCursor(root: string): number {
27
27
  try { return Number(JSON.parse(readFileSync(cursorFile(root), 'utf8')).lastId) || 0; } catch { return 0; }
28
28
  }
29
29
 
30
+ /** When the cursor last moved (= a run last found a new saved message). Undefined before the first message. */
31
+ export function readSavedCursorAt(root: string): string | undefined {
32
+ try { const at = JSON.parse(readFileSync(cursorFile(root), 'utf8')).at; return typeof at === 'string' ? at : undefined; } catch { return undefined; }
33
+ }
34
+
30
35
  function writeSavedCursor(root: string, lastId: number): void {
31
36
  mkdirSync(intakeLedgerDir(root), { recursive: true });
32
37
  writeFileSync(cursorFile(root), JSON.stringify({ lastId, at: new Date().toISOString() }) + '\n');