elanous 0.2.9 → 0.2.10

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 (361) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/2DsiDfDCks44g1cPUMhiW/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/24-e6d28380efab7faf.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/3615-740dd270764ffa24.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/3734-1015fe991eb9b7e7.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/5012-a7fe6f3c8b6fe45a.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/5783-ef40ebfc91d55230.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/62-ff227c3edb8b248b.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/7633-1b3709b0854c9412.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/7951-8b03dbb10b24cd06.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/8067-87ce253ec2f23697.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 8195-6fde3d7a39859206.js} +5 -5
  14. package/apps/pwa/out/_next/static/chunks/8894-b5fb36bab837cd29.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/9454-84d0d5d77446bd92.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/988-5f92e7ab7ccea3dd.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/9989-5582582f06429840.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/app/approvals/page-d2253d7e58ef1890.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-5441d4a9ab35e518.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/app/board/page-d9b12d680922d980.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-f972fc1c00a62e88.js → page-54b32bd27726588d.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/app/bots/page-e268d6d145827a22.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/app/chat/page-f8116559c7424da7.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/app/consult/page-1085399fd0f32112.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/control/page-1df686a14f65895a.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/app/design-check/page-54e1f966ab60a4dc.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/app/editor/{page-973996d584f008c1.js → page-7ed95f296f5ee8e9.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/exec/page-11edac668d77e5c3.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/field/page-59e2b9a22cd5a6d5.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/app/intake/page-2976df21ad74ab48.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/app/layout-34d211a67f16d510.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/live/page-8ff652d5d950639b.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/app/market/{page-8b0402a46a749a3d.js → page-b31610deda76337b.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-037f83e2a1edc8e2.js → page-6cb0e1bbefe5fb3d.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/morning/page-a286a152cf584576.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/not-found-d9237581e7003bcb.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/app/observatory/page-83274e761643fdd2.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-ceee6cfb1d4e6d68.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-9c9f8bb3e4a64091.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c17217603ea3426a.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/app/page-efb6177cbf1e8a51.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/app/reflection/page-d307523703bc4df3.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-462f4e4bab45ce7d.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/sessions/page-8990e96f33af3837.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-0df30e0c7e50148a.js → page-e168fa1030af0548.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/app/settings/page-1233e05a4068bc9c.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-13a1840b1fac337a.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/setup/page-64a29fc80b11ab55.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/share/page-68e787339a16b3dc.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/showroom/page-4fddc90b23feb215.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/tasks/page-886ba8260dc79c79.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/term/page-0f26507a1722e40b.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/today/page-109d4ccd42259c9d.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/trace/page-f5c2a4e510726774.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/vault/page-b2e0039416f42522.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-90670589b87abea8.js → page-26102fd7350f171e.js} +1 -1
  57. package/apps/pwa/out/_next/static/chunks/app/workflows/page-708948bc8a802794.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/workspace/page-5e5611620f78fb0b.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-35bedde6d885f08f.js → page-139ba5c367bbf60a.js} +1 -1
  60. package/apps/pwa/out/_next/static/chunks/main-app-202b4e81f33895ea.js +1 -0
  61. package/apps/pwa/out/_next/static/css/43feca5fd387dcbd.css +1 -0
  62. package/apps/pwa/out/approvals/index.html +1 -1
  63. package/apps/pwa/out/approvals/index.txt +14 -14
  64. package/apps/pwa/out/autopilot/index.html +1 -1
  65. package/apps/pwa/out/autopilot/index.txt +14 -14
  66. package/apps/pwa/out/board/index.html +1 -1
  67. package/apps/pwa/out/board/index.txt +14 -14
  68. package/apps/pwa/out/botlab/index.html +1 -1
  69. package/apps/pwa/out/botlab/index.txt +14 -14
  70. package/apps/pwa/out/bots/index.html +1 -1
  71. package/apps/pwa/out/bots/index.txt +14 -14
  72. package/apps/pwa/out/chat/index.html +1 -1
  73. package/apps/pwa/out/chat/index.txt +14 -14
  74. package/apps/pwa/out/consult/index.html +1 -1
  75. package/apps/pwa/out/consult/index.txt +14 -14
  76. package/apps/pwa/out/control/index.html +1 -1
  77. package/apps/pwa/out/control/index.txt +14 -14
  78. package/apps/pwa/out/design-check/index.html +1 -1
  79. package/apps/pwa/out/design-check/index.txt +14 -14
  80. package/apps/pwa/out/editor/index.html +1 -1
  81. package/apps/pwa/out/editor/index.txt +14 -14
  82. package/apps/pwa/out/exec/index.html +1 -1
  83. package/apps/pwa/out/exec/index.txt +14 -14
  84. package/apps/pwa/out/field/index.html +1 -1
  85. package/apps/pwa/out/field/index.txt +14 -14
  86. package/apps/pwa/out/healthz +1 -1
  87. package/apps/pwa/out/index.html +1 -1
  88. package/apps/pwa/out/index.txt +14 -14
  89. package/apps/pwa/out/intake/index.html +1 -1
  90. package/apps/pwa/out/intake/index.txt +14 -14
  91. package/apps/pwa/out/live/index.html +1 -1
  92. package/apps/pwa/out/live/index.txt +14 -14
  93. package/apps/pwa/out/market/index.html +1 -1
  94. package/apps/pwa/out/market/index.txt +14 -14
  95. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  96. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  97. package/apps/pwa/out/morning/index.html +1 -1
  98. package/apps/pwa/out/morning/index.txt +14 -14
  99. package/apps/pwa/out/observatory/index.html +1 -1
  100. package/apps/pwa/out/observatory/index.txt +14 -14
  101. package/apps/pwa/out/ops/checklist/index.html +1 -1
  102. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  103. package/apps/pwa/out/ops/release/index.html +1 -1
  104. package/apps/pwa/out/ops/release/index.txt +14 -14
  105. package/apps/pwa/out/ops/seats/index.html +1 -0
  106. package/apps/pwa/out/ops/seats/index.txt +30 -0
  107. package/apps/pwa/out/reflection/index.html +1 -1
  108. package/apps/pwa/out/reflection/index.txt +15 -15
  109. package/apps/pwa/out/scheduler/index.html +1 -1
  110. package/apps/pwa/out/scheduler/index.txt +14 -14
  111. package/apps/pwa/out/sessions/index.html +1 -1
  112. package/apps/pwa/out/sessions/index.txt +14 -14
  113. package/apps/pwa/out/settings/devices/index.html +1 -1
  114. package/apps/pwa/out/settings/devices/index.txt +14 -14
  115. package/apps/pwa/out/settings/index.html +1 -1
  116. package/apps/pwa/out/settings/index.txt +14 -14
  117. package/apps/pwa/out/setup/done/index.html +1 -1
  118. package/apps/pwa/out/setup/done/index.txt +14 -14
  119. package/apps/pwa/out/setup/index.html +1 -1
  120. package/apps/pwa/out/setup/index.txt +14 -14
  121. package/apps/pwa/out/share/index.html +1 -1
  122. package/apps/pwa/out/share/index.txt +14 -14
  123. package/apps/pwa/out/showroom/index.html +1 -1
  124. package/apps/pwa/out/showroom/index.txt +14 -14
  125. package/apps/pwa/out/tasks/index.html +1 -1
  126. package/apps/pwa/out/tasks/index.txt +14 -14
  127. package/apps/pwa/out/term/index.html +1 -1
  128. package/apps/pwa/out/term/index.txt +14 -14
  129. package/apps/pwa/out/today/index.html +1 -0
  130. package/apps/pwa/out/today/index.txt +33 -0
  131. package/apps/pwa/out/trace/index.html +1 -1
  132. package/apps/pwa/out/trace/index.txt +14 -14
  133. package/apps/pwa/out/vault/index.html +1 -1
  134. package/apps/pwa/out/vault/index.txt +14 -14
  135. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  136. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  137. package/apps/pwa/out/workflows/index.html +1 -1
  138. package/apps/pwa/out/workflows/index.txt +14 -14
  139. package/apps/pwa/out/workspace/index.html +1 -1
  140. package/apps/pwa/out/workspace/index.txt +14 -14
  141. package/apps/pwa/out/worktrees/index.html +1 -1
  142. package/apps/pwa/out/worktrees/index.txt +14 -14
  143. package/graphs/field/field-feed.yaml +2 -0
  144. package/graphs/implement-loop.yaml +1 -1
  145. package/package.json +1 -1
  146. package/plugins/card-followup/graphs/run-step.ts +48 -13
  147. package/plugins/doc-draft/graphs/doc-draft.yaml +3 -3
  148. package/plugins/doc-draft/graphs/run-step.ts +13 -6
  149. package/scripts/ci-pwa-build-gate.ts +160 -0
  150. package/scripts/coord-channel-watch.sh +42 -8
  151. package/scripts/coord-digest.ts +199 -0
  152. package/scripts/coord-post.sh +60 -3
  153. package/scripts/install.ps1 +68 -9
  154. package/scripts/install.sh +39 -12
  155. package/scripts/lib/pwa-scenarios/scenarios.ts +20 -0
  156. package/scripts/lib/tui-regress-checks.ts +146 -0
  157. package/scripts/memory-lifecycle-cycle.ts +2 -2
  158. package/scripts/ops-now-probe.ts +327 -0
  159. package/scripts/publish-internal-dist.ts +11 -1
  160. package/scripts/release-loop/cutoff.ts +19 -8
  161. package/scripts/release-loop/ops-upgrade-node.ts +72 -16
  162. package/scripts/release-loop/release-readiness.ts +9 -0
  163. package/scripts/release-loop/version-node.ts +58 -10
  164. package/scripts/repo-watch-cycle.ts +1 -1
  165. package/scripts/retro-cycle.ts +2 -2
  166. package/scripts/schedule-health-report.ts +1 -1
  167. package/scripts/storyboard/storyboard.ts +5 -161
  168. package/scripts/test-diet/lib.ts +30 -1
  169. package/scripts/test-diet/node.ts +82 -7
  170. package/scripts/test-timeout-audit.ts +114 -0
  171. package/scripts/tree-sync-alert.ts +1 -1
  172. package/scripts/tui-regress.ts +71 -0
  173. package/scripts/waitlist-daily-digest.ts +1 -1
  174. package/skills/explainer-video/SKILL.md +4 -0
  175. package/skills/explainer-video/engine/reel.mjs +18 -13
  176. package/skills/explainer-video/engine/reel.sh +24 -5
  177. package/skills/explainer-video/engine/stitch.mjs +145 -0
  178. package/skills/pitch-deck/SKILL.md +36 -0
  179. package/skills/pitch-deck/template.html +80 -0
  180. package/src/acp/codex-approval-adapter.ts +9 -3
  181. package/src/acp/core-turn-bridge.ts +117 -18
  182. package/src/acp/fact-question.ts +11 -0
  183. package/src/acp/server.ts +48 -7
  184. package/src/acp/tool-approval.ts +126 -0
  185. package/src/acp/turn-runner.ts +36 -0
  186. package/src/agent/monad-agent-turn.ts +8 -0
  187. package/src/ask-user-question/pending-questions.ts +25 -10
  188. package/src/ask-user-question/types.ts +9 -0
  189. package/src/auth/setup-link-tokens.ts +16 -5
  190. package/src/autopilot/build/integrity-gate.ts +12 -0
  191. package/src/boot/daemon-prompt-turn.ts +3 -0
  192. package/src/boot/daemon-tools/index.ts +8 -0
  193. package/src/boot/daemon-tools/types.ts +2 -0
  194. package/src/boot/daemon-tools/web-search.ts +1 -0
  195. package/src/chat/ctrl-c-in-input.ts +14 -0
  196. package/src/chat/index.ts +52 -0
  197. package/src/chat/pickers/state.ts +6 -2
  198. package/src/cli/agent-cli.ts +7 -0
  199. package/src/cli/decisions-cli.ts +25 -3
  200. package/src/cli/doctor-bundle-upload.ts +68 -0
  201. package/src/cli/doctor-cli.ts +38 -4
  202. package/src/cli/ops-cli.ts +20 -0
  203. package/src/cli/pod-cli.ts +49 -0
  204. package/src/cli/pr-cli.ts +31 -2
  205. package/src/cli/pwa-stop.ts +163 -13
  206. package/src/cli/pwa-test.ts +3 -0
  207. package/src/cli/release-cli.ts +63 -20
  208. package/src/cli/self-update.ts +14 -0
  209. package/src/cli/storyboard-cli.ts +37 -0
  210. package/src/cli/test-state-dir-flag.ts +11 -4
  211. package/src/connectors/linear.ts +146 -0
  212. package/src/context-bus/coord-events.ts +115 -0
  213. package/src/dashboard/dashboard-boot-fixture.ts +23 -0
  214. package/src/dashboard/index.ts +58 -21
  215. package/src/dashboard/slash-runtime/dashboard-handlers.ts +7 -0
  216. package/src/dashboard/slash-runtime/help-from-registry.ts +43 -28
  217. package/src/decisions/decision-cards.ts +49 -1
  218. package/src/decisions/decision-ledger.ts +75 -4
  219. package/src/decisions/decision-linear-projection.ts +157 -0
  220. package/src/decisions/web-push-decision-cards.ts +50 -0
  221. package/src/directive/interpretation-card.ts +36 -0
  222. package/src/discord.ts +2 -1
  223. package/src/domains/coo-admin-tool.ts +62 -0
  224. package/src/domains/core-tools.ts +11 -3
  225. package/src/domains/elanous-card.ts +29 -0
  226. package/src/domains/ops-facts-tool.ts +76 -0
  227. package/src/domains/outbound-alert.ts +41 -13
  228. package/src/domains/release-tool.ts +168 -0
  229. package/src/domains/telegram-kind-route.ts +19 -1
  230. package/src/exec-requests/planner.ts +98 -28
  231. package/src/exec-requests/runner.ts +107 -10
  232. package/src/exec-requests/store.ts +8 -2
  233. package/src/field/field-media.ts +10 -7
  234. package/src/field/field-reel.ts +14 -4
  235. package/src/git-fs/worktree.ts +45 -6
  236. package/src/graph-runner/graph-approval-card.ts +113 -0
  237. package/src/graph-runner/graph-cli.ts +129 -4
  238. package/src/graph-runner/graph-notify.ts +25 -3
  239. package/src/graph-runner/runner.ts +142 -16
  240. package/src/harness/browser-verify.ts +25 -2
  241. package/src/harness/harness-cli-command.ts +231 -35
  242. package/src/harness/harness-incidents-writer.fixture.ts +24 -0
  243. package/src/harness/harness-incidents.ts +129 -0
  244. package/src/harness/harness-stop.ts +53 -3
  245. package/src/hitl/clarification-policy.ts +3 -1
  246. package/src/index.ts +156 -37
  247. package/src/intake-plane/author-goals.ts +37 -4
  248. package/src/intake-plane/check.ts +3 -0
  249. package/src/intake-plane/discord-seat-work.ts +47 -2
  250. package/src/intake-plane/seat-doc-route.ts +170 -6
  251. package/src/intake-plane/telegram-seat-work.ts +44 -2
  252. package/src/intake-plane/telegram-work.ts +1 -0
  253. package/src/maturity/cli-maturity.ts +34 -0
  254. package/src/maturity/feature-maturity.ts +98 -1
  255. package/src/maturity/tui-slash-maturity.ts +31 -0
  256. package/src/memory.ts +32 -4
  257. package/src/nexus/api/exec-requests.ts +33 -3
  258. package/src/nexus/api/http-server.ts +53 -1
  259. package/src/nexus/api/meta-api.ts +11 -0
  260. package/src/nexus/api/ops-api.ts +10 -1
  261. package/src/nexus/api/ops-seats-sources.ts +37 -7
  262. package/src/nexus/api/ops-seats.ts +16 -6
  263. package/src/nexus/api/seat-requests.ts +88 -8
  264. package/src/nexus/api/sessions-store.ts +63 -0
  265. package/src/nexus/api/setup-codex-login.ts +160 -0
  266. package/src/nexus/api/usage-api.ts +102 -0
  267. package/src/nexus/index.ts +45 -4
  268. package/src/onboarding/first-run.ts +89 -0
  269. package/src/onboarding/lan-address.ts +45 -0
  270. package/src/onboarding/web-first.ts +119 -24
  271. package/src/onboarding.ts +4 -0
  272. package/src/ops-now/ops-now-refresh.ts +100 -0
  273. package/src/project/project-store.ts +115 -0
  274. package/src/release-loop/checklist.ts +78 -4
  275. package/src/release-loop/feature-store.ts +154 -21
  276. package/src/release-loop/release-schedule.ts +53 -0
  277. package/src/repl/index.ts +7 -0
  278. package/src/seat-dispatch/ceo-commands.ts +41 -8
  279. package/src/seat-dispatch/ceo-intent.ts +38 -0
  280. package/src/seat-loop/seat-loop.ts +231 -0
  281. package/src/seat-loop/seat-report.ts +64 -0
  282. package/src/self-dev/decompose.ts +66 -11
  283. package/src/self-dev/run-store.ts +10 -1
  284. package/src/self-implement/execution-clarification.ts +115 -0
  285. package/src/self-implement/gate-cli.ts +6 -3
  286. package/src/self-implement/orchestrator.ts +37 -3
  287. package/src/self-implement/pipeline-shape.ts +1 -1
  288. package/src/self-implement/run-status-mapping.ts +5 -3
  289. package/src/session/chat.ts +2 -0
  290. package/src/session/index.ts +9 -0
  291. package/src/shell-env-bootstrap.ts +17 -0
  292. package/src/steward/directive.ts +51 -2
  293. package/src/steward/launch.ts +250 -0
  294. package/src/steward/outcome-notify.ts +115 -0
  295. package/src/steward/steward-cards.ts +8 -2
  296. package/src/steward/triage-plan.ts +32 -4
  297. package/src/steward/triage.ts +93 -9
  298. package/src/steward/working-backwards.ts +64 -0
  299. package/src/storyboard/storyboard.ts +141 -0
  300. package/src/task-cards/card-store.ts +2 -2
  301. package/src/task-orchestrator/surfaces/pod-lease.ts +260 -0
  302. package/src/telegram-card-followup.ts +233 -0
  303. package/src/telegram-report.ts +15 -9
  304. package/src/telegram-run.ts +7 -0
  305. package/src/telegram.ts +29 -8
  306. package/src/tool-runtime/types.ts +4 -0
  307. package/src/tui-client/dashboard-session.ts +2 -0
  308. package/src/user-config.ts +118 -11
  309. package/src/version/packed-revision.json +1 -1
  310. package/src/web-push/notify-exec-request.ts +32 -0
  311. package/apps/pwa/out/_next/static/bFI5EEwg7T6aG88ibX4Zq/_buildManifest.js +0 -1
  312. package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
  313. package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
  314. package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
  315. package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +0 -1
  316. package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +0 -1
  317. package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
  318. package/apps/pwa/out/_next/static/chunks/7032-f4ef698518fba61c.js +0 -1
  319. package/apps/pwa/out/_next/static/chunks/8067-d976b441a104ab74.js +0 -1
  320. package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +0 -1
  321. package/apps/pwa/out/_next/static/chunks/8630-cdfc61e82c653ffc.js +0 -1
  322. package/apps/pwa/out/_next/static/chunks/8894-e263b332208e3957.js +0 -1
  323. package/apps/pwa/out/_next/static/chunks/9454-139040d37c79dbc9.js +0 -1
  324. package/apps/pwa/out/_next/static/chunks/9740-8f5ff96c522b5f55.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/app/approvals/page-fb7c2d458f32fcad.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-35e2713597dd24d1.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/board/page-918b656f064577ba.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/bots/page-5f30e00693527ebd.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/chat/page-7fbae173b3c103a7.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/consult/page-3b4edcce364062ea.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/control/page-c5794f484c251f44.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/design-check/page-1130d8eefcd87e76.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/exec/page-016e2bda04eecb1f.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/field/page-b4d6d9a3b56f2940.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/intake/page-ec49a466121233ba.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/app/layout-f72db356744ceade.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/app/live/page-a6fd9b50d1b85378.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/app/morning/page-adfbd2ba9598e59a.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/app/not-found-46fedff57d4bec67.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/app/observatory/page-0cf318e9dcee2bbf.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-899b327cb8b6845c.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-6c1e6ba743a29dfe.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/app/page-1a8e4807292f6b17.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/app/reflection/page-28deba6bcb834916.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-de68e2205794037d.js +0 -1
  346. package/apps/pwa/out/_next/static/chunks/app/sessions/page-c71c33a573230672.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/app/settings/page-4fc56790fe0d2682.js +0 -1
  348. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-59df2aebb38a7c1e.js +0 -1
  349. package/apps/pwa/out/_next/static/chunks/app/setup/page-4647be5dbcbd0795.js +0 -1
  350. package/apps/pwa/out/_next/static/chunks/app/share/page-e025892bda74b743.js +0 -1
  351. package/apps/pwa/out/_next/static/chunks/app/showroom/page-a0955de73b89f078.js +0 -1
  352. package/apps/pwa/out/_next/static/chunks/app/tasks/page-369846d2921af584.js +0 -1
  353. package/apps/pwa/out/_next/static/chunks/app/term/page-72cbc1c8cdd0f65d.js +0 -1
  354. package/apps/pwa/out/_next/static/chunks/app/trace/page-77b28bfb0996d5ea.js +0 -1
  355. package/apps/pwa/out/_next/static/chunks/app/vault/page-3220c4934f49c6c2.js +0 -1
  356. package/apps/pwa/out/_next/static/chunks/app/workflows/page-7df9243ed021a4cc.js +0 -1
  357. package/apps/pwa/out/_next/static/chunks/app/workspace/page-4498c3853f4f688d.js +0 -1
  358. package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +0 -1
  359. package/apps/pwa/out/_next/static/css/f92f63a5df0ba229.css +0 -1
  360. package/src/dashboard/turn-typeahead-echo.ts +0 -23
  361. /package/apps/pwa/out/_next/static/{bFI5EEwg7T6aG88ibX4Zq → 2DsiDfDCks44g1cPUMhiW}/_ssgManifest.js +0 -0
@@ -1,7 +1,13 @@
1
1
  import { execFileSync } from 'node:child_process';
2
2
  import { createHash } from 'node:crypto';
3
- import { readFileSync } from 'node:fs';
3
+ import { existsSync, readFileSync } from 'node:fs';
4
+ import { constants as osConstants } from 'node:os';
4
5
  import { resolve } from 'node:path';
6
+ import { LogStore, logsDbPath } from '../mss/logging/log-store.js';
7
+ import { loadSelfDevRun } from '../self-dev/run-store.js';
8
+ import { normalizeRunId } from './harness-space.js';
9
+ import { effectiveInstanceRoot } from '../instance/resolve.js';
10
+ import { detectBursts, formatIncidentBurstWarning, readRunExits, recentBurst, recordRunExit } from './harness-incidents.js';
5
11
  import { Command, Option } from 'commander';
6
12
  import { runGitCommand } from '../git-fs/runner.js';
7
13
  import { GOAL_TYPES, lintGoalFile, parseGoalId, parseGoalType, tracedPathReferences, type GoalType } from '../self-implement/goal-author.js';
@@ -323,6 +329,191 @@ function resolveLaunchSubstrate(opts: HarnessSubstrateOpts): ResolvedHarnessSubs
323
329
  else console.log(head);
324
330
  return resolved;
325
331
  }
332
+ type PodExit = { status: number | null; signal?: string | null };
333
+ type PodExitReason = 'signal' | 'human-stop' | 'supervisor' | 'no-launch' | 'unknown';
334
+ const STOPPING_SUPERVISOR_VERDICTS = new Set(['UNCONVERGEABLE', 'CONTRACT-CONFLICT']);
335
+ // Ledger events that only exist once the child actually ran — any of them rules out «never reached the Pod».
336
+ const LAUNCH_EVIDENCE_EVENTS = new Set(['job-applied', 'pod-child-run', 'reviewed', 'rework-budget', 'pr-opened']);
337
+
338
+ /** Keep the run identity seen at launch even if the bounded output tail later drops it. */
339
+ export function harnessPodRunId(output: string): string | undefined {
340
+ return output.match(/\[self-dev\][^\r\n]*\brun\s+(run-[a-z0-9_-]{8,64})(?=\s|$)/i)?.[1];
341
+ }
342
+
343
+ /** Classify a missing Pod result from run-scoped evidence, never from a nonzero exit alone. */
344
+ export function classifyHarnessPodExit(
345
+ exit: PodExit,
346
+ output: string,
347
+ deps: {
348
+ loadLedger?: typeof loadRunLedger;
349
+ hasJobApplied?: (runId: string | undefined, output: string) => boolean | undefined;
350
+ supervisorDecision?: (runId: string) => { stopReason?: string; why?: string } | undefined;
351
+ loadCheckpoint?: typeof loadSelfDevRun;
352
+ runId?: string;
353
+ } = {},
354
+ ): { lines: string[]; reason: PodExitReason | 'pod-failure' } {
355
+ let podFailure: string | undefined;
356
+ let podJobFailed = false;
357
+ for (const line of output.split('\n').reverse()) {
358
+ if (!line.trimStart().startsWith('[{') || !line.includes('"error"')) continue;
359
+ try {
360
+ const results: unknown = JSON.parse(line);
361
+ if (!Array.isArray(results)) continue;
362
+ for (const result of results) {
363
+ if (!result || typeof result !== 'object') continue;
364
+ const error = (result as { error?: { code?: string; message?: string } }).error;
365
+ if (typeof error?.code !== 'string' || typeof error.message !== 'string' || !error.code.startsWith('pod-')) continue;
366
+ podFailure = error.message;
367
+ podJobFailed = error.code === 'pod-job-failed';
368
+ break;
369
+ }
370
+ } catch { /* Non-result stdout cannot establish that a Pod child ran. */ }
371
+ if (podFailure) break;
372
+ }
373
+ if (!podFailure) {
374
+ const summary = output.split('\n').find((line) => /\s❌\sfailed · .* — pod-[\w-]+: /.test(line));
375
+ podFailure = summary?.split(/\s❌\sfailed · .* — pod-[\w-]+: /)[1];
376
+ podJobFailed = summary?.includes(' — pod-job-failed: ') ?? false;
377
+ }
378
+ // Only the run announced at launch — another run id quoted in the output is not this run (review round 3).
379
+ const observedRunId = deps.runId ?? harnessPodRunId(output);
380
+ const runId = observedRunId ? normalizeRunId(observedRunId) : undefined;
381
+ if (podFailure) {
382
+ const childError = podJobFailed ? podFailure.split('childError=')[1]?.split('\\n', 1)[0]?.split('\n', 1)[0] : undefined;
383
+ try { debug.log('harness.pod', 'exit-classified', { runId, reason: 'pod-failure', status: exit.status, signal: exit.signal ?? null }); } catch { /* observation is fail-soft */ }
384
+ return { reason: 'pod-failure', lines: [podJobFailed && childError && childError !== 'no-result-line'
385
+ ? `Pod 안 자식이 실패했다 — ${childError}`
386
+ : `Pod 실행이 실패했다 — ${podFailure.split('\\n', 1)[0]}`] };
387
+ }
388
+ let ledger: ReturnType<typeof loadRunLedger> = null;
389
+ let ledgerReadable = false;
390
+ if (runId) {
391
+ try { ledger = (deps.loadLedger ?? loadRunLedger)(runId); ledgerReadable = ledger !== null; } catch { /* unreadable is not absent */ }
392
+ }
393
+ const signal = exit.signal ?? (exit.status !== null && exit.status > 128
394
+ ? Object.entries(osConstants.signals).find(([, number]) => number === exit.status! - 128)?.[0] : undefined);
395
+ const humanStop = ledger?.some((entry) => entry.event === 'human-stop');
396
+ // A continuing verdict (EXTEND · SUFFICIENT) is not why the run ended — only stopping verdicts count.
397
+ const supervisor = [...(ledger ?? [])].reverse().find((entry) =>
398
+ (entry.event === 'supervisor-decision' || entry.event === 'supervisor-verdict' || entry.event === 'rework-budget')
399
+ && STOPPING_SUPERVISOR_VERDICTS.has(String(entry.data.supervisorVerdict ?? entry.data.verdict))
400
+ || entry.event === 'run-status' && entry.data.stage === 'review-blocked');
401
+ let decision: { stopReason?: string; why?: string } | undefined;
402
+ if (runId) {
403
+ try { decision = (deps.supervisorDecision ?? lookupHarnessSupervisorDecision)(runId); } catch { /* unknown */ }
404
+ }
405
+ const verdict = decision?.stopReason ?? supervisor?.data.supervisorVerdict ?? supervisor?.data.verdict
406
+ ?? (supervisor?.event === 'run-status' ? supervisor.data.stage : undefined);
407
+ const reasonText = decision?.why ?? supervisor?.data.supervisorReason ?? supervisor?.data.reason;
408
+ const ledgerPrUrl = [...(ledger ?? [])].reverse().find((entry) => entry.event === 'pr-opened'
409
+ && (typeof entry.data.url === 'string' || typeof entry.data.prUrl === 'string'))?.data;
410
+ let checkpoint: ReturnType<typeof loadSelfDevRun> = null;
411
+ if (runId) {
412
+ try { checkpoint = (deps.loadCheckpoint ?? loadSelfDevRun)(runId); } catch { /* unreadable is not absent */ }
413
+ }
414
+ const pr = output.match(/https:\/\/github\.com\/[\w.-]+\/[\w.-]+\/pull\/\d+\b/)?.[0]
415
+ ?? (typeof ledgerPrUrl?.url === 'string' ? ledgerPrUrl.url
416
+ : typeof ledgerPrUrl?.prUrl === 'string' ? ledgerPrUrl.prUrl : undefined)
417
+ ?? checkpoint?.results.find((result) => result.prUrl)?.prUrl;
418
+ let reason: PodExitReason;
419
+ let line: string;
420
+ if (humanStop) {
421
+ reason = 'human-stop';
422
+ line = '사람이 멈춘 런이다';
423
+ } else if (signal) {
424
+ reason = 'signal';
425
+ line = `런이 멈췄다(${signal}) — \`harness stop\` 이나 세션 종료일 수 있다 · 그 전까지 만든 PR 은 남아 있다`;
426
+ } else if (typeof verdict === 'string' || checkpoint?.supervisorStopReason) {
427
+ reason = 'supervisor';
428
+ line = `감독 판정: ${verdict ?? checkpoint?.supervisorStopReason}${typeof reasonText === 'string' && reasonText.trim() ? ` — ${reasonText.trim().split(/\r?\n/, 1)[0]}` : ''}`;
429
+ } else {
430
+ let applied: boolean | undefined;
431
+ try { applied = (deps.hasJobApplied ?? hasHarnessPodJobApplied)(runId, output); } catch { /* unknown */ }
432
+ const noLaunch = ledgerReadable && !ledger?.some((entry) => LAUNCH_EVIDENCE_EVENTS.has(entry.event))
433
+ && applied === false && !pr;
434
+ reason = noLaunch ? 'no-launch' : 'unknown';
435
+ line = noLaunch
436
+ ? 'Pod 실행에 닿지 못했다 — 풀·컨텍스트·SSH 연결을 확인하거나 `--substrate local` 로 명시하라'
437
+ : 'Pod 실행 여부 또는 종료 이유를 확인하지 못했다';
438
+ }
439
+ try { debug.log('harness.pod', 'exit-classified', { runId, reason, status: exit.status, signal: signal ?? null }); } catch { /* observation is fail-soft */ }
440
+ return { reason, lines: [line, ...(pr ? [`PR 이 남아 있다: ${pr}`] : [])] };
441
+ }
442
+
443
+ /** The classified status is the same exit handed to the launcher; ledger failure is never an exit failure. */
444
+ export function recordClassifiedHarnessPodExit(runId: string | undefined, reason: string, status: number): void {
445
+ if (!runId) return;
446
+ try {
447
+ const signal = status > 128
448
+ ? Object.entries(osConstants.signals).find(([, number]) => number === status - 128)?.[0] ?? null : null;
449
+ recordRunExit({ runId, reason, status, signal, at: new Date().toISOString() }, effectiveInstanceRoot());
450
+ } catch { /* incident writes cannot change Pod output or exit */ }
451
+ }
452
+
453
+ /** Every Pod launch entrance (harness ask/say · dev ask --substrate pod) warns once after a recent burst; it never blocks the launch. */
454
+ export function warnRecentIncidentBurst(
455
+ root: string = effectiveInstanceRoot(), now: Date = new Date(), write: (line: string) => void = (line) => console.error(line),
456
+ ): boolean {
457
+ try {
458
+ const burst = recentBurst(root, now);
459
+ if (!burst) return false;
460
+ write(formatIncidentBurstWarning(burst));
461
+ try { debug.log('harness.incident', 'burst', { reason: burst.reason, count: burst.count }); } catch { /* observation is fail-soft */ }
462
+ return true;
463
+ } catch { return false; /* incident reads cannot block a launch */ }
464
+ }
465
+
466
+ function lookupHarnessSupervisorDecision(runId: string): { stopReason?: string; why?: string } | undefined {
467
+ const path = logsDbPath();
468
+ if (!existsSync(path)) return undefined;
469
+ const store = LogStore.openReadOnly(path);
470
+ try {
471
+ let beforeId: number | undefined;
472
+ for (;;) {
473
+ const rows = store.query({ exactCategories: ['self-dev.supervisor'], events: ['decision'], grep: runId,
474
+ limit: 1_000, ...(beforeId === undefined ? {} : { beforeId }) });
475
+ for (const row of rows) {
476
+ try {
477
+ const data = JSON.parse(row.data ?? '{}') as Record<string, unknown>;
478
+ if (data.runId !== runId) continue;
479
+ return data.action === 'stop' && typeof data.stopReason === 'string'
480
+ ? { stopReason: data.stopReason, ...(typeof data.why === 'string' ? { why: data.why } : {}) }
481
+ : undefined;
482
+ } catch { /* Malformed observation is not evidence. */ }
483
+ }
484
+ if (rows.length < 1_000) return undefined;
485
+ beforeId = rows.at(-1)!.id;
486
+ }
487
+ } finally { store.close(); }
488
+ }
489
+
490
+ function hasHarnessPodJobApplied(runId: string | undefined, output: string): boolean | undefined {
491
+ const job = output.match(/\bsi-[a-z0-9-]+\b/i)?.[0];
492
+ if (!runId && !job) return undefined;
493
+ const path = logsDbPath();
494
+ if (!existsSync(path)) return undefined;
495
+ const store = LogStore.openReadOnly(path);
496
+ try {
497
+ for (const needle of new Set([runId, job].filter((value): value is string => value !== undefined))) {
498
+ let beforeId: number | undefined;
499
+ for (;;) {
500
+ const rows = store.query({ exactCategories: ['self-implement.pod'], events: ['job-applied'],
501
+ grep: needle, limit: 1_000, ...(beforeId === undefined ? {} : { beforeId }) });
502
+ if (rows.some((row) => {
503
+ try {
504
+ const data = JSON.parse(row.data ?? '{}') as Record<string, unknown>;
505
+ return (runId !== undefined && data.runId === runId)
506
+ || (job !== undefined && data.job === job && (runId === undefined || data.runId === undefined || data.runId === runId));
507
+ } catch { return false; }
508
+ })) return true;
509
+ if (rows.length < 1_000) break;
510
+ beforeId = rows.at(-1)!.id;
511
+ }
512
+ }
513
+ return false;
514
+ } finally { store.close(); }
515
+ }
516
+
326
517
  /** ⭐ 런 계약의 실행 칸 = pod — 호스트는 그래프를 안 돌리고 Pod 로 보낸다(harness-pod-dispatch.ts). */
327
518
  async function onPod(opts: unknown, entrance: 'cli-harness-ask' | 'cli-harness-say', input: string, pool: string,
328
519
  recordDispatch: (input: DispatchTaskInput, deps?: DispatchTaskDeps) => ReturnType<typeof dispatchTask> = dispatchTask): Promise<void> {
@@ -334,6 +525,7 @@ async function onPod(opts: unknown, entrance: 'cli-harness-ask' | 'cli-harness-s
334
525
  process.exitCode = 2;
335
526
  return;
336
527
  }
528
+ warnRecentIncidentBurst();
337
529
  let dispatchRecorded = false;
338
530
  try {
339
531
  const goalText = entrance === 'cli-harness-ask' ? readFileSync(input, 'utf8') : input;
@@ -351,42 +543,16 @@ async function onPod(opts: unknown, entrance: 'cli-harness-ask' | 'cli-harness-s
351
543
  }
352
544
  const { dispatchHarnessOnPod } = await import('./harness-pod-dispatch.js');
353
545
  let output = '';
546
+ let runId: string | undefined;
354
547
  const status = await dispatchHarnessOnPod({ entrance, input, podPool: pool, ...(dispatchRecorded ? { dispatchRecorded: true } : {}), ...(o.podMemory ? { podMemory: o.podMemory } : {}), ...(o.autoMerge === false ? { autoMerge: false } : {}), ...(o.base ? { base: o.base } : {}), ...(o.json ? { json: true } : {}), ...(o.source ? { source: o.source } : {}) },
355
- entrance === 'cli-harness-ask' ? { onOutput: (text) => { output = (output + text).slice(-16_000); } } : {});
548
+ { onOutput: (text) => {
549
+ runId ??= harnessPodRunId(output + text);
550
+ output = (output + text).slice(-16_000);
551
+ } });
356
552
  if (status !== 0) {
357
- let childError: string | undefined;
358
- let podFailure: string | undefined;
359
- let podJobFailed = false;
360
- if (entrance === 'cli-harness-ask') {
361
- for (const line of output.split('\n').reverse()) {
362
- if (!line.trimStart().startsWith('[{') || !line.includes('"error"')) continue;
363
- try {
364
- const results: unknown = JSON.parse(line);
365
- if (!Array.isArray(results)) continue;
366
- for (const result of results) {
367
- if (!result || typeof result !== 'object') continue;
368
- const error = (result as { error?: { code?: string; message?: string } }).error;
369
- if (typeof error?.code !== 'string' || typeof error.message !== 'string' || !error.code.startsWith('pod-')) continue;
370
- if (error.code === 'pod-job-failed') {
371
- podJobFailed = true;
372
- childError = error.message.split('childError=')[1]?.split('\n', 1)[0];
373
- }
374
- podFailure = error.message;
375
- break;
376
- }
377
- } catch { /* Non-result stdout cannot establish that a Pod child ran. */ }
378
- if (podFailure) break;
379
- }
380
- if (!podFailure) {
381
- const summary = output.split('\n').find((line) => /\s❌\sfailed · .* — pod-[\w-]+: /.test(line));
382
- podFailure = summary?.split(/\s❌\sfailed · .* — pod-[\w-]+: /)[1];
383
- podJobFailed = summary?.includes(' — pod-job-failed: ') ?? false;
384
- if (podJobFailed) childError = podFailure?.split('childError=')[1]?.split('\n', 1)[0];
385
- }
386
- }
387
- if (podJobFailed && childError && childError !== 'no-result-line') console.error(`Pod 안 자식이 실패했다 — ${childError}`);
388
- else if (podFailure) console.error(`Pod 실행이 실패했다 — ${podFailure.split('\n', 1)[0]}`);
389
- else console.error('Pod 실행에 닿지 못했다 — 풀·컨텍스트·SSH 연결을 확인하거나 `--substrate local` 로 명시하라');
553
+ const classified = classifyHarnessPodExit({ status }, output, { ...(runId ? { runId } : {}) });
554
+ recordClassifiedHarnessPodExit(runId ?? harnessPodRunId(output), classified.reason, status);
555
+ for (const line of classified.lines) console.error(line);
390
556
  process.exitCode = status;
391
557
  }
392
558
  }
@@ -1596,6 +1762,36 @@ export function installHarnessCliCommand(program: Command, deps: HarnessCliComma
1596
1762
  .command('harness')
1597
1763
  .description('dev-harness worktree 수명 — 워크트리 생성(worktree add)·조회(worktrees)·정리(clean)·프로세스 관찰(processes)');
1598
1764
  installHarnessCliSinkHook(harnessCmd, deps.registerSink, deps.resolveSurface);
1765
+ harnessCmd.command('incidents').description('최근 하니스 Pod 종료 사고 원장을 읽는다')
1766
+ .option('--since <minutes>', '최근 몇 분의 종료를 볼지 (기본 15분)', '15')
1767
+ .option('--json', '구조화 출력')
1768
+ .action((opts: { since: string; json?: boolean }) => {
1769
+ const since = Number(opts.since);
1770
+ if (!Number.isFinite(since) || since < 0) {
1771
+ console.error('❌ --since 는 0 이상의 분이어야 합니다');
1772
+ process.exitCode = 2;
1773
+ return;
1774
+ }
1775
+ try {
1776
+ const root = effectiveInstanceRoot();
1777
+ const rows = readRunExits(root);
1778
+ if (rows.length === 0) {
1779
+ console.log(opts.json ? JSON.stringify({ rows: [], bursts: [] }) : '기록 없음 — 사고 0 이 아니라 아직 기록이 없다');
1780
+ return;
1781
+ }
1782
+ const now = Date.now();
1783
+ const selected = rows.filter((row) => Date.parse(row.at) >= now - since * 60_000 && Date.parse(row.at) <= now);
1784
+ const bursts = detectBursts(rows).filter((burst) => Date.parse(burst.lastAt) >= now - since * 60_000 && Date.parse(burst.lastAt) <= now);
1785
+ if (opts.json) console.log(JSON.stringify({ rows: selected, bursts }));
1786
+ else {
1787
+ for (const row of selected) console.log(`${row.at} run ${row.runId} · ${row.reason} · status=${row.status} signal=${row.signal ?? '-'}`);
1788
+ for (const burst of bursts) console.log(`${burst.firstAt}–${burst.lastAt} 묶음 ${burst.reason} ×${burst.count} · ${burst.runIds.join(', ')}`);
1789
+ }
1790
+ } catch (error) {
1791
+ console.error(humanErrorLine(error));
1792
+ process.exitCode = 1;
1793
+ }
1794
+ });
1599
1795
  installDeliverableVerifyCliCommand(harnessCmd, deps.deliverableVerify);
1600
1796
  installHarnessProcessObservationCommand(harnessCmd, deps.processObservation);
1601
1797
  installHarnessBudgetCommand(harnessCmd);
@@ -0,0 +1,24 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { existsSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { recordRunExit, type RunExit } from './harness-incidents.js';
5
+
6
+ const [root, json, mode] = process.argv.slice(2);
7
+ if (!root || !json) throw new Error('root and exit are required');
8
+ const exit = JSON.parse(json) as RunExit;
9
+ if (mode === 'hold') {
10
+ const db = new Database(join(root, '.incident-write-lock.sqlite'));
11
+ db.exec('BEGIN IMMEDIATE');
12
+ process.stdout.write('locked\n');
13
+ // Released by a file, not a signal: SIGUSR2 handlers are not delivered reliably under Bun on macOS.
14
+ const release = join(root, 'release-hold');
15
+ await new Promise<void>((done) => {
16
+ const timer = setInterval(() => { if (existsSync(release)) { clearInterval(timer); done(); } }, 20);
17
+ });
18
+ db.exec('COMMIT');
19
+ db.close();
20
+ recordRunExit(exit, root);
21
+ process.exit(0);
22
+ } else {
23
+ recordRunExit(exit, root);
24
+ }
@@ -0,0 +1,129 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+
5
+ export interface RunExit {
6
+ runId: string;
7
+ reason: string;
8
+ status: number | null;
9
+ signal: string | null;
10
+ at: string;
11
+ }
12
+
13
+ export interface IncidentBurst {
14
+ reason: string;
15
+ count: number;
16
+ firstAt: string;
17
+ lastAt: string;
18
+ runIds: string[];
19
+ }
20
+
21
+ const KST_OFFSET_MS = 9 * 60 * 60_000;
22
+ const INCIDENT_FILE = /^\d{4}-\d{2}-\d{2}\.jsonl$/;
23
+
24
+ function kstDate(at: number): string {
25
+ return new Date(at + KST_OFFSET_MS).toISOString().slice(0, 10);
26
+ }
27
+
28
+ /** Read only recorded exits. Missing ledgers are not evidence that no runs failed. */
29
+ export function readRunExits(root: string): RunExit[] {
30
+ const dir = join(root, 'incidents');
31
+ if (!existsSync(dir)) return [];
32
+ const rows: RunExit[] = [];
33
+ const seen = new Set<string>();
34
+ for (const file of readdirSync(dir).filter((name) => INCIDENT_FILE.test(name)).sort()) {
35
+ const path = join(dir, file);
36
+ let text: string;
37
+ try {
38
+ if (!statSync(path).isFile()) continue;
39
+ text = readFileSync(path, 'utf8');
40
+ } catch (error) {
41
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') continue;
42
+ throw error;
43
+ }
44
+ for (const line of text.split('\n')) {
45
+ if (!line.trim()) continue;
46
+ let value: unknown;
47
+ try { value = JSON.parse(line); } catch { continue; }
48
+ if (!value || typeof value !== 'object') continue;
49
+ const row = value as Partial<RunExit>;
50
+ if (typeof row.runId !== 'string' || !row.runId.trim() || seen.has(row.runId)
51
+ || typeof row.reason !== 'string' || typeof row.at !== 'string' || !Number.isFinite(Date.parse(row.at))
52
+ || !(row.status === null || typeof row.status === 'number')
53
+ || !(row.signal === null || typeof row.signal === 'string')) continue;
54
+ seen.add(row.runId);
55
+ rows.push(row as RunExit);
56
+ }
57
+ }
58
+ return rows.sort((a, b) => Date.parse(a.at) - Date.parse(b.at) || a.runId.localeCompare(b.runId));
59
+ }
60
+
61
+ /** SQLite's process-owned write lock serializes ledger checks and appends across processes. */
62
+ export function recordRunExit(row: RunExit, root: string): void {
63
+ if (!row.runId?.trim()) return;
64
+ const at = Date.parse(row.at);
65
+ if (!Number.isFinite(at)) throw new Error('invalid incident exit time');
66
+ const dir = join(root, 'incidents');
67
+ mkdirSync(dir, { recursive: true });
68
+ const lock = new Database(join(root, '.incident-write-lock.sqlite'));
69
+ try {
70
+ lock.exec('PRAGMA busy_timeout = 60000');
71
+ lock.exec('BEGIN IMMEDIATE');
72
+ try {
73
+ if (!readRunExits(root).some((record) => record.runId === row.runId)) {
74
+ appendFileSync(join(dir, `${kstDate(at)}.jsonl`), `${JSON.stringify(row)}\n`);
75
+ }
76
+ lock.exec('COMMIT');
77
+ } catch (error) {
78
+ lock.exec('ROLLBACK');
79
+ throw error;
80
+ }
81
+ } finally {
82
+ lock.close();
83
+ }
84
+ }
85
+
86
+ /** Non-overlapping, maximal same-reason windows; no run contributes to two bursts. */
87
+ export function detectBursts(rows: readonly RunExit[], { windowMs = 60_000, threshold = 3 }: {
88
+ windowMs?: number; threshold?: number;
89
+ } = {}): IncidentBurst[] {
90
+ const bursts: IncidentBurst[] = [];
91
+ const byReason = new Map<string, RunExit[]>();
92
+ const seen = new Set<string>();
93
+ for (const row of rows) {
94
+ if (!row.runId || seen.has(row.runId) || !Number.isFinite(Date.parse(row.at))) continue;
95
+ seen.add(row.runId);
96
+ const group = byReason.get(row.reason) ?? [];
97
+ group.push(row);
98
+ byReason.set(row.reason, group);
99
+ }
100
+ for (const [reason, group] of byReason) {
101
+ group.sort((a, b) => Date.parse(a.at) - Date.parse(b.at) || a.runId.localeCompare(b.runId));
102
+ for (let start = 0; start < group.length;) {
103
+ let end = start + 1;
104
+ while (end < group.length && Date.parse(group[end]!.at) - Date.parse(group[start]!.at) <= windowMs) end++;
105
+ if (end - start < threshold) { start++; continue; }
106
+ const members = group.slice(start, end);
107
+ bursts.push({ reason, count: members.length, firstAt: members[0]!.at,
108
+ lastAt: members[members.length - 1]!.at, runIds: members.map((row) => row.runId) });
109
+ start = end;
110
+ }
111
+ }
112
+ return bursts.sort((a, b) => Date.parse(a.firstAt) - Date.parse(b.firstAt) || a.reason.localeCompare(b.reason));
113
+ }
114
+
115
+ export function recentBurst(root: string, now: Date | number, { lookbackMs = 15 * 60_000 }: {
116
+ lookbackMs?: number;
117
+ } = {}): IncidentBurst | null {
118
+ const time = typeof now === 'number' ? now : now.getTime();
119
+ const recent = detectBursts(readRunExits(root)).filter((burst) => {
120
+ const last = Date.parse(burst.lastAt);
121
+ return last <= time && last >= time - lookbackMs;
122
+ });
123
+ return recent.sort((a, b) => Date.parse(a.lastAt) - Date.parse(b.lastAt)).at(-1) ?? null;
124
+ }
125
+
126
+ export function formatIncidentBurstWarning(burst: IncidentBurst): string {
127
+ const time = new Date(Date.parse(burst.lastAt) + KST_OFFSET_MS).toISOString().slice(11, 16);
128
+ return `⚠ 최근 묶음 사고: ${burst.reason} ×${burst.count} (${time} KST) — 원인 확인 전 재발사는 중복·재사고 위험 · elanous harness incidents`;
129
+ }
@@ -7,15 +7,51 @@
7
7
  //
8
8
  // ⛔ 불변식: 기록된 시작 시각과 실제 프로세스 시작 시각이 다르면 신호를 보내지 않는다(PID 재사용 방지).
9
9
  // ⛔ 불변식: `elanous.run=<runId>` 라벨이 붙은 Job 만 지운다.
10
+ // ⛔ 불변식: 짧은 runId 의 후보가 둘 이상이면 프로세스 신호·Job 삭제 전에 중단한다.
10
11
 
11
- import { readFileSync, readlinkSync } from 'node:fs';
12
+ import { existsSync, readdirSync, readFileSync, readlinkSync } from 'node:fs';
12
13
  import { isAbsolute, join, resolve } from 'node:path';
13
14
  import { spawnSync } from 'node:child_process';
14
15
  import { debug } from '../debug/log.js';
15
16
  import { selfDevRunsDir } from '../self-dev/run-store.js';
16
- import { loadRunLedger, runLedgerDir } from '../self-implement/run-ledger.js';
17
+ import { loadRunLedger, resolveFederatedRunLedgerDirectories, runLedgerDir } from '../self-implement/run-ledger.js';
17
18
 
18
19
  const RUN_ID = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
20
+ const FULL_RUN_ID = /^run-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
21
+
22
+ export class AmbiguousHarnessStopRunIdError extends Error {
23
+ constructor(given: string, candidates: readonly string[]) {
24
+ super(`harness stop ${given}: 더 긴 id 를 주십시오\n${candidates.map((id) => ` ${id}`).join('\n')}`);
25
+ this.name = 'AmbiguousHarnessStopRunIdError';
26
+ }
27
+ }
28
+
29
+ /** List names, not ledger bodies: a run may have written its pid.json before its ledger. */
30
+ export function stopRunIdCandidates(given: string, ledgerDirs: readonly string[], runsDir: string): string[] {
31
+ const found = new Set<string>();
32
+ for (const dir of ledgerDirs) {
33
+ let names: string[];
34
+ try { names = readdirSync(dir); }
35
+ catch (error) {
36
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') continue;
37
+ throw error;
38
+ }
39
+ for (const name of names) {
40
+ if (!name.endsWith('.jsonl')) continue;
41
+ const id = name.slice(0, -'.jsonl'.length);
42
+ if (FULL_RUN_ID.test(id) && id.startsWith(given)) found.add(id);
43
+ }
44
+ }
45
+ try {
46
+ for (const entry of readdirSync(runsDir, { withFileTypes: true })) {
47
+ if (entry.isDirectory() && entry.name.startsWith(given) && FULL_RUN_ID.test(entry.name)
48
+ && existsSync(join(runsDir, entry.name, 'pid.json'))) found.add(entry.name);
49
+ }
50
+ } catch (error) {
51
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
52
+ }
53
+ return [...found].sort();
54
+ }
19
55
  /** `pid.json` 의 `startedAt` 은 `Date.now() − uptime` 근삿값이다 — 이만큼의 차이는 같은 프로세스로 본다. */
20
56
  export const START_TOLERANCE_MS = 5_000;
21
57
 
@@ -101,6 +137,7 @@ function scanStopProcessTable(runId: string, goalPath: string | null): StopProce
101
137
 
102
138
  export interface HarnessStopDeps {
103
139
  readPidRecord: (runId: string) => PidRecord | null;
140
+ runIdCandidates?: (given: string) => string[];
104
141
  readGoalPath: (runId: string) => string | null;
105
142
  scanProcesses: (runId: string, goalPath: string | null) => StopProcessScan;
106
143
  /** 살아 있는 프로세스의 시작 시각(ms) · 없으면 null. */
@@ -115,6 +152,7 @@ export interface HarnessStopDeps {
115
152
 
116
153
  export interface HarnessStopResult {
117
154
  runId: string;
155
+ givenRunId?: string;
118
156
  process: 'stopped' | 'killed' | 'absent' | 'owner-mismatch' | 'unmeasured' | 'dry-run';
119
157
  pid?: number;
120
158
  candidates: StopProcessCandidate[];
@@ -154,6 +192,10 @@ export function defaultHarnessStopDeps(overrides: Partial<HarnessStopDeps> = {})
154
192
  };
155
193
  return {
156
194
  readPidRecord: (runId) => readPidRecordFrom(selfDevRunsDir(), runId),
195
+ runIdCandidates: (given) => {
196
+ const ledgerDirs = new Set([runLedgerDir(), ...resolveFederatedRunLedgerDirectories({})]);
197
+ return stopRunIdCandidates(given, [...ledgerDirs], selfDevRunsDir());
198
+ },
157
199
  readGoalPath: stopGoalPathFromLedger,
158
200
  scanProcesses: scanStopProcessTable,
159
201
  processStartMs: psProcessStartMs,
@@ -172,6 +214,13 @@ export function defaultHarnessStopDeps(overrides: Partial<HarnessStopDeps> = {})
172
214
 
173
215
  export async function stopHarnessRun(runId: string, deps: HarnessStopDeps, contexts?: readonly string[], dryRun = false): Promise<HarnessStopResult> {
174
216
  if (!RUN_ID.test(runId)) throw new Error(`invalid run id: ${runId}`);
217
+ const given = runId;
218
+ if (!FULL_RUN_ID.test(given)) {
219
+ const matches = [...new Set(deps.runIdCandidates?.(given) ?? [])].sort();
220
+ if (matches.length === 1) runId = matches[0]!;
221
+ debug.log('harness.stop', 'run-id-resolved', { given, resolved: runId, candidates: matches });
222
+ if (matches.length > 1) throw new AmbiguousHarnessStopRunIdError(given, matches);
223
+ }
175
224
  // ── ⓐ 프로세스 ────────────────────────────────────────────
176
225
  let processState: HarnessStopResult['process'] = 'absent';
177
226
  const record = deps.readPidRecord(runId);
@@ -235,7 +284,7 @@ export async function stopHarnessRun(runId: string, deps: HarnessStopDeps, conte
235
284
  if (r.status === 0) jobsDeleted += r.stdout.split('\n').filter((line) => line.startsWith('job.batch/')).length;
236
285
  else debug.log('harness.stop', 'job-delete-failed', { runId, context, error: r.stderr.slice(0, 200) }, { level: 'warn' });
237
286
  }
238
- const result: HarnessStopResult = { runId, process: processState, ...(record ? { pid: record.pid } : {}), candidates, scanned: scan.status === 'ok', pidRecordFound: record !== null, dryRun, jobsDeleted, contexts: targets };
287
+ const result: HarnessStopResult = { runId, ...(given !== runId ? { givenRunId: given } : {}), process: processState, ...(record ? { pid: record.pid } : {}), candidates, scanned: scan.status === 'ok', pidRecordFound: record !== null, dryRun, jobsDeleted, contexts: targets };
239
288
  debug.log('harness.stop', 'stopped', { runId, process: processState, jobsDeleted, contexts: targets.length });
240
289
  return result;
241
290
  }
@@ -249,6 +298,7 @@ export function formatHarnessStop(r: HarnessStopResult): string {
249
298
  'owner-mismatch': '소유 불일치(건드리지 않음 — PID 가 다른 프로세스로 재사용됐다)',
250
299
  };
251
300
  return [
301
+ ...(r.givenRunId ? [`↳ ${r.givenRunId} → ${r.runId}(전체)`] : []),
252
302
  `harness stop ${r.runId}`,
253
303
  ` 프로세스: ${label[r.process]}${r.pid ? ` · pid ${r.pid}` : ''}`,
254
304
  ...(!r.scanned && r.process !== 'unmeasured' ? [' 프로세스 표: 못 잼(기록 밖 발사 프로세스는 확인하지 못함)'] : []),
@@ -182,7 +182,7 @@ export function decideAndObserveClarification(
182
182
  impact: candidate.impact,
183
183
  action: decision.action,
184
184
  reason: decision.reason,
185
- recommendedOption: decision.recommendedOption,
185
+ ...(context.phase === 'execution' ? {} : { recommendedOption: decision.recommendedOption }),
186
186
  budgetUsed: context.budget.used,
187
187
  budgetLimit: context.budget.limit,
188
188
  });
@@ -210,6 +210,8 @@ export function toAskUserQuestionRequest(decision: ClarificationDecision): AskUs
210
210
  header: 'Direction',
211
211
  question: `${candidate.prompt}\n\nRecommended: ${decision.recommendedOption}. ${candidate.whyNow}`,
212
212
  options: candidate.options.map(({ recommended: _recommended, ...option }) => option),
213
+ impact: candidate.impact,
214
+ recommendedIndex: candidate.options.findIndex(option => option.recommended),
213
215
  includeOther: true,
214
216
  };
215
217
  return { questions: [question] };