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
@@ -79,6 +79,7 @@ import { classifyReviewProviderFailure } from '../self-dev/review-provider-fallb
79
79
  import { classifyError } from '../session-runtime/retry-policy.js';
80
80
  import { decomposeSelfDevGoal, inferHotPaths, type SelfDevDecomposeOptions } from '../self-dev/decompose.js';
81
81
  import { boundaryGlobs, boundaryViolations } from './goal-boundary.js';
82
+ import { askAtExecution, type ExecutionClarificationResult } from './execution-clarification.js';
82
83
  import { targetScopedGoalText } from './goal-text-path-scope.js';
83
84
  import type { SelfDevGoalType } from '../self-dev/orchestrate.js';
84
85
  import { DEFAULT_BRANCH_WORKTREE_BASE, gateGitResidue, observeGitResidue } from '../git-fs/worktree.js';
@@ -967,6 +968,8 @@ export interface SelfImplementSeams {
967
968
  /** 변경 파일 목록 — 게이트 «라우터»가 「코드가 바뀌었나」를 판정하는 입력.
968
969
  * ⛔ 미지정이면 실물 `changedFiles(cwd)` 를 쓴다. 못 얻으면 `undefined` 이고, 라우터는 그때 «돌린다»(fail-safe). */
969
970
  changedFilesForGateRoute?: (cwd: string) => readonly string[] | undefined;
971
+ /** An execution choice is made only after a measured change crosses the authored target paths. */
972
+ askExecutionClarification?: typeof askAtExecution;
970
973
  /** ★ ⑤ 내부 리뷰 seam(2026-07-21·review-gated merge) — gate 통과 diff 를 리뷰어(reviewPullRequest·
971
974
  * agent-substrate·staged 하니스와 동일 엔진)가 비평. verdict fail(mustFix) → rework 재주입(A 확장),
972
975
  * clean(pass/warn) → 병합 결정. 미주입 시 리뷰 스킵(gate 통과=바로 결정·종전 동작). */
@@ -1082,7 +1085,7 @@ export const SELF_IMPLEMENT_PROGRESS_STAGES = [
1082
1085
  'gating', 'gated', 'reviewing', 'reviewed', 'awaiting-approval',
1083
1086
  'pr-opening', 'pr-opened', 'merging', 'merged', 'worktree-completed',
1084
1087
  'aborted', 'gate-failed', 'review-blocked', 'merge-conflict', 'pr-declined',
1085
- 'awaiting-clarification',
1088
+ 'awaiting-clarification', 'parked',
1086
1089
  // ⭐⭐ 🩸 2026-09-12 — ***「선언한 대상을 «하나도» 안 만들고 끝났다」를 사람 표면에 낸다.***
1087
1090
  // ⛔ 기존 단계 이름을 «빌려 쓰지 않는다» — `progress()` 는 단계 이름으로 원장 행을 만들므로
1088
1091
  // 빌려 쓰면 그 단계 집계가 오염된다([T] 2026-09-12 · `#17767` 이후 계약).
@@ -6268,6 +6271,39 @@ async function runSelfImplementInner(
6268
6271
  return { ok: false, stage: 'aborted', node: 'implement', ...resolveRunOutcome({ termination: 'abandoned' }), sessionId, worktreePath: wt.path, branch: wt.branch, quotaExhaustionAssessment: quotaExhaustionAssessmentForRun(), ...(impl.completionDisposition ? { completionDisposition: impl.completionDisposition } : {}), ...(pr ? { prUrl: pr.url, prNumber: pr.number } : {}), detail: impl.summary };
6269
6272
  }
6270
6273
 
6274
+ // Measure the existing declared-target boundary after implementation, before gate/PR.
6275
+ // Unknown declaration or changed files cannot justify an invented choice.
6276
+ let gateRouteFiles: readonly string[] | undefined;
6277
+ try { gateRouteFiles = (s.changedFilesForGateRoute ?? changedFiles)(wt.path); } catch { gateRouteFiles = undefined; }
6278
+ if (opts.goalFile) {
6279
+ let goalDocument: string | undefined;
6280
+ try { goalDocument = readFileSync(opts.goalFile, 'utf8'); } catch { /* unknown scope */ }
6281
+ const scope = detectDeclaredScopeDiff({ goalDocument, changedFiles: gateRouteFiles?.filter(file => !harnessSeededPaths.includes(file)), nameCap: gateRouteFiles?.length });
6282
+ if (scope.status === 'known' && scope.outsideCount !== null && scope.outsideCount > 0) {
6283
+ const result: ExecutionClarificationResult = await (s.askExecutionClarification ?? askAtExecution)({
6284
+ id: 'execution_scope', decision: 'Changing files outside the authored target paths',
6285
+ prompt: 'Implementation changed files outside the authored target paths. Keep the original scope or expand it?',
6286
+ whyNow: 'Continuing to gate or publish these edits would expand the requested scope.',
6287
+ impact: scope.outsideNames.some(file => {
6288
+ if (existsSync(join(wt.path, file))) return false;
6289
+ const tracked = runGitCommand(wt.path, ['ls-files', '--error-unmatch', '--', file], { encoding: 'utf8', timeout: 20_000 });
6290
+ return tracked.status === 0;
6291
+ }) ? 'critical' : 'high',
6292
+ options: [
6293
+ { label: 'Stay in scope', description: 'Do not accept out-of-scope changes.', recommended: true },
6294
+ { label: 'Expand scope', description: 'Accept the changed files outside the target paths.' },
6295
+ ],
6296
+ }, { runId }, { writeRunLedger: s.writeRunLedger });
6297
+ if ('parked' in result || 'deferred' in result || result.choice !== 'Expand scope') {
6298
+ finalizeSupervisorDeliveries('execution-scope-not-approved');
6299
+ observe('execution-scope-held', { outsideCount: scope.outsideCount, disposition: 'parked' in result ? 'parked' : 'in-scope-only' });
6300
+ if ('parked' in result) progress('parked', `critical 질문 ${result.questionId} 미응답 — 이 런만 대기`);
6301
+ return { ok: false, stage: 'parked' in result ? 'parked' : 'soft-stopped', node: 'implement', ...resolveRunOutcome({ termination: 'abandoned' }), sessionId, worktreePath: wt.path, branch: wt.branch,
6302
+ detail: 'parked' in result ? `critical-unanswered: ${result.questionId}` : 'Out-of-scope edits held in the worktree; gate and publication skipped.' };
6303
+ }
6304
+ }
6305
+ }
6306
+
6271
6307
  // ⛔⭐ 이 템플릿이 `gate` 노드를 «안 가지면» 그 단계는 돌지 않는다.
6272
6308
  // 📌 research-loop 이 그렇다 — 문서만 바뀐 판에는 «변경 파일 범위» 게이트가 돌 시험이 없다.
6273
6309
  // ⚠️ 꺼진 상태에서는 언제나 implement-loop 이라 참이다 ⇒ 오늘과 «같은» 걸음이다.
@@ -6275,8 +6311,6 @@ async function runSelfImplementInner(
6275
6311
  // 기본이 꺼짐이라 지금 폭발 반경은 0이고, 기본을 켜기 «전»에 이 구멍을 먼저 막아야 한다.
6276
6312
  // ⛔⭐ 라우터가 판정한다 — 값은 «코드»가 낸다(RFC §4.3 ⑵). 「모른다」는 «돌린다» 쪽이다.
6277
6313
  // 🚨 이 줄이 2026-09-08 승격의 구멍 ⓐ 를 닫는다: research 골이 «코드»를 만지면 시험이 돈다.
6278
- let gateRouteFiles: readonly string[] | undefined;
6279
- try { gateRouteFiles = (s.changedFilesForGateRoute ?? changedFiles)(wt.path); } catch { gateRouteFiles = undefined; }
6280
6314
  const gateRoute = routeGate(graphTemplate, gateRouteFiles);
6281
6315
  const runsGate = gateRoute.runsGate;
6282
6316
  progress('gating', !runsGate
@@ -13,7 +13,7 @@ export type PipelineNodeId =
13
13
 
14
14
  /** Each pipeline node's possible terminal stages. Nodes without a terminal exit use an empty array. */
15
15
  export const TERMINAL_STAGES_BY_NODE = {
16
- implement: ['aborted', 'timed-out', 'soft-stopped'],
16
+ implement: ['aborted', 'timed-out', 'soft-stopped', 'parked'],
17
17
  gate: [],
18
18
  review: ['review-blocked'],
19
19
  rework: ['gate-failed', 'review-blocked'],
@@ -1,4 +1,4 @@
1
- export type RunStatus = 'completed' | 'failed' | 'cancelled';
1
+ export type RunStatus = 'completed' | 'failed' | 'cancelled' | 'parked';
2
2
  /** ⭐ `crashed` — ***잡히지 않은 예외로 죽어 종결을 스스로 못 적은*** 런 (2026-08-11 · 🅣⊕🅢 어휘 합의).
3
3
  * ⛔ `aborted`(스스로 중단을 «판정»했다)와 다른 값이다 — 이쪽은 «판정할 겨를이 없었다».
4
4
  * 📏 왜 생겼나: 실측 미완 57 중 ≈38 이 어느 원장에도 종결이 없었고, 멈춘 자리가 전부 「노드 안」이었다. */
@@ -20,7 +20,8 @@ export type SelfImplementStage =
20
20
  | 'aborted'
21
21
  | 'timed-out'
22
22
  | 'pr-declined'
23
- | 'soft-stopped';
23
+ | 'soft-stopped'
24
+ | 'parked';
24
25
 
25
26
  const STAGE_OUTCOMES = {
26
27
  merged: { runStatus: 'completed' },
@@ -34,9 +35,10 @@ const STAGE_OUTCOMES = {
34
35
  'timed-out': { runStatus: 'failed', failureKind: 'timed-out' },
35
36
  'pr-declined': { runStatus: 'cancelled' },
36
37
  'soft-stopped': { runStatus: 'cancelled' },
38
+ parked: { runStatus: 'parked' },
37
39
  } satisfies Record<SelfImplementStage, MappedRunOutcome>;
38
40
 
39
- const RUN_STATUSES = new Set<RunStatus>(['completed', 'failed', 'cancelled']);
41
+ const RUN_STATUSES = new Set<RunStatus>(['completed', 'failed', 'cancelled', 'parked']);
40
42
 
41
43
  /** Runtime guard for the run-status payload produced by observeRunOutcome. */
42
44
  export function isRunStatus(value: unknown): value is RunStatus {
@@ -109,6 +109,8 @@ export interface RunTurnOpts {
109
109
  * delegated coding agent's approval prompts back to the originating
110
110
  * chat via `delegate_code_agent`. */
111
111
  hitlConfirmChannel?: import('../hitl/confirm.js').ConfirmChannel;
112
+ /** Verified by the hosting bot from the inbound user id against its server-side owner allowlist. */
113
+ verifiedOwner?: { id: string };
112
114
  /** Paired multi-option question channel (Telegram sets this). Surface
113
115
  * runTurnImpls thread it into `delegate_code_agent` so a delegated
114
116
  * agent's structured questions render as option buttons in the
@@ -202,6 +202,8 @@ export interface SessionMeta {
202
202
  * 아님). ~/.elanous 공유 스토어 세션은 전부 'prod'. append 는 이 값을 보존(생성자 귀속).
203
203
  * 구 세션엔 부재(tolerant read) — 미상 = 필터 비매치. */
204
204
  originInstance?: string;
205
+ /** Project association, omitted for sessions without an assigned project (including legacy records). */
206
+ projectId?: string;
205
207
  /** For telegram-sourced sessions; undefined for cli. */
206
208
  tgChatId?: number;
207
209
  tgThreadId?: number;
@@ -383,6 +385,7 @@ export interface CreateSessionOpts {
383
385
  tgBotId?: string;
384
386
  /** Stable identity for a persona's resident conversation. */
385
387
  personaId?: string;
388
+ projectId?: string;
386
389
  title?: string;
387
390
  /** 표면 origin(cli/pwa/tg/dc/harness) — source 유니온 밖의 세밀 라벨(PWA 챗 구분용). */
388
391
  origin?: string;
@@ -534,6 +537,7 @@ export function createSession(opts: CreateSessionOpts = {}, root: string = sessi
534
537
  tgThreadId: opts.tgThreadId,
535
538
  ...(opts.tgBotId ? { tgBotId: opts.tgBotId } : {}),
536
539
  ...(opts.personaId ? { personaId: opts.personaId } : {}),
540
+ ...(opts.projectId !== undefined ? { projectId: opts.projectId } : {}),
537
541
  tgSessionKey,
538
542
  };
539
543
  writeFileSync(sessionFile(id, root), '', 'utf-8');
@@ -575,6 +579,7 @@ export function adoptSession(id: string, opts: CreateSessionOpts = {}, root: str
575
579
  sourceKind: opts.sourceKind ?? defaultSessionSourceKind(source),
576
580
  ...(opts.forkedFromId ? { forkedFromId: opts.forkedFromId } : {}),
577
581
  ...(origin ? { origin } : {}),
582
+ ...(opts.projectId !== undefined ? { projectId: opts.projectId } : {}),
578
583
  originInstance: resolveInstanceName(),
579
584
  ...(opts.tgChatId != null ? { tgChatId: opts.tgChatId } : {}),
580
585
  ...(opts.tgThreadId != null ? { tgThreadId: opts.tgThreadId } : {}),
@@ -601,6 +606,7 @@ export function forkSessionFromHistory(
601
606
  tgThreadId: opts.tgThreadId,
602
607
  title: opts.title,
603
608
  origin: opts.origin,
609
+ projectId: opts.projectId,
604
610
  }, root);
605
611
  for (const msg of opts.messages) {
606
612
  const content = typeof msg.content === 'string' ? msg.content : '';
@@ -738,6 +744,8 @@ export interface ListSessionsOpts {
738
744
  tgThreadId?: number;
739
745
  /** 생성 인스턴스 정확 일치(prod · test:<repo> …). 구 세션(부재)은 비매치. */
740
746
  originInstance?: string;
747
+ /** Project id exact match; sessions without the field do not match. */
748
+ projectId?: string;
741
749
  /** 표면 origin 정확 일치(cli | pwa | tg | dc | harness). */
742
750
  origin?: string;
743
751
  /** 제외할 origin(들) — 예: ['harness'] 로 하니스 자식 세션을 사용자 대화 목록에서 숨김. */
@@ -774,6 +782,7 @@ export function listSessions(opts: ListSessionsOpts = {}, root: string = session
774
782
  idx = idx.filter(m => !m.sourceKind || !opts.excludeSourceKinds!.includes(m.sourceKind));
775
783
  }
776
784
  if (opts.originInstance) idx = idx.filter(m => m.originInstance === opts.originInstance);
785
+ if (opts.projectId !== undefined) idx = idx.filter(m => m.projectId === opts.projectId);
777
786
  if (opts.origin) idx = idx.filter(m => m.origin === opts.origin);
778
787
  if (opts.excludeOrigins && opts.excludeOrigins.length > 0) {
779
788
  idx = idx.filter((m) => {
@@ -210,6 +210,23 @@ export function capturedEnvAvailable(): boolean {
210
210
  return captured && cached !== null;
211
211
  }
212
212
 
213
+ /** Append login-shell PATH entries to the target without replacing its existing environment. */
214
+ export function mergeCapturedPath(target: NodeJS.ProcessEnv = process.env, opts: { capture?: boolean } = {}): number {
215
+ // capture: 아직 캡처 전이면 여기서 한 번 캡처한다(호출부가 getCapturedEnv 를 직접 부르지 않게 — identity-env 래칫).
216
+ const capturedPath = opts.capture || capturedEnvAvailable() ? getCapturedEnv().PATH : undefined;
217
+ const seen = new Set((target.PATH ?? '').split(':'));
218
+ const missing: string[] = [];
219
+ for (const entry of capturedPath?.split(':') ?? []) {
220
+ if (!entry || seen.has(entry)) continue;
221
+ missing.push(entry);
222
+ seen.add(entry);
223
+ }
224
+ if (missing.length) target.PATH = target.PATH ? `${target.PATH}:${missing.join(':')}` : missing.join(':');
225
+ const added = missing.length;
226
+ debug.log('shell.envbootstrap.capture', 'path-merged', { added });
227
+ return added;
228
+ }
229
+
213
230
  /** Reset cache — test seam. */
214
231
  export function resetCapturedEnvForTesting(): void {
215
232
  cached = null;
@@ -1,10 +1,15 @@
1
- import { createHash } from 'node:crypto';
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { effectiveInstanceRoot } from '../instance/resolve.js';
2
5
  import { getSecretAsync } from '../nexus/config/secrets/index.js';
3
6
  import { getUserConfig } from '../user-config.js';
4
7
 
5
8
  export type DirectiveSource = 'telegram' | 'pwa' | 'tui' | 'cli';
6
9
  export interface DirectiveIssue { id: string; identifier: string; title: string; description?: string | null }
10
+ export interface DirectiveOrigin { chatId: number; botId?: string; threadId?: number }
7
11
  export interface DirectiveDeps {
12
+ root?: string;
8
13
  fetch?: typeof fetch;
9
14
  getSecret?: (id: string) => Promise<string | undefined>;
10
15
  team?: string;
@@ -31,8 +36,50 @@ async function graphql<T>(key: string, query: string, variables: Record<string,
31
36
  return payload.data;
32
37
  }
33
38
 
39
+ function originPath(root: string, identifier: string): string {
40
+ if (!/^[a-zA-Z0-9_-]+$/.test(identifier)) throw new Error('Invalid directive identifier');
41
+ return join(root, 'steward', 'origins', `${identifier}.json`);
42
+ }
43
+
44
+ type TelegramOrigin = import('../autopilot/mission-origin.js').MissionOrigin & { channel: 'telegram'; chatId: number };
45
+
46
+ function validOrigin(value: unknown): value is TelegramOrigin {
47
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
48
+ const origin = value as Record<string, unknown>;
49
+ return origin.channel === 'telegram' && Number.isSafeInteger(origin.chatId) &&
50
+ (origin.botId === undefined || typeof origin.botId === 'string' && /^\d+$/.test(origin.botId)) &&
51
+ (origin.threadId === undefined || Number.isSafeInteger(origin.threadId));
52
+ }
53
+
54
+ export function loadDirectiveOrigins(root: string, identifier: string): TelegramOrigin[] {
55
+ try {
56
+ const value: unknown = JSON.parse(readFileSync(originPath(root, identifier), 'utf8'));
57
+ const origins = Array.isArray(value) ? value : [value];
58
+ return origins.filter(validOrigin);
59
+ } catch { return []; }
60
+ }
61
+
62
+ export function saveDirectiveOrigin(root: string, identifier: string, origin: DirectiveOrigin): void {
63
+ if (!Number.isSafeInteger(origin.chatId) || origin.threadId !== undefined && !Number.isSafeInteger(origin.threadId)) throw new Error('Invalid directive origin');
64
+ const path = originPath(root, identifier);
65
+ const next: TelegramOrigin = { channel: 'telegram', chatId: origin.chatId,
66
+ ...(origin.botId && /^\d+$/.test(origin.botId) ? { botId: origin.botId } : {}),
67
+ ...(origin.threadId !== undefined ? { threadId: origin.threadId } : {}) };
68
+ const origins = loadDirectiveOrigins(root, identifier);
69
+ if (origins.some(item => JSON.stringify(item) === JSON.stringify(next))) return;
70
+ origins.push(next);
71
+ mkdirSync(join(root, 'steward', 'origins'), { recursive: true });
72
+ const temp = `${path}.${process.pid}.${randomUUID()}.tmp`;
73
+ writeFileSync(temp, JSON.stringify(origins.length === 1 ? next : origins));
74
+ renameSync(temp, path);
75
+ }
76
+
77
+ export function loadDirectiveOrigin(root: string, identifier: string): TelegramOrigin | null {
78
+ return loadDirectiveOrigins(root, identifier)[0] ?? null;
79
+ }
80
+
34
81
  /** The same entry point is used by the CLI and conversational surfaces. */
35
- export async function addDirective(text: string, options: { source?: DirectiveSource; dryRun?: boolean } = {}, deps: DirectiveDeps = {}): Promise<{ status: 'created' | 'repeated' | 'dry-run'; issue?: string; hash: string }> {
82
+ export async function addDirective(text: string, options: { source?: DirectiveSource; dryRun?: boolean; origin?: DirectiveOrigin } = {}, deps: DirectiveDeps = {}): Promise<{ status: 'created' | 'repeated' | 'dry-run'; issue?: string; hash: string }> {
36
83
  const original = text.trim();
37
84
  if (!original) throw new Error('directive text is required');
38
85
  const source = options.source ?? 'cli';
@@ -74,6 +121,7 @@ export async function addDirective(text: string, options: { source?: DirectiveSo
74
121
  'mutation($input:CommentCreateInput!){commentCreate(input:$input){success}}',
75
122
  { input: { issueId: duplicate.id, body: `다시 말함 · ${source} · ${at}\n\n${original}` } });
76
123
  if (!result.commentCreate?.success) throw new Error('Linear comment creation failed');
124
+ if (source === 'telegram' && options.origin) saveDirectiveOrigin(deps.root ?? effectiveInstanceRoot(), duplicate.identifier, options.origin);
77
125
  return { status: 'repeated', issue: duplicate.identifier, hash };
78
126
  }
79
127
  const labels = await request<{ issueLabels: { nodes: Array<{ id: string; name: string }> } }>(
@@ -90,6 +138,7 @@ export async function addDirective(text: string, options: { source?: DirectiveSo
90
138
  'mutation($input:IssueCreateInput!){issueCreate(input:$input){success issue{id identifier title}}}',
91
139
  { input: { teamId, title: original.slice(0, 240), description: `${original}\n\n출처: ${source}\n시각: ${at}\n[directive-hash:${hash}]`, labelIds: [labelId], priority: 0 } });
92
140
  if (!created.issueCreate?.success || !created.issueCreate.issue?.identifier) throw new Error('Linear issue creation failed');
141
+ if (source === 'telegram' && options.origin) saveDirectiveOrigin(deps.root ?? effectiveInstanceRoot(), created.issueCreate.issue.identifier, options.origin);
93
142
  return { status: 'created', issue: created.issueCreate.issue.identifier, hash };
94
143
  }
95
144
 
@@ -0,0 +1,250 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { closeSync, mkdirSync, openSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
3
+ import { join, resolve } from 'node:path';
4
+ import { debug } from '../debug/log.js';
5
+ import { redactSecrets } from '../task-cards/card-store.js';
6
+ import type { ScheduledDecision, StewardSettings, TriageIssue } from './triage.js';
7
+
8
+ /** `completed` = the run finished without a merge (a non-code artifact, or nothing to deliver) — never reported as a failure. */
9
+ export type LaunchStatus = 'shadow' | 'launched' | 'skipped-budget' | 'failed' | 'running' | 'merged' | 'completed';
10
+ export interface LaunchEntry {
11
+ issue: string;
12
+ title: string;
13
+ source: string;
14
+ command: string;
15
+ status: LaunchStatus;
16
+ runId?: string;
17
+ pid?: number;
18
+ log?: string;
19
+ reason?: string;
20
+ prNumber?: number;
21
+ prUrl?: string;
22
+ reported?: boolean;
23
+ notified?: 'sent' | 'shadow' | 'skipped-no-origin' | 'failed';
24
+ notifyAttempts?: number;
25
+ notifyRecipients?: Record<string, { notified: 'sent' | 'failed'; attempts: number }>;
26
+ notifyPreview?: { hasChatId: boolean; kind: 'file' | 'link' };
27
+ artifactRef?: string;
28
+ artifactBranch?: string;
29
+ artifactWorktree?: string;
30
+ awaitingMerge?: boolean;
31
+ }
32
+ export interface HitlEntry { issue: string; reason: string; raised: boolean; attempted?: boolean }
33
+ export interface LaunchLedger { launches: Record<string, LaunchEntry>; hitl: Record<string, HitlEntry> }
34
+ export interface LaunchItem { issue: TriageIssue; row: ScheduledDecision; command: string; source: string }
35
+ export interface LaunchPlan { launches: LaunchItem[]; hitl: Array<{ issue: TriageIssue; row: ScheduledDecision }> }
36
+ export interface LaunchDeps {
37
+ root: string;
38
+ settings: StewardSettings;
39
+ ledger: LaunchLedger;
40
+ /** Injected command boundary; the production adapter runs only argv, never a shell. */
41
+ command?: (args: string[]) => { exitCode: number; stdout: string; stderr?: string };
42
+ spawn?: (args: string[], log: string) => { pid: number };
43
+ }
44
+
45
+ const RUN_ID = /\brun-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i;
46
+ const DEFAULT_POOL = 'pool-node-b@node-b:8';
47
+ // `harness budget --json` outcomes: proceed | next-provider | wait-reset | stop. next-provider still runs (the harness switches provider itself).
48
+ const BUDGET_PROCEED: readonly string[] = ['proceed', 'next-provider'];
49
+ const cli = (args: string[]) => ['bun', 'bin/elanous.mjs', ...(process.env.NODE_ENV === 'test' ? ['--test'] : []), ...args];
50
+ const sourceOf = (issue: TriageIssue): string => /^출처: (telegram|pwa|tui|cli)$/m.exec(issue.body)?.[1] ?? `linear:${issue.identifier}`;
51
+ const promptOf = (issue: TriageIssue): string => `${issue.title}\n${issue.body.slice(0, 1500)}`;
52
+ const argvFor = (issue: TriageIssue, settings: StewardSettings): string[] =>
53
+ // Options first, then `--`: an issue title that starts with `-` must stay part of the sentence.
54
+ cli(['harness', 'say', '--substrate', 'pod', '--pod-pool', settings.podPool ?? DEFAULT_POOL, '--base', 'main', '--json', '--', promptOf(issue)]);
55
+
56
+ export function readLaunchLedger(root: string): LaunchLedger {
57
+ try {
58
+ const value = JSON.parse(readFileSync(join(root, 'steward', 'launches.json'), 'utf8')) as LaunchLedger;
59
+ if (!value || typeof value.launches !== 'object' || !value.launches || typeof value.hitl !== 'object' || !value.hitl) throw new Error('Invalid steward launch ledger');
60
+ return value;
61
+ } catch (error) {
62
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') return { launches: {}, hitl: {} };
63
+ throw error;
64
+ }
65
+ }
66
+
67
+ export function saveLaunchLedger(root: string, ledger: LaunchLedger): void {
68
+ const dir = join(root, 'steward');
69
+ mkdirSync(dir, { recursive: true });
70
+ const target = join(dir, 'launches.json');
71
+ const temp = `${target}.${process.pid}.${randomUUID()}.tmp`;
72
+ writeFileSync(temp, JSON.stringify(ledger));
73
+ renameSync(temp, target);
74
+ }
75
+
76
+ export function planLaunches(rows: ScheduledDecision[], issues: TriageIssue[], ledger: LaunchLedger, settings: StewardSettings): LaunchPlan {
77
+ const byId = new Map(issues.map(issue => [issue.identifier, issue]));
78
+ const activeCount = Object.values(ledger.launches).filter(entry => !entry.awaitingMerge && (entry.status === 'launched' || entry.status === 'running')).length;
79
+ const available = Math.max(0, (settings.maxParallel ?? 3) - activeCount);
80
+ const launches: LaunchItem[] = [];
81
+ const hitl: LaunchPlan['hitl'] = [];
82
+ for (const row of rows) {
83
+ const issue = byId.get(row.issue);
84
+ if (!issue) continue;
85
+ if (row.rung === 'hitl' || row.hitlReason || row.disposition === 'hitl') {
86
+ if (settings.launch !== 'off' && (!ledger.hitl[row.issue] || settings.launch === 'live' && !ledger.hitl[row.issue].attempted)) hitl.push({ issue, row });
87
+ continue;
88
+ }
89
+ if ((settings.launch ?? 'shadow') === 'off' || row.disposition !== 'now' || row.rung !== 4 || (ledger.launches[row.issue] && ledger.launches[row.issue].status !== 'skipped-budget' && !(settings.launch === 'live' && ledger.launches[row.issue].status === 'shadow')) || launches.length >= available) continue;
90
+ const args = argvFor(issue, settings);
91
+ launches.push({ issue, row, source: sourceOf(issue), command: args.map(arg => JSON.stringify(arg)).join(' ') });
92
+ }
93
+ debug.log('steward.launch', 'planned', { count: launches.length, hitl: hitl.length, available });
94
+ return { launches, hitl };
95
+ }
96
+
97
+ function runCommand(args: string[]): { exitCode: number; stdout: string; stderr: string } {
98
+ const result = Bun.spawnSync(args, { cwd: resolve(import.meta.dir, '../..'), stdout: 'pipe', stderr: 'pipe' });
99
+ return { exitCode: result.exitCode, stdout: result.stdout.toString(), stderr: result.stderr.toString() };
100
+ }
101
+
102
+ function spawnDetached(args: string[], log: string): { pid: number } {
103
+ const fd = openSync(log, 'a');
104
+ try {
105
+ const child = Bun.spawn(args, { cwd: resolve(import.meta.dir, '../..'), stdout: fd, stderr: fd, stdin: 'ignore', detached: true });
106
+ child.unref();
107
+ return { pid: child.pid };
108
+ } finally { closeSync(fd); }
109
+ }
110
+
111
+ function lastJson(stdout: string): Record<string, unknown> {
112
+ const line = stdout.trim().split('\n').at(-1);
113
+ const value: unknown = JSON.parse(line ?? '');
114
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Invalid CLI JSON');
115
+ return value as Record<string, unknown>;
116
+ }
117
+
118
+ /** One issue is reserved before any side effect; budget skips are retriable, never counted as a launch. */
119
+ export function launch(item: LaunchItem, deps: LaunchDeps): LaunchEntry {
120
+ const { ledger, settings, root } = deps;
121
+ if (item.row.rung !== 4 || item.row.hitlReason || item.row.disposition !== 'now' || (ledger.launches[item.issue.identifier] && ledger.launches[item.issue.identifier].status !== 'skipped-budget' && !(settings.launch === 'live' && ledger.launches[item.issue.identifier].status === 'shadow'))) throw new Error('steward launch is not eligible');
122
+ if (settings.launch === 'off') throw new Error('steward launch is off');
123
+ const key = item.issue.identifier;
124
+ const entry: LaunchEntry = { issue: key, title: redactSecrets(item.issue.title), source: item.source, command: redactSecrets(item.command), status: 'shadow' };
125
+ if (settings.launch !== 'live') {
126
+ ledger.launches[key] = entry;
127
+ saveLaunchLedger(root, ledger);
128
+ debug.log('steward.launch', 'shadow', { issue: key, command: entry.command });
129
+ return entry;
130
+ }
131
+ const command = deps.command ?? runCommand;
132
+ try {
133
+ const budget = command(cli(['harness', 'budget', '--json']));
134
+ const decision = budget.exitCode === 0 ? lastJson(budget.stdout) : {};
135
+ if (!BUDGET_PROCEED.includes(String(decision.outcome))) {
136
+ entry.status = 'skipped-budget';
137
+ entry.reason = redactSecrets(budget.exitCode === 0 ? JSON.stringify(decision.reasons ?? ['budget unavailable']) : budget.stderr ?? 'budget unavailable');
138
+ // A budget skip may be reconsidered on the next cycle, but the reason remains observable now.
139
+ ledger.launches[key] = entry;
140
+ saveLaunchLedger(root, ledger);
141
+ debug.log('steward.launch', 'skipped-budget', { issue: key, reason: entry.reason });
142
+ return entry;
143
+ }
144
+ } catch (error) {
145
+ entry.status = 'skipped-budget';
146
+ entry.reason = redactSecrets(String(error));
147
+ ledger.launches[key] = entry;
148
+ saveLaunchLedger(root, ledger);
149
+ debug.log('steward.launch', 'skipped-budget', { issue: key, reason: entry.reason });
150
+ return entry;
151
+ }
152
+ entry.status = 'launched';
153
+ entry.log = join(root, 'steward', 'launch', `${key.replace(/[^a-zA-Z0-9_-]/g, '_')}.log`);
154
+ mkdirSync(join(root, 'steward', 'launch'), { recursive: true });
155
+ ledger.launches[key] = entry;
156
+ saveLaunchLedger(root, ledger);
157
+ try {
158
+ entry.pid = (deps.spawn ?? spawnDetached)(argvFor(item.issue, settings), entry.log).pid;
159
+ } catch (error) {
160
+ entry.status = 'failed';
161
+ entry.reason = redactSecrets(String(error));
162
+ }
163
+ if (entry.pid) {
164
+ try { entry.runId = readFileSync(entry.log, 'utf8').match(RUN_ID)?.[0]; }
165
+ catch { /* The detached child may not have written its log yet. */ }
166
+ }
167
+ saveLaunchLedger(root, ledger);
168
+ debug.log('steward.launch', 'launched', { issue: key, runId: entry.runId, pid: entry.pid, status: entry.status });
169
+ return entry;
170
+ }
171
+
172
+ export function raiseHitl(issue: TriageIssue, row: ScheduledDecision, deps: LaunchDeps): HitlEntry {
173
+ const old = deps.ledger.hitl[issue.identifier];
174
+ if (old && (old.attempted || deps.settings.launch !== 'live')) return old;
175
+ const entry: HitlEntry = old ?? { issue: issue.identifier, reason: redactSecrets(row.hitlReason ?? row.why), raised: false };
176
+ deps.ledger.hitl[issue.identifier] = entry;
177
+ if (deps.settings.launch !== 'live') {
178
+ saveLaunchLedger(deps.root, deps.ledger);
179
+ return entry;
180
+ }
181
+ entry.attempted = true;
182
+ saveLaunchLedger(deps.root, deps.ledger);
183
+ try {
184
+ const category = row.hitlReason === 'public' ? 'publish' : ['money', 'security', 'irreversible'].includes(row.hitlReason ?? '') ? row.hitlReason! : 'other';
185
+ const title = redactSecrets(issue.title);
186
+ const result = (deps.command ?? runCommand)(cli(['decisions', 'raise', '--title', title, '--category', category,
187
+ '--s', title.slice(0, 240), '--c', redactSecrets(row.why).slice(0, 240), '--option', 'a=approve:execute after human decision',
188
+ '--option', 'b=decline:do not execute', '--skip-recommend', 'human decision required', '--agent', 'steward', ...(issue.ref ? ['--ref', issue.ref] : []), '--json']));
189
+ entry.raised = result.exitCode === 0;
190
+ } catch { /* Command may not be installed; card remains the human handoff. */ }
191
+ saveLaunchLedger(deps.root, deps.ledger);
192
+ if (entry.raised) debug.log('steward.launch', 'hitl-raised', { issue: issue.identifier });
193
+ return entry;
194
+ }
195
+
196
+ function alive(pid: number | undefined): boolean {
197
+ if (!pid) return false;
198
+ try { process.kill(pid, 0); return true; } catch (error) { return (error as NodeJS.ErrnoException).code === 'EPERM'; }
199
+ }
200
+
201
+ export function collectOutcomes(ledger: LaunchLedger, deps: LaunchDeps): LaunchEntry[] {
202
+ if (deps.settings.launch !== 'live') return [];
203
+ const done: LaunchEntry[] = [];
204
+ for (const entry of Object.values(ledger.launches)) {
205
+ if (entry.reported || entry.status === 'shadow' || entry.status === 'skipped-budget') continue;
206
+ if (!entry.runId && entry.log) {
207
+ try { entry.runId = readFileSync(entry.log, 'utf8').match(RUN_ID)?.[0]; } catch { /* Wait for log. */ }
208
+ }
209
+ if (entry.runId) {
210
+ try {
211
+ const result = (deps.command ?? runCommand)(cli(['self', 'run-ledger', entry.runId, '--json']));
212
+ if (result.exitCode !== 0) throw new Error(result.stderr ?? 'run ledger unavailable');
213
+ const events = result.stdout.trim().split('\n').filter(Boolean).map(line => JSON.parse(line) as { event?: string; data?: Record<string, unknown> });
214
+ if (!events.length) throw new Error('empty run ledger');
215
+ const reversed = [...events].reverse();
216
+ const mergedOffset = reversed.findIndex(event => event.event === 'merged' && event.data?.merged === true);
217
+ const terminalOffset = reversed.findIndex(event => event.event === 'run-status' || event.event === 'terminal');
218
+ const mergedIndex = mergedOffset < 0 ? -1 : events.length - 1 - mergedOffset;
219
+ const terminalIndex = terminalOffset < 0 ? -1 : events.length - 1 - terminalOffset;
220
+ const merged = mergedIndex >= 0 ? events[mergedIndex] : undefined;
221
+ const terminal = terminalIndex >= 0 ? events[terminalIndex] : undefined;
222
+ const pr = reversed.find(event => event.event === 'pr-opened');
223
+ const artifact = reversed.find(event => event.event === 'deploy-noncode' && typeof event.data?.ref === 'string');
224
+ entry.artifactRef = typeof artifact?.data?.ref === 'string' ? artifact.data.ref : undefined;
225
+ entry.artifactBranch = typeof artifact?.data?.branch === 'string' ? artifact.data.branch : undefined;
226
+ const worktree = reversed.find(event => event.event === 'planned' && typeof event.data?.worktree === 'string' && event.data.worktree);
227
+ entry.artifactWorktree = typeof worktree?.data?.worktree === 'string' ? worktree.data.worktree : undefined;
228
+ const number = merged?.data?.number ?? pr?.data?.number;
229
+ const url = merged?.data?.url ?? pr?.data?.url;
230
+ const validUrl = typeof url === 'string' && /^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+$/.test(url) ? url : undefined;
231
+ entry.prNumber = typeof number === 'number' ? number : validUrl ? Number(validUrl.split('/').at(-1)) : undefined;
232
+ entry.prUrl = validUrl && (entry.prNumber === undefined || validUrl.endsWith(`/pull/${entry.prNumber}`)) ? validUrl : undefined;
233
+ const failed = terminalIndex > mergedIndex && (terminal?.data?.runStatus === 'failed' || terminal?.data?.terminal === 'failed');
234
+ entry.awaitingMerge = !failed && !merged && !artifact && !!pr && terminal?.data?.runStatus === 'completed';
235
+ const completed = !failed && !merged && terminal?.data?.runStatus === 'completed';
236
+ // A finished run is only «merged» when a merge event exists; finishing with an artifact or with nothing is `completed`, not a failure.
237
+ entry.status = failed ? 'failed' : merged ? 'merged' : entry.awaitingMerge ? 'running' : completed ? 'completed' : terminal ? 'failed' : 'running';
238
+ if (entry.status === 'failed' && !entry.reason) {
239
+ const reason = terminal?.data?.reason ?? terminal?.data?.terminal;
240
+ entry.reason = redactSecrets(typeof reason === 'string' ? reason : '런 실패');
241
+ }
242
+ } catch { if (entry.status !== 'failed') entry.status = 'running'; }
243
+ } else if (entry.status !== 'failed') entry.status = alive(entry.pid) ? 'running' : 'failed';
244
+ if (entry.status === 'running') continue;
245
+ done.push(entry);
246
+ debug.log('steward.launch', 'outcome', { issue: entry.issue, status: entry.status, runId: entry.runId, prNumber: entry.prNumber });
247
+ }
248
+ saveLaunchLedger(deps.root, ledger);
249
+ return done;
250
+ }