elanous 0.2.21 → 0.2.22

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 (315) 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/chunks/1307-08b716476f1d50af.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/{2489-ff55a35b202c0f94.js → 2489-d238d3e2eb5b2bf5.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/3857-5985236d9307f676.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/5633-d6852385000738ef.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/5977-5b9e8a45a5b0c823.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/8857-aa1cc4ac90af4d40.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/9247-55bcfbb0e8057f59.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/app/approvals/page-88459e0903da2803.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-d781be3e3a9cfd74.js → page-d85c43f46f58716b.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/app/board/{page-b0a80997903852d8.js → page-4e645600579ae952.js} +1 -1
  13. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-2b1263165630d9c8.js → page-87edbfd19117823b.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/app/bots/page-7f0c2b7801ac6a54.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/app/ceo/page-bf1f030a9d1adffd.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/app/chat/{page-e2d6bed48ac20120.js → page-cbaf66b1e1804adf.js} +1 -1
  17. package/apps/pwa/out/_next/static/chunks/app/consult/{page-111cabe4d1fd69ae.js → page-80ed511e02c81412.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/control/{page-544dd4bd4847eeb7.js → page-9f5610caaa06178c.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/decisions/{page-3ee191319bf97d87.js → page-ec8f3575e339de74.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/design-check/page-86d753e460f1a989.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/app/editor/page-4166a7d51cf0a57d.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/app/exec/{page-a449f1ed472b81c2.js → page-2a19c5cd3b562580.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/app/field/{page-9045094d6d3b5cd0.js → page-b6a09cde2e07521a.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/inside/{page-c06673596849ef45.js → page-f7eae1518ca0c353.js} +1 -1
  25. package/apps/pwa/out/_next/static/chunks/app/intake/page-456a8573ad811381.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/app/{layout-9f81233f29dcd409.js → layout-8462d6a543cbb2d9.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/app/live/page-2974cdfb87c0f9c9.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/app/live-run/page-f927ffa25f3c86d1.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/loops/page-abd86cd832aaa011.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/app/market/page-79daf68f6256adaa.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8c3f168fc3f91939.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/morning/{page-7a12f61be4a579d1.js → page-786fb4504d0f2fa9.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/app/{not-found-c85b0589591a99c9.js → not-found-75209f14597dcb8f.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/app/observatory/{page-725a4da02dd129eb.js → page-bf30affe3a85af7e.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1187a44b7515434b.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-3d0910185a66ed54.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-f401ee1dd5a26e25.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/app/outputs/page-5dfe7604fd842784.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/app/{page-21bec4afeb2f0917.js → page-76cf838050a8d681.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-dc39ee7a36d26385.js → page-7ec8e289b4c14df6.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-84ae759f76053672.js → page-8c8ac17d52be2a79.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/app/sessions/{page-3db58ddb61fe2be9.js → page-d359ab048d63a9d9.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-c5315e30f3528dc4.js → page-b8b345c880bf78d1.js} +1 -1
  44. package/apps/pwa/out/_next/static/chunks/app/settings/{page-2fe368efb6de3b6a.js → page-f151cc10ad026940.js} +1 -1
  45. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-9875f103057da433.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/app/setup/page-9789328af9a9aa62.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/share/{page-c69762987b751174.js → page-bf495a1e284f4e00.js} +1 -1
  48. package/apps/pwa/out/_next/static/chunks/app/showroom/page-a263d94bca4be22a.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/tasks/page-3962eab8ec58d16e.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/term/page-9abcfac07e02a542.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/today/page-bee49f927c333abb.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/trace/page-bac2d56dcb02d955.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/vault/page-937cbcc13176295c.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d09495594f7f480d.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/workflows/{page-7f8d7fcdf058a845.js → page-69cb93d1a2e57e0e.js} +1 -1
  56. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-22163556cd38bc88.js → page-72ec543928d67c3e.js} +1 -1
  57. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-82906eeda0186a7b.js +1 -0
  58. package/apps/pwa/out/_next/static/css/f2bd5257e6af88d8.css +1 -0
  59. package/apps/pwa/out/approvals/index.html +1 -1
  60. package/apps/pwa/out/approvals/index.txt +14 -14
  61. package/apps/pwa/out/autopilot/index.html +1 -1
  62. package/apps/pwa/out/autopilot/index.txt +14 -14
  63. package/apps/pwa/out/board/index.html +1 -1
  64. package/apps/pwa/out/board/index.txt +14 -14
  65. package/apps/pwa/out/botlab/index.html +1 -1
  66. package/apps/pwa/out/botlab/index.txt +14 -14
  67. package/apps/pwa/out/bots/index.html +1 -1
  68. package/apps/pwa/out/bots/index.txt +14 -14
  69. package/apps/pwa/out/ceo/index.html +1 -1
  70. package/apps/pwa/out/ceo/index.txt +14 -14
  71. package/apps/pwa/out/chat/index.html +1 -1
  72. package/apps/pwa/out/chat/index.txt +14 -14
  73. package/apps/pwa/out/consult/index.html +1 -1
  74. package/apps/pwa/out/consult/index.txt +14 -14
  75. package/apps/pwa/out/control/index.html +1 -1
  76. package/apps/pwa/out/control/index.txt +14 -14
  77. package/apps/pwa/out/decisions/index.html +1 -1
  78. package/apps/pwa/out/decisions/index.txt +14 -14
  79. package/apps/pwa/out/design-check/index.html +1 -1
  80. package/apps/pwa/out/design-check/index.txt +14 -14
  81. package/apps/pwa/out/editor/index.html +1 -1
  82. package/apps/pwa/out/editor/index.txt +14 -14
  83. package/apps/pwa/out/exec/index.html +1 -1
  84. package/apps/pwa/out/exec/index.txt +14 -14
  85. package/apps/pwa/out/field/index.html +1 -1
  86. package/apps/pwa/out/field/index.txt +14 -14
  87. package/apps/pwa/out/healthz +1 -1
  88. package/apps/pwa/out/index.html +1 -1
  89. package/apps/pwa/out/index.txt +14 -14
  90. package/apps/pwa/out/inside/index.html +1 -1
  91. package/apps/pwa/out/inside/index.txt +14 -14
  92. package/apps/pwa/out/intake/index.html +1 -1
  93. package/apps/pwa/out/intake/index.txt +14 -14
  94. package/apps/pwa/out/live/index.html +1 -1
  95. package/apps/pwa/out/live/index.txt +14 -14
  96. package/apps/pwa/out/live-run/index.html +1 -1
  97. package/apps/pwa/out/live-run/index.txt +14 -14
  98. package/apps/pwa/out/loops/index.html +1 -1
  99. package/apps/pwa/out/loops/index.txt +14 -14
  100. package/apps/pwa/out/market/index.html +1 -1
  101. package/apps/pwa/out/market/index.txt +14 -14
  102. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  103. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  104. package/apps/pwa/out/morning/index.html +1 -1
  105. package/apps/pwa/out/morning/index.txt +14 -14
  106. package/apps/pwa/out/observatory/index.html +1 -1
  107. package/apps/pwa/out/observatory/index.txt +14 -14
  108. package/apps/pwa/out/ops/checklist/index.html +1 -1
  109. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  110. package/apps/pwa/out/ops/release/index.html +1 -1
  111. package/apps/pwa/out/ops/release/index.txt +14 -14
  112. package/apps/pwa/out/ops/seats/index.html +1 -1
  113. package/apps/pwa/out/ops/seats/index.txt +14 -14
  114. package/apps/pwa/out/outputs/index.html +1 -1
  115. package/apps/pwa/out/outputs/index.txt +14 -14
  116. package/apps/pwa/out/reflection/index.html +1 -1
  117. package/apps/pwa/out/reflection/index.txt +15 -15
  118. package/apps/pwa/out/scheduler/index.html +1 -1
  119. package/apps/pwa/out/scheduler/index.txt +14 -14
  120. package/apps/pwa/out/sessions/index.html +1 -1
  121. package/apps/pwa/out/sessions/index.txt +14 -14
  122. package/apps/pwa/out/settings/devices/index.html +1 -1
  123. package/apps/pwa/out/settings/devices/index.txt +14 -14
  124. package/apps/pwa/out/settings/index.html +1 -1
  125. package/apps/pwa/out/settings/index.txt +14 -14
  126. package/apps/pwa/out/setup/done/index.html +1 -1
  127. package/apps/pwa/out/setup/done/index.txt +14 -14
  128. package/apps/pwa/out/setup/index.html +1 -1
  129. package/apps/pwa/out/setup/index.txt +14 -14
  130. package/apps/pwa/out/share/index.html +1 -1
  131. package/apps/pwa/out/share/index.txt +14 -14
  132. package/apps/pwa/out/showroom/index.html +1 -1
  133. package/apps/pwa/out/showroom/index.txt +14 -14
  134. package/apps/pwa/out/tasks/index.html +1 -1
  135. package/apps/pwa/out/tasks/index.txt +14 -14
  136. package/apps/pwa/out/term/index.html +1 -1
  137. package/apps/pwa/out/term/index.txt +14 -14
  138. package/apps/pwa/out/today/index.html +1 -1
  139. package/apps/pwa/out/today/index.txt +14 -14
  140. package/apps/pwa/out/trace/index.html +1 -1
  141. package/apps/pwa/out/trace/index.txt +14 -14
  142. package/apps/pwa/out/vault/index.html +1 -1
  143. package/apps/pwa/out/vault/index.txt +14 -14
  144. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  145. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  146. package/apps/pwa/out/workflows/index.html +1 -1
  147. package/apps/pwa/out/workflows/index.txt +14 -14
  148. package/apps/pwa/out/workspace/index.html +1 -1
  149. package/apps/pwa/out/workspace/index.txt +14 -14
  150. package/apps/pwa/out/worktrees/index.html +1 -1
  151. package/apps/pwa/out/worktrees/index.txt +14 -14
  152. package/graphs/catalog/catalog.yaml +1 -1
  153. package/graphs/drafts/recipes.yaml +2 -2
  154. package/graphs/test-diet/recipes.yaml +3 -0
  155. package/graphs/test-diet/test-diet.yaml +3 -1
  156. package/package.json +1 -1
  157. package/scripts/f-session-monitor.ts +69 -0
  158. package/scripts/hq/standby-snapshot.ts +7 -3
  159. package/scripts/knowledge-compress.ts +11 -0
  160. package/scripts/nightly-docops.ts +90 -3
  161. package/scripts/research-coo-admin-dates.ts +39 -0
  162. package/scripts/skills/public-check.ts +7 -2
  163. package/scripts/smart-merge/resync-shadow.ts +5 -3
  164. package/scripts/test-diet/lib.ts +81 -1
  165. package/scripts/test-diet/node.ts +48 -2
  166. package/scripts/verify-coo-admin-evidence.ts +83 -0
  167. package/src/acp/client.ts +4 -2
  168. package/src/agent/run-context.ts +4 -0
  169. package/src/agent-env/profile.ts +25 -5
  170. package/src/agent-mission/driver.ts +26 -12
  171. package/src/agent-mission/supervisor-metrics.ts +17 -2
  172. package/src/agent-substrate/acp-reviewer.ts +12 -5
  173. package/src/agent-substrate/pr-reviewer.ts +11 -4
  174. package/src/agent-substrate/review-intent.ts +102 -3
  175. package/src/autopilot/build/llm-conflict-merge.ts +22 -8
  176. package/src/autopilot/pr-manager.ts +18 -8
  177. package/src/away/release-watch.ts +47 -8
  178. package/src/briefing/brief-items.ts +104 -11
  179. package/src/chat/index.ts +1 -0
  180. package/src/cli/brief-cli.ts +20 -2
  181. package/src/cli/config-classify.ts +83 -0
  182. package/src/cli/control-cli.ts +14 -0
  183. package/src/cli/decisions-cli.ts +26 -16
  184. package/src/cli/logs-model-input.ts +1 -1
  185. package/src/cli/pr-cli.ts +65 -8
  186. package/src/cli/release-cli.ts +21 -4
  187. package/src/cli/tasks-cli.ts +108 -1
  188. package/src/connectors/types.ts +6 -6
  189. package/src/context-bus/context-now-surfaces.ts +18 -0
  190. package/src/context-bus/coord-events.ts +32 -4
  191. package/src/context-bus/long-term-memory.ts +23 -6
  192. package/src/context-bus/nightly-condense.ts +169 -21
  193. package/src/control/control-status.ts +87 -0
  194. package/src/control-plane/light-member.ts +5 -3
  195. package/src/control-plane/member.ts +172 -7
  196. package/src/core-turn/run-goal-loop.ts +22 -1
  197. package/src/dashboard/slash-runtime/dashboard-handlers.ts +9 -0
  198. package/src/decisions/decision-cards.ts +20 -1
  199. package/src/decisions/decision-ledger.ts +11 -8
  200. package/src/decisions/web-push-decision-cards.ts +19 -3
  201. package/src/discord-self-message.ts +2 -2
  202. package/src/domains/coo-admin-dates.evidence.json +876 -0
  203. package/src/domains/coo-admin-tool.ts +48 -14
  204. package/src/domains/ops-status.ts +53 -0
  205. package/src/domains/outbound-alert.ts +24 -1
  206. package/src/exec-requests/planner.ts +6 -0
  207. package/src/graph-kinds/registry.ts +6 -6
  208. package/src/graph-runner/graph-cli.ts +34 -2
  209. package/src/graph-runner/graph-run-api.ts +7 -0
  210. package/src/graph-runner/graph-tick.ts +73 -12
  211. package/src/graph-runner/runner.ts +43 -11
  212. package/src/graph-unify/wf-to-graph.ts +79 -0
  213. package/src/graph-wizard/generate.ts +34 -2
  214. package/src/graph-wizard/steps.ts +13 -0
  215. package/src/harness/harness-cli-command.ts +121 -23
  216. package/src/harness/harness-queue.ts +58 -14
  217. package/src/harness/launch-advice.ts +23 -0
  218. package/src/index.ts +36 -7
  219. package/src/knowledge/conclusion-compress.ts +93 -0
  220. package/src/knowledge/know-find.ts +9 -2
  221. package/src/knowledge/query.ts +20 -0
  222. package/src/llm.ts +26 -16
  223. package/src/loops/neighbor-runtime.ts +88 -0
  224. package/src/loops/orchestrator/author-depth.ts +29 -11
  225. package/src/loops/orchestrator/overlap.ts +7 -1
  226. package/src/loops/orchestrator/resync-shadow.ts +1 -1
  227. package/src/loops/orchestrator/tick.ts +35 -2
  228. package/src/loops/orchestrator/traffic.ts +2 -0
  229. package/src/loops/registry.ts +5 -0
  230. package/src/market/publish.ts +17 -1
  231. package/src/maturity/feature-maturity.ts +2 -1
  232. package/src/mss/logging/redaction.ts +17 -0
  233. package/src/nexus/api/draft-metrics.ts +22 -8
  234. package/src/nexus/api/graph-peer-edit.ts +77 -0
  235. package/src/nexus/api/graph-wizard-api.ts +9 -1
  236. package/src/nexus/api/graphs-api.ts +145 -7
  237. package/src/nexus/api/http-server.ts +93 -8
  238. package/src/nexus/api/plugins-market.ts +24 -0
  239. package/src/nexus/index.ts +3 -2
  240. package/src/oauth/claude-pty-maturity.ts +24 -0
  241. package/src/oauth/fallback-chain.ts +27 -14
  242. package/src/plugins/maker/market-bundle.ts +9 -0
  243. package/src/plugins/maker/plugin-maker.ts +9 -3
  244. package/src/plugins/maker/wizard-generate.ts +73 -9
  245. package/src/policy/llm-policy.ts +11 -6
  246. package/src/release-loop/checklist-board.ts +27 -0
  247. package/src/release-loop/checklist.ts +38 -0
  248. package/src/seat-address/seat-address.ts +1 -0
  249. package/src/seat-loop/seat-loop.ts +174 -20
  250. package/src/seat-loop/seat-report.ts +29 -1
  251. package/src/self-dev/dev-cli.ts +90 -8
  252. package/src/self-dev/dev-pipeline.ts +20 -1
  253. package/src/self-dev/draft-sweep.ts +45 -8
  254. package/src/self-dev/orchestrate.ts +3 -0
  255. package/src/self-dev/review-provider-fallback.ts +83 -0
  256. package/src/self-dev/run-supervisor.ts +5 -1
  257. package/src/self-implement/harness-policy-gates.ts +7 -5
  258. package/src/self-implement/host-regate.ts +28 -14
  259. package/src/self-implement/l8-merge-queue.ts +80 -8
  260. package/src/self-implement/orchestrator.ts +68 -27
  261. package/src/self-implement/seams.ts +5 -3
  262. package/src/self-implement/self-implement-cli.ts +4 -0
  263. package/src/shell-env-bootstrap.ts +19 -4
  264. package/src/steward/triage.ts +25 -8
  265. package/src/storage/file-lock.ts +33 -4
  266. package/src/task-agent/completion-evidence.ts +31 -3
  267. package/src/task-agent/cover.ts +61 -0
  268. package/src/task-agent/live-moves.ts +126 -3
  269. package/src/task-agent/shadow.ts +19 -4
  270. package/src/task-agent/task-hand.ts +2 -0
  271. package/src/task-orchestrator/surfaces/pod-pool.ts +138 -15
  272. package/src/task-orchestrator/surfaces/self-implement.ts +7 -0
  273. package/src/telegram-commands.ts +8 -0
  274. package/src/telegram-project-command.ts +6 -0
  275. package/src/telegram.ts +2 -2
  276. package/src/typecheck-ratchet.ts +2 -1
  277. package/src/user-config.ts +35 -4
  278. package/src/version/packed-revision.json +1 -1
  279. package/src/workflow-runtime/executor.ts +24 -2
  280. package/src/workflow-runtime/triggers/registry.ts +27 -0
  281. package/src/workflow-runtime/triggers/schedule-source.ts +30 -3
  282. package/apps/pwa/out/_next/static/chunks/1307-c97eba9fb72a9a5e.js +0 -1
  283. package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +0 -1
  284. package/apps/pwa/out/_next/static/chunks/5977-59fedea50d13c762.js +0 -1
  285. package/apps/pwa/out/_next/static/chunks/8857-f1762140ef9a96b1.js +0 -1
  286. package/apps/pwa/out/_next/static/chunks/9247-7581825d511a64c0.js +0 -1
  287. package/apps/pwa/out/_next/static/chunks/9497-b0e758d1cd4ae8d7.js +0 -1
  288. package/apps/pwa/out/_next/static/chunks/app/approvals/page-937c92292a4ab4f0.js +0 -1
  289. package/apps/pwa/out/_next/static/chunks/app/bots/page-ea998f7f5d156bea.js +0 -1
  290. package/apps/pwa/out/_next/static/chunks/app/ceo/page-33c328b64ca508f8.js +0 -1
  291. package/apps/pwa/out/_next/static/chunks/app/design-check/page-e4bcb2c503dab04b.js +0 -1
  292. package/apps/pwa/out/_next/static/chunks/app/editor/page-300897dcbbd5ed76.js +0 -1
  293. package/apps/pwa/out/_next/static/chunks/app/intake/page-103fa3191c1b2e08.js +0 -1
  294. package/apps/pwa/out/_next/static/chunks/app/live/page-be8b4615c919e36c.js +0 -1
  295. package/apps/pwa/out/_next/static/chunks/app/live-run/page-c685b104e0dfa609.js +0 -1
  296. package/apps/pwa/out/_next/static/chunks/app/loops/page-8d693331cfbb6add.js +0 -1
  297. package/apps/pwa/out/_next/static/chunks/app/market/page-cef4f30a4c22fae1.js +0 -1
  298. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66ea1b54fc83fa15.js +0 -1
  299. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-8cc707bd09a497d9.js +0 -1
  300. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-90b55be1f15ce0e8.js +0 -1
  301. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cb16606e08be65db.js +0 -1
  302. package/apps/pwa/out/_next/static/chunks/app/outputs/page-16dc64b74ed6a40a.js +0 -1
  303. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-8d04132dd0ac5e10.js +0 -1
  304. package/apps/pwa/out/_next/static/chunks/app/setup/page-f23484de467c9ea3.js +0 -1
  305. package/apps/pwa/out/_next/static/chunks/app/showroom/page-5456692bda39aafb.js +0 -1
  306. package/apps/pwa/out/_next/static/chunks/app/tasks/page-d75f7cd65845ce98.js +0 -1
  307. package/apps/pwa/out/_next/static/chunks/app/term/page-5603f5e568b25ce3.js +0 -1
  308. package/apps/pwa/out/_next/static/chunks/app/today/page-d2cc0f4e89f42028.js +0 -1
  309. package/apps/pwa/out/_next/static/chunks/app/trace/page-a6eb73b8a4840f1c.js +0 -1
  310. package/apps/pwa/out/_next/static/chunks/app/vault/page-c0a3f4d10edb83fa.js +0 -1
  311. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6c0b88068c143f15.js +0 -1
  312. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-141436fd26dd56fc.js +0 -1
  313. package/apps/pwa/out/_next/static/css/e81df11490b4413b.css +0 -1
  314. /package/apps/pwa/out/_next/static/{MObU8ujkMciAKr6ljAV05 → aBi6EyMtLsrl7J8eH4kF7}/_buildManifest.js +0 -0
  315. /package/apps/pwa/out/_next/static/{MObU8ujkMciAKr6ljAV05 → aBi6EyMtLsrl7J8eH4kF7}/_ssgManifest.js +0 -0
@@ -20,6 +20,7 @@ import { parseGraphTemplateYaml, type GraphEdgeSpec, type GraphNodeSpec, type Gr
20
20
  import { createGraphVariant, type GraphVariantPlan } from '../self-implement/graph-variant.js';
21
21
  import { psProcessStartMs, START_TOLERANCE_MS } from '../harness/harness-stop.js';
22
22
  import { executeBashNode } from '../workflow-runtime/nodes/bash.js';
23
+ import { interpolate } from '../workflow-runtime/variables.js';
23
24
  import type { BashNode, NodeExecContext, WorkflowDeps } from '../workflow-runtime/types.js';
24
25
 
25
26
  type BashRun = WorkflowDeps['runBash'];
@@ -59,6 +60,8 @@ export interface GraphRunState {
59
60
 
60
61
  export interface GraphRunOptions {
61
62
  input?: unknown;
63
+ /** Bytes already checked by the tick gate, used for a current-graph start or legacy resume. */
64
+ graphSource?: string;
62
65
  dryRun?: boolean;
63
66
  variant?: { goal: string; plan: GraphVariantPlan };
64
67
  runId?: string;
@@ -72,7 +75,7 @@ export interface GraphRunOptions {
72
75
  * its own universe and lands in the tree-derived test universe while the run ledger is prod.
73
76
  */
74
77
  pinChildUniverse?: boolean;
75
- deps?: { root?: string; runBash?: BashRun; log?: (event: string, data: Record<string, unknown>) => void; processStartMs?: (pid: number) => number | null; growthProposer?: GrowthProposer; growthLLM?: (prompt: string) => Promise<string>; classifyGrowthRecipe?: (node: GraphNodeSpec, resolved: { command?: string; approval?: string }) => GrowthRecipeEffect; growthDecision?: GrowthDecisionDeps & { list?: (filters: { status: 'all' }) => DecisionEntry[] } };
78
+ deps?: { root?: string; runBash?: BashRun; callLLM?: (prompt: string) => Promise<string>; log?: (event: string, data: Record<string, unknown>) => void; beforeNode?: () => void; processStartMs?: (pid: number) => number | null; growthProposer?: GrowthProposer; growthLLM?: (prompt: string) => Promise<string>; classifyGrowthRecipe?: (node: GraphNodeSpec, resolved: { command?: string; approval?: string }) => GrowthRecipeEffect; growthDecision?: GrowthDecisionDeps & { list?: (filters: { status: 'all' }) => DecisionEntry[] } };
76
79
  }
77
80
 
78
81
  function safeSegment(value: string): string {
@@ -83,24 +86,29 @@ function safeSegment(value: string): string {
83
86
  }
84
87
 
85
88
  type CommandRecipe = { command: string; dry_run_command?: string; timeout_ms?: number };
86
- type Recipe = CommandRecipe | ({ approval: string } & Partial<CommandRecipe>);
89
+ type PromptRecipe = { prompt: string };
90
+ type Recipe = CommandRecipe | PromptRecipe | ({ approval: string } & Partial<CommandRecipe>);
87
91
 
88
92
  /** 접두 없는 recipe 가 카탈로그 역할이고, 그래프 옆 recipes.yaml 에 같은 키의 `{ command }` 가 있을 때만 cmd 처럼 실행한다. */
89
93
  function roleCommand(recipe: string, recipes: Record<string, Recipe>, catalogRoles: ReadonlySet<string>): CommandRecipe | undefined {
90
94
  if (!recipe || recipe === 'none' || recipe.includes(':') || !catalogRoles.has(recipe)) return undefined;
91
95
  const entry = recipes[recipe];
92
96
  // An entry that carries an approval only runs through `approval:` — never as a bare command.
93
- return entry && !('approval' in entry) && typeof entry.command === 'string' ? { command: entry.command,
97
+ return entry && 'command' in entry && !('approval' in entry) && typeof entry.command === 'string' ? { command: entry.command,
94
98
  ...(entry.dry_run_command ? { dry_run_command: entry.dry_run_command } : {}),
95
99
  ...(entry.timeout_ms ? { timeout_ms: entry.timeout_ms } : {}) } : undefined;
96
100
  }
97
101
 
98
- function resolveNodeRecipe(node: GraphNodeSpec, recipes: Record<string, Recipe>, catalogRoles: ReadonlySet<string>): { command?: CommandRecipe; approval?: { approval: string } } {
102
+ function resolveNodeRecipe(node: GraphNodeSpec, recipes: Record<string, Recipe>, catalogRoles: ReadonlySet<string>): { command?: CommandRecipe; prompt?: PromptRecipe; approval?: { approval: string } } {
99
103
  if (node.recipe === 'none') return {};
104
+ if (node.kind === 'prompt' && node.recipe.startsWith('prompt:')) {
105
+ const entry = recipes[node.recipe.slice('prompt:'.length)];
106
+ return entry && 'prompt' in entry ? { prompt: entry } : {};
107
+ }
100
108
  if (node.recipe.startsWith('cmd:')) {
101
109
  const id = node.recipe.slice(4);
102
110
  const entry = recipes[id];
103
- return entry && !('approval' in entry) && typeof entry.command === 'string' ? { command: { command: entry.command,
111
+ return entry && 'command' in entry && !('approval' in entry) && typeof entry.command === 'string' ? { command: { command: entry.command,
104
112
  ...(entry.dry_run_command ? { dry_run_command: entry.dry_run_command } : {}),
105
113
  ...(entry.timeout_ms ? { timeout_ms: entry.timeout_ms } : {}) } } : {};
106
114
  }
@@ -133,7 +141,10 @@ function recipesFor(path: string, recipeSource: string): Record<string, Recipe>
133
141
  for (const [id, raw] of Object.entries(parsed)) {
134
142
  if (!raw || typeof raw !== 'object' || Array.isArray(raw)) throw new Error(`invalid recipe: ${id}`);
135
143
  const item = raw as Record<string, unknown>;
136
- if (Object.hasOwn(item, 'approval')) {
144
+ if (Object.hasOwn(item, 'prompt')) {
145
+ if (typeof item.prompt !== 'string' || !item.prompt.trim() || Object.keys(item).some(key => key !== 'prompt')) throw new Error(`invalid recipe: ${id}`);
146
+ recipes[id] = { prompt: item.prompt };
147
+ } else if (Object.hasOwn(item, 'approval')) {
137
148
  if (typeof item.approval !== 'string' || !item.approval.trim() ||
138
149
  (item.command !== undefined && (typeof item.command !== 'string' || !item.command.trim())) ||
139
150
  (item.timeout_ms !== undefined && (!Number.isSafeInteger(item.timeout_ms) || (item.timeout_ms as number) <= 0)) ||
@@ -382,7 +393,7 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
382
393
  ? listGraphRuns(root).runs.filter((run) => run.runId === options.resumeRunId && run.graphPath === resolve(path)) : [];
383
394
  if (matchingRuns.length > 1) throw new Error(`ambiguous run id: ${options.resumeRunId}`);
384
395
  const currentSource = options.resumeRunId && (options.resumeGraphId || matchingRuns.length) && !options.useCurrentGraph
385
- ? undefined : readFileSync(path, 'utf8');
396
+ ? undefined : options.graphSource ?? readFileSync(path, 'utf8');
386
397
  const currentHeader: unknown = options.resumeRunId && !options.resumeGraphId && !matchingRuns.length ? parseYaml(currentSource!) : undefined;
387
398
  const resumeGraphId = options.resumeGraphId ?? matchingRuns[0]?.graphId ?? (currentHeader && typeof currentHeader === 'object' && !Array.isArray(currentHeader)
388
399
  ? (currentHeader as Record<string, unknown>).graph_id : undefined);
@@ -393,7 +404,7 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
393
404
  }
394
405
  const snapshotDir = saved ? `${saved.statePath}.graph` : undefined;
395
406
  const graphMode: 'snapshot' | 'current' = snapshotDir && saved?.graphSnapshot && !options.useCurrentGraph ? 'snapshot' : 'current';
396
- const source = graphMode === 'snapshot' ? readFileSync(join(snapshotDir!, 'graph.yaml'), 'utf8') : currentSource ?? readFileSync(path, 'utf8');
407
+ const source = graphMode === 'snapshot' ? readFileSync(join(snapshotDir!, 'graph.yaml'), 'utf8') : currentSource ?? options.graphSource ?? readFileSync(path, 'utf8');
397
408
  // The shared YAML parser requires a recipe string. A command-less node in
398
409
  // this runner is represented internally as `none`, without changing that parser.
399
410
  const raw: unknown = parseYaml(source);
@@ -451,7 +462,7 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
451
462
  for (const node of graph.nodes) {
452
463
  if (node.recipe === 'none') continue;
453
464
  const resolved = resolveNodeRecipe(node, recipes, catalogRoles);
454
- if (!resolved.command && !resolved.approval) throw new Error(`unknown command recipe for ${node.nodeId}: ${node.recipe}`);
465
+ if (!resolved.command && !resolved.prompt && !resolved.approval) throw new Error(`unknown command recipe for ${node.nodeId}: ${node.recipe}`);
455
466
  }
456
467
  const graphId = safeSegment(graph.graphId);
457
468
  if (saved && graphId !== saved.graphId) {
@@ -625,6 +636,7 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
625
636
  writeFileSync(join(`${statePath}.graph`, 'recipes.yaml'), recipeSource, { flag: 'wx' });
626
637
  }
627
638
  while (current !== undefined) {
639
+ options.deps?.beforeNode?.();
628
640
  const node = graph.nodes.find((n) => n.nodeId === current);
629
641
  if (!node) throw new Error(`undeclared node: ${current}`);
630
642
  const resumingPending = state.pending?.nodeId === current;
@@ -643,6 +655,7 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
643
655
  }
644
656
  const resolved = resolveNodeRecipe(node, recipes, catalogRoles);
645
657
  const command = resolved.command;
658
+ const prompt = resolved.prompt;
646
659
  const approval = resolved.approval ? { approval: approvalMessage(resolved.approval.approval, state.input) } : undefined;
647
660
  if (resumingPending && (!approval || approval.approval !== state.pending?.message)) {
648
661
  throw new Error(`pending approval no longer matches graph: ${current}`);
@@ -739,6 +752,25 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
739
752
  error = result.error ? redactCredentials(result.error) : undefined;
740
753
  state.executed++;
741
754
  }
755
+ if (prompt && !state.dryRun && !resumingCompleted) {
756
+ const outputs: Record<string, { ok: boolean; output: unknown; durationMs: number }> = Object.create(null);
757
+ for (const previous of state.nodes) outputs[previous.nodeId] = { ok: previous.ok, output: lastJsonObject(previous.output) ?? previous.output ?? null, durationMs: 0 };
758
+ try {
759
+ const text = interpolate(prompt.prompt, {
760
+ arguments: typeof state.input === 'string' ? state.input : state.input === undefined ? '' : JSON.stringify(state.input),
761
+ artifactsDir: dirname(statePath), outputs,
762
+ });
763
+ if (text.missing.length) throw new Error(`missing prompt reference: ${text.missing.join(', ')}`);
764
+ // The LLM is injected only; production wiring to a provider is a follow-up slice.
765
+ if (!options.deps?.callLLM) throw new Error('prompt node requires an injected LLM (deps.callLLM)');
766
+ output = await options.deps.callLLM(text.text);
767
+ ok = true;
768
+ } catch (cause) {
769
+ ok = false;
770
+ error = cause instanceof Error ? cause.message : String(cause);
771
+ }
772
+ state.executed++;
773
+ }
742
774
  const seconds = Number(((performance.now() - startedAt) / 1000).toFixed(2));
743
775
  // A wall clock stepped back mid-node must not record an end before the start.
744
776
  const nodeEndedAt = new Date(Math.max(Date.now(), Date.parse(nodeStartedAt))).toISOString();
@@ -753,7 +785,7 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
753
785
  if (approval && !state.dryRun && !command) {
754
786
  output = JSON.stringify({ outcome: ok ? 'approved' : 'rejected', ...(state.pending?.decidedBy === undefined ? {} : { decidedBy: state.pending.decidedBy }), decidedAt: state.pending?.decidedAt ?? null });
755
787
  }
756
- state.nodes.push({ nodeId: current, ok, exit, executed: !!command && (!state.dryRun || !!command.dry_run_command), ...((command || approval) && (!state.dryRun || !!command?.dry_run_command) && output !== undefined ? { output } : {}), ...(error ? { error } : {}),
788
+ state.nodes.push({ nodeId: current, ok, exit, executed: (!!command && (!state.dryRun || !!command.dry_run_command)) || (!!prompt && !state.dryRun), ...((command || prompt || approval) && (!state.dryRun || !!command?.dry_run_command) && output !== undefined ? { output } : {}), ...(error ? { error } : {}),
757
789
  ...(approval && state.pending ? { ...(state.pending.decidedBy === undefined ? {} : { decidedBy: state.pending.decidedBy }), decidedAt: state.pending.decidedAt } : {}),
758
790
  startedAt: nodeStartedAt, endedAt: nodeEndedAt, seconds });
759
791
  }
@@ -761,7 +793,7 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
761
793
  delete state.pending;
762
794
  delete state.approvalSourceHash;
763
795
  }
764
- const reported = command && (!state.dryRun || command.dry_run_command) ? lastJsonObject(resumingCompleted ? completed?.output : output)?.outcome : undefined;
796
+ const reported = (prompt && !state.dryRun) || (command && (!state.dryRun || command.dry_run_command)) ? lastJsonObject(resumingCompleted ? completed?.output : output)?.outcome : undefined;
765
797
  const namedOutcome = typeof reported === 'string' ? reported : undefined;
766
798
  console.error(`[graph] ${current} ${ok ? 'ok' : 'fail'} (${seconds.toFixed(2)}s)`);
767
799
  debug.log('graph.run', 'node', { graphId, runId, nodeId: current, phase: ok ? 'ok' : 'fail', seconds, exit });
@@ -0,0 +1,79 @@
1
+ import { topoSort } from '../workflow-runtime/schema.js';
2
+ import type { DagNode, WorkflowDefinition } from '../workflow-runtime/types.js';
3
+ import type { GraphEdgeSpec, GraphNodeSpec, GraphTemplateSpec } from '../self-implement/graph-yaml.js';
4
+ import { WORKFLOW_CORE_KINDS } from '../graph-kinds/registry.js';
5
+
6
+ /** In-memory graph projection. The compatibility executor dispatches these
7
+ * workflow recipes; graph-runner cannot execute all workflow node variants yet. */
8
+ export interface WorkflowGraph {
9
+ readonly template: GraphTemplateSpec;
10
+ readonly recipes: Readonly<Record<string, DagNode>>;
11
+ }
12
+
13
+ export type WorkflowGraphResult =
14
+ | { readonly ok: true; readonly graph: WorkflowGraph }
15
+ | { readonly ok: false; readonly reason: string };
16
+
17
+ /** Project a validated workflow without changing its YAML or executing any work.
18
+ * Reject constructs whose graph projection would silently change DAG semantics. */
19
+ export function workflowToGraph(workflow: WorkflowDefinition): WorkflowGraphResult {
20
+ if ((workflow.concurrency ?? 1) !== 1) return { ok: false, reason: 'concurrency > 1' };
21
+ if (!workflow.nodes.length) return { ok: false, reason: 'empty workflow' };
22
+ if (workflow.nodes.some(node => node.on_error)) {
23
+ return { ok: false, reason: 'error routing' };
24
+ }
25
+ let order: string[];
26
+ try { order = topoSort(workflow.nodes); }
27
+ catch (error) { return { ok: false, reason: String(error) }; }
28
+ if (order.length !== workflow.nodes.length || new Set(order).size !== order.length) {
29
+ return { ok: false, reason: 'invalid topology' };
30
+ }
31
+ const byId = new Map(workflow.nodes.map(node => [node.id, node] as const));
32
+ const recipes: Record<string, DagNode> = Object.create(null);
33
+ const nodes: GraphNodeSpec[] = [];
34
+ for (const id of order) {
35
+ const node = byId.get(id)!;
36
+ const kind = typeof node.kind === 'string' ? node.kind
37
+ : WORKFLOW_CORE_KINDS.find(key => key !== 'subworkflow' && key in node);
38
+ if (!kind) {
39
+ return { ok: false, reason: `unknown node variant: ${id}` };
40
+ }
41
+ recipes[id] = node;
42
+ nodes.push({ nodeId: id, kind, recipe: `workflow:${id}`, maxVisits: 1 });
43
+ }
44
+ // A single cursor visits each node exactly once in the same stable topological
45
+ // order as the serial workflow executor. Preserve original dependencies and
46
+ // conditions in recipes; do not mistake a when expression for a graph outcome.
47
+ const edges: GraphEdgeSpec[] = order.slice(1).map((id, index) => ({ from: order[index]!, to: id }));
48
+ const template: GraphTemplateSpec = {
49
+ graphId: workflow.name, version: 1, entryNode: order[0]!, terminalNodes: [order.at(-1)!],
50
+ nodes, edges,
51
+ };
52
+ return { ok: true, graph: { template, recipes } };
53
+ }
54
+
55
+ /** The workflow compatibility cursor follows the projected graph edges, not
56
+ * the source DAG. The WF event adapter remains the owner of node dispatch. */
57
+ export function walkWorkflowGraph(graph: WorkflowGraph): string[] {
58
+ const order: string[] = [];
59
+ const visited = new Set<string>();
60
+ let current: string | undefined = graph.template.entryNode;
61
+ while (current !== undefined) {
62
+ const spec = graph.template.nodes.find(node => node.nodeId === current);
63
+ if (visited.has(current) || !spec || spec.recipe !== `workflow:${current}` || spec.maxVisits !== 1
64
+ || !Object.hasOwn(graph.recipes, current)) throw new Error(`invalid workflow graph cursor: ${current}`);
65
+ const recipe = graph.recipes[current]!;
66
+ const kind = typeof recipe.kind === 'string' ? recipe.kind
67
+ : WORKFLOW_CORE_KINDS.find(key => key !== 'subworkflow' && key in recipe);
68
+ if (recipe.id !== current || kind !== spec.kind) throw new Error(`invalid workflow graph recipe: ${current}`);
69
+ visited.add(current);
70
+ order.push(current);
71
+ const outgoing: GraphEdgeSpec[] = graph.template.edges.filter(edge => edge.from === current);
72
+ if (outgoing.length > 1 || outgoing.some(edge => edge.to === undefined)) throw new Error(`invalid workflow graph edge: ${current}`);
73
+ current = outgoing[0]?.to;
74
+ }
75
+ if (order.length !== graph.template.nodes.length || !graph.template.terminalNodes.includes(order.at(-1)!)) {
76
+ throw new Error('incomplete workflow graph path');
77
+ }
78
+ return order;
79
+ }
@@ -18,6 +18,8 @@ import { validateGraphYaml } from '../nexus/api/graph-kinds.js';
18
18
  import { defaultGraphsDir } from '../self-implement/graph-templates.js';
19
19
  import { WORKFLOW_SYNTH_SYSTEM_PROMPT } from '../workflow-synth/system-prompt.js';
20
20
  import { WIZARD_ARCHETYPES } from './archetypes.js';
21
+ import { listInstalledPacks, queryInstalledPack } from '../knowledge/query.js';
22
+ import { parsePackIdString } from '../knowledge/kgs/pack.js';
21
23
  import { WIZARD_MAX_RETRIES, WIZARD_STEPS, WIZARD_STEP_IDS, recipesYamlFor, type WizardNodeStep } from './steps.js';
22
24
 
23
25
  export interface GraphWizardTurn { role: 'user' | 'assistant'; text: string }
@@ -28,6 +30,8 @@ export interface GraphWizardRequest {
28
30
  /** 있으면 prompt 는 «이 그래프를 고치라»는 지시다 — 결과는 고친 «전체» YAML. */
29
31
  currentYaml?: string;
30
32
  history?: GraphWizardTurn[];
33
+ /** Installed pack selected for a knowledge (RAG) node. */
34
+ packId?: string;
31
35
  }
32
36
 
33
37
  export interface GraphWizardResult {
@@ -58,10 +62,18 @@ export interface GraphWizardDeps {
58
62
  /** 이미 쓰인 graph_id(핵심 ⊕ 내 그래프). 기본 = graphs/** ⊕ <state>/graphs. */
59
63
  existingIds?: () => ReadonlySet<string>;
60
64
  maxAttempts?: number;
65
+ /** Installed pack inventory and retrieval seams for isolated generation. */
66
+ packs?: typeof listInstalledPacks;
67
+ searchPack?: typeof queryInstalledPack;
61
68
  }
62
69
 
63
70
  export const GRAPH_WIZARD_MAX_ATTEMPTS = 3;
64
71
 
72
+ /** Inventory used by the editor's pack picker and the wizard's validation. */
73
+ export function wizardInstalledPacks(deps: GraphWizardDeps = {}): Array<{ id: string; title: string }> {
74
+ return (deps.packs ?? listInstalledPacks)();
75
+ }
76
+
65
77
  /** 요청 자체가 틀렸다(400) — LLM 실패(502)와 가른다. */
66
78
  export class GraphWizardInputError extends Error { override name = 'GraphWizardInputError'; }
67
79
  const GRAPH_ID = /^[a-z0-9-]+$/;
@@ -422,8 +434,9 @@ async function defaultCallLLM(prompt: string): Promise<string> {
422
434
  function buildPrompt(input: {
423
435
  req: GraphWizardRequest; kind: GraphKind; id: string; base?: TemplateEntry; templates: readonly TemplateEntry[];
424
436
  previous?: { yaml: string; issues: string[] };
437
+ evidence?: string;
425
438
  }): string {
426
- const { req, kind, id, base, templates, previous } = input;
439
+ const { req, kind, id, base, templates, previous, evidence } = input;
427
440
  const kinds = listNodeKinds(kind).map((k) => k.kind).join(', ');
428
441
  const editing = typeof req.currentYaml === 'string' && req.currentYaml.trim() !== '';
429
442
  const lines: string[] = [
@@ -451,6 +464,8 @@ function buildPrompt(input: {
451
464
  } else {
452
465
  lines.push('', `사용자의 말: ${req.prompt}`);
453
466
  }
467
+ if (evidence) lines.push('', '선택한 설치 팩의 검색 근거:', evidence,
468
+ `지식(RAG) 노드를 cmd 단계로 넣고 # 지식 검색 | knowledge-rag | ${req.packId} 주석을 붙인다. 다음 산출 단계는 이 노드의 근거를 사용한다. 실행 입력 input.query 가 있으면 질문으로 검색하고 없으면 설치 팩 전체 색인에서 가져온다. 근거에 없는 사실을 만들지 마라.`);
454
469
  if (previous) {
455
470
  lines.push('', '직전 시도가 검증에 실패했다. 아래 문제를 «전부» 고쳐 전체 YAML 을 다시 써라:', ...previous.issues.map((i) => `- ${i}`),
456
471
  '직전 YAML:', '```yaml', previous.yaml.trim(), '```');
@@ -467,6 +482,15 @@ export async function generateGraphFromPrompt(req: GraphWizardRequest, deps: Gra
467
482
  const kind: GraphKind = req.kind === 'workflow' ? 'workflow' : 'harness';
468
483
  const prompt = typeof req.prompt === 'string' ? req.prompt.trim() : '';
469
484
  if (!prompt) throw new GraphWizardInputError('prompt is required');
485
+ if (req.packId !== undefined && kind !== 'harness') throw new GraphWizardInputError('pack selection requires a harness graph');
486
+ let evidence: string | undefined;
487
+ if (req.packId !== undefined) {
488
+ if (!parsePackIdString(req.packId) || !wizardInstalledPacks(deps).some(pack => pack.id === req.packId))
489
+ throw new GraphWizardInputError(`pack not installed: ${req.packId}`);
490
+ const cards = (deps.searchPack ?? queryInstalledPack)(req.packId, prompt);
491
+ if (!cards.length) throw new GraphWizardInputError(`no knowledge found in ${req.packId} for: ${prompt}`);
492
+ evidence = cards.slice(0, 5).map(card => `[${card.ref}] ${card.title}: ${card.body.slice(0, 1200)}`).join('\n');
493
+ }
470
494
  const editing = typeof req.currentYaml === 'string' && req.currentYaml.trim() !== '';
471
495
  const graphsDir = deps.graphsDir ?? defaultGraphsDir();
472
496
  const templates = kind === 'harness' ? listWizardTemplates(graphsDir) : [];
@@ -497,7 +521,7 @@ export async function generateGraphFromPrompt(req: GraphWizardRequest, deps: Gra
497
521
  for (attempts = 1; attempts <= maxAttempts; attempts += 1) {
498
522
  let text: string;
499
523
  try {
500
- text = await callLLM(buildPrompt({ req: { ...req, prompt }, kind, id, base: keywordBase, templates,
524
+ text = await callLLM(buildPrompt({ req: { ...req, prompt }, kind, id, base: keywordBase, templates, evidence,
501
525
  ...(attempts > 1 ? { previous: { yaml, issues } } : {}) }));
502
526
  } catch (error) {
503
527
  debug.log('graph.wizard', 'error', { id, kind, attempt: attempts, base, message: error instanceof Error ? error.message : String(error) });
@@ -514,6 +538,14 @@ export async function generateGraphFromPrompt(req: GraphWizardRequest, deps: Gra
514
538
  try { issues = validateWizardYaml(kind, yaml); }
515
539
  catch (error) { issues = [`검증 실패: ${error instanceof Error ? error.message : String(error)}`]; }
516
540
  steps = kind === 'harness' ? parseNodeAnnotations(yaml) : {};
541
+ if (req.packId && issues.length === 0) {
542
+ const raw = parseYaml(yaml) as { nodes?: Array<{ node_id: string; recipe: string }>; edges?: Array<{ from: string; map?: Record<string, string> }> };
543
+ const rag = (raw.nodes ?? []).find(node => node.recipe.startsWith('cmd:') && steps[node.node_id]?.step === 'knowledge-rag' && steps[node.node_id]?.arg === req.packId);
544
+ if (!rag || !(raw.edges ?? []).some(edge => edge.from === rag.node_id &&
545
+ typeof edge.map?.ok === 'string' && !['done', 'failed'].includes(edge.map.ok) &&
546
+ (raw.nodes ?? []).some(node => node.node_id === edge.map!.ok && node.recipe.startsWith('cmd:'))))
547
+ issues.push(`선택한 팩 ${req.packId} 의 knowledge-rag 노드를 산출 단계에 연결하라`);
548
+ }
517
549
  recipes = undefined;
518
550
  dryRun = undefined;
519
551
  if (kind === 'harness' && issues.length === 0) {
@@ -9,6 +9,7 @@ import { spawnSync } from 'node:child_process';
9
9
  import { existsSync, readFileSync } from 'node:fs';
10
10
  import { resolve } from 'node:path';
11
11
  import { debug } from '../debug/log.js';
12
+ import { queryInstalledPack } from '../knowledge/query.js';
12
13
 
13
14
  export interface WizardStepSpec {
14
15
  id: string;
@@ -28,6 +29,7 @@ export const WIZARD_STEPS: readonly WizardStepSpec[] = [
28
29
  { id: 'notify-me', use: '실패·포기 같은 사건을 나(대표)에게 알린다. arg = 알림 문구', outcomes: ['ok', 'fail'], timeoutMs: 60_000 },
29
30
  { id: 'gh-pr-review', use: 'GitHub PR 을 엘라누스 리뷰어로 리뷰한다. arg = PR 번호(없으면 실행 입력 input.pr). 결과 ok(must-fix 없음) · must-fix', outcomes: ['ok', 'must-fix', 'fail'], timeoutMs: 900_000 },
30
31
  { id: 'gh-pr-merge', use: 'GitHub PR 을 squash 머지한다. arg = PR 번호(없으면 input.pr)', outcomes: ['ok', 'fail'], timeoutMs: 300_000 },
32
+ { id: 'knowledge-rag', use: '설치된 지식 팩만 검색해 인용 가능한 근거를 낸다. arg = pack:<slug>@<version>; 실행 입력 input.query 또는 앞 단계 결과로 검색하며 둘 다 없으면 팩의 색인에서 가져온다', outcomes: ['ok', 'fail'], timeoutMs: 120_000 },
31
33
  { id: 'custom', use: '위 단계로 안 되는 외부 연동(노션·슬랙 업로드 등). 실행하면 «아직 구현되지 않음»으로 실패한다. arg = 무엇을 해야 하는지', outcomes: ['ok', 'fail'], timeoutMs: 60_000 },
32
34
  ];
33
35
 
@@ -140,6 +142,17 @@ export function runWizardStep(step: string, arg: string | undefined, ctx: GraphC
140
142
  const started = Date.now();
141
143
  let result: StepResult;
142
144
  switch (step) {
145
+ case 'knowledge-rag': {
146
+ const query = ctx.input && typeof ctx.input === 'object' && typeof (ctx.input as { query?: unknown }).query === 'string'
147
+ ? (ctx.input as { query: string }).query : prev.trim();
148
+ if (!arg) { result = { outcome: 'fail', error: 'knowledge-rag: pack id(arg) required' }; break; }
149
+ try {
150
+ const hits = queryInstalledPack(arg, query);
151
+ result = hits.length ? { outcome: 'ok', text: hits.slice(0, 5).map(hit => `[${hit.ref}] ${hit.title}: ${hit.body}`).join('\n') }
152
+ : { outcome: 'fail', error: `knowledge-rag: no matches in ${arg}` };
153
+ } catch (error) { result = { outcome: 'fail', error: error instanceof Error ? error.message : String(error) }; }
154
+ break;
155
+ }
143
156
  case 'web-search': {
144
157
  const query = arg?.trim() || (ctx.input && typeof ctx.input === 'object' && typeof (ctx.input as { query?: unknown }).query === 'string' ? (ctx.input as { query: string }).query : '');
145
158
  if (!query) { result = { outcome: 'fail', error: 'web-search: 검색어(arg)가 없다' }; break; }
@@ -1,4 +1,4 @@
1
- import { execFile, execFileSync, spawn } from 'node:child_process';
1
+ import { execFile, execFileSync, spawn, spawnSync } from 'node:child_process';
2
2
  import { promisify } from 'node:util';
3
3
  import { dlopen, FFIType } from 'bun:ffi';
4
4
  import { createHash } from 'node:crypto';
@@ -10,6 +10,7 @@ import { LogStore, logsDbPath } from '../mss/logging/log-store.js';
10
10
  import { loadSelfDevRun } from '../self-dev/run-store.js';
11
11
  import { normalizeRunId } from './harness-space.js';
12
12
  import { effectiveInstanceRoot, prodInstanceRoot } from '../instance/resolve.js';
13
+ import { stateDirSourceForChild } from '../agent/identity-env.js';
13
14
  import { lookupGoal, type GoalLookupOptions, type GoalLookupResult } from '../self-implement/goal-lookup.js';
14
15
  import { archiveGoals, type GoalArchiveOptions } from '../self-implement/goal-archive.js';
15
16
  import { detectBursts, formatIncidentBurstWarning, incidentLastLines, readRunExits, recentBurst, recordRunExit } from './harness-incidents.js';
@@ -23,13 +24,15 @@ import { resolveChildLlmEffort, resolveImplementationChildModel } from '../self-
23
24
  import { resolveHarnessTarget } from '../self-implement/harness-target-options.js';
24
25
  import { queryRunningRuns } from '../self-implement/running-runs.js';
25
26
  import { DevPipelineError } from '../self-dev/dev-pipeline.js';
26
- import { collectDraftMetrics, DRAFT_SWEEP_CONCURRENCY, mapBounded, runDraftSweep, sweepFailureReason, type DraftSweepAdapters, type DraftSweepResult, type SweepDraft, type SweepMergedPr, type SweepReviewGate } from '../self-dev/draft-sweep.js';
27
+ import { collectDraftMetrics, collectOverlapMetrics, DRAFT_SWEEP_CONCURRENCY, mapBounded, resolveDraftSweepCloseCap, runDraftSweep, sweepFailureReason, type DraftSweepAdapters, type DraftSweepResult, type OverlapMetricAdapters, type SweepDraft, type SweepMergedPr, type SweepReviewGate } from '../self-dev/draft-sweep.js';
28
+ import { readAskPreflightRowsSince, readSalvagedRowsSince } from '../self-dev/ask-launch-io.js';
27
29
  import { debug } from '../debug/log.js';
28
30
  import { installHarnessSalvageCommand, installHarnessSalvageRetentionCommand } from './harness-salvage-cli.js';
29
31
  import { decideNestedElanousLaunch, readNestedElanousDepth } from './nested-elanous-policy.js';
30
32
  import { dispatchTask, type DispatchTaskInput, type DispatchTaskDeps } from '../execution-loop/dispatch-task.js';
31
33
  import { launchRequestId, preLaunchGate, type PreLaunchGateDeps } from '../execution-loop/launch-gate.js';
32
34
  import { PR_LABELS } from '../github/pr-labels.js';
35
+ import { ghAutomationEnv } from '../autopilot/pr-manager.js';
33
36
  import { CODEX_PROVIDER, GROK_PROVIDER, decideBudget, decideLaunchBudget, readBudgetInputsLive, type BudgetDecision, type BudgetInputs } from '../self-implement/budget-gate.js';
34
37
  import { DEFAULT_FALLBACK_CHAIN } from '../oauth/fallback-chain.js';
35
38
  import { installDeliverableVerifyCliCommand, type InstallDeliverableVerifyCliDeps } from './deliverable-verify-cli.js';
@@ -1589,6 +1592,28 @@ function readProcessCwd(pid: number): Pick<HarnessProcessRecord, 'cwd' | 'cwdSta
1589
1592
  }
1590
1593
  }
1591
1594
 
1595
+ /** One `lsof` for many pids (a per-pid lsof costs ~0.1–0.3 s); lsof exits 1 when some pid is gone yet prints the rest. */
1596
+ function batchProcessCwdReader(pids: readonly number[]): (pid: number) => Pick<HarnessProcessRecord, 'cwd' | 'cwdStatus' | 'cwdFailureReason'> {
1597
+ const cwds = new Map<number, string>();
1598
+ let failure: string | undefined;
1599
+ if (pids.length) {
1600
+ const result = spawnSync('lsof', ['-a', '-d', 'cwd', '-Fpn', '-p', pids.join(',')],
1601
+ { encoding: 'utf8', timeout: 5_000, maxBuffer: 16 * 1024 * 1024, stdio: ['ignore', 'pipe', 'ignore'] });
1602
+ if (result.error || typeof result.stdout !== 'string') failure = observationFailureReason(result.error ?? new Error('lsof output missing'));
1603
+ else {
1604
+ let current: number | undefined;
1605
+ for (const line of result.stdout.split('\n')) {
1606
+ if (line.startsWith('p')) current = Number(line.slice(1));
1607
+ else if (line.startsWith('n') && current !== undefined && line.length > 1) cwds.set(current, line.slice(1).trim());
1608
+ }
1609
+ }
1610
+ }
1611
+ return (pid) => {
1612
+ const cwd = cwds.get(pid);
1613
+ return cwd ? { cwd, cwdStatus: 'observed' } : { cwdStatus: 'unknown', cwdFailureReason: failure ?? 'lsof cwd missing' };
1614
+ };
1615
+ }
1616
+
1592
1617
  const OWNERSHIP_ENV_KEYS = new Set<string>(Object.values(HARNESS_PROCESS_OWNERSHIP_ENV));
1593
1618
 
1594
1619
  function ownershipValueFromEntry(entry: string, key: string): string | undefined {
@@ -1744,10 +1769,23 @@ export function readProcessOwnership(
1744
1769
  }
1745
1770
  }
1746
1771
 
1747
- export function defaultListHarnessProcesses(): HarnessProcessListObservation {
1772
+ /** Narrows the per-process reads (`lsof` cwd ≈ 0.1 s each · `ps eww`) of `defaultListHarnessProcesses`. */
1773
+ export interface HarnessProcessListOptions {
1774
+ /** Keep only matching records; the others are dropped before any per-process read. */
1775
+ readonly include?: (record: HarnessProcessRecord) => boolean;
1776
+ /** Skip the ownership (`ps eww`) read — for callers that only need the cwd. */
1777
+ readonly cwdOnly?: boolean;
1778
+ /** Wall-clock budget for the per-process reads; records past it keep `cwdStatus: 'unknown'` (reason names the budget). */
1779
+ readonly deadlineMs?: number;
1780
+ readonly readCwd?: (pid: number) => Pick<HarnessProcessRecord, 'cwd' | 'cwdStatus' | 'cwdFailureReason'>;
1781
+ readonly psOutput?: () => string;
1782
+ readonly clock?: () => number;
1783
+ }
1784
+
1785
+ export function defaultListHarnessProcesses(options: HarnessProcessListOptions = {}): HarnessProcessListObservation {
1748
1786
  let out: string;
1749
1787
  try {
1750
- out = execFileSync('ps', ['-axo', 'pid=,ppid=,pcpu=,etime=,command='], {
1788
+ out = options.psOutput ? options.psOutput() : execFileSync('ps', ['-axo', 'pid=,ppid=,pcpu=,etime=,command='], {
1751
1789
  encoding: 'utf8',
1752
1790
  timeout: 5_000,
1753
1791
  stdio: ['ignore', 'pipe', 'ignore'],
@@ -1757,11 +1795,21 @@ export function defaultListHarnessProcesses(): HarnessProcessListObservation {
1757
1795
  }
1758
1796
  const parsed = parseHarnessProcessPsOutput(out);
1759
1797
  if (parsed.status === 'failed') return parsed;
1760
- const records = parsed.records.map((record) => ({
1761
- ...record,
1762
- ...readProcessCwd(record.pid),
1763
- ownership: readProcessOwnership(record.pid),
1764
- }));
1798
+ const clock = options.clock ?? Date.now;
1799
+ const selected = options.include ? parsed.records.filter(options.include) : parsed.records;
1800
+ const readCwd = options.readCwd ?? (options.cwdOnly ? batchProcessCwdReader(selected.map((record) => record.pid)) : readProcessCwd);
1801
+ const deadline = options.deadlineMs === undefined ? Infinity : clock() + options.deadlineMs;
1802
+ const records = selected.map((record) => {
1803
+ // A host with hundreds of harness processes made the serial reads take minutes (10-08 queue tick hang).
1804
+ if (clock() >= deadline) {
1805
+ return { ...record, cwdStatus: 'unknown' as const, cwdFailureReason: `observation deadline ${options.deadlineMs}ms exceeded` };
1806
+ }
1807
+ return {
1808
+ ...record,
1809
+ ...readCwd(record.pid),
1810
+ ...(options.cwdOnly ? {} : { ownership: readProcessOwnership(record.pid) }),
1811
+ };
1812
+ });
1765
1813
  if (parsed.status === 'incomplete') {
1766
1814
  return {
1767
1815
  status: 'incomplete',
@@ -1877,12 +1925,30 @@ type GithubPull = {
1877
1925
 
1878
1926
  type GhExecute = (args: string[]) => string;
1879
1927
 
1880
- // ⛔ `env: process.env` is required: Bun resolves the executable against the PATH it started with, not the one
1881
- // ensure-bin-path augments. Under cron's minimal PATH the hourly sweep died with «Executable not found: gh» (2026-09-28).
1882
- const executeGh: GhExecute = (args) => execFileSync('gh', args, {
1883
- encoding: 'utf8', timeout: 30_000, maxBuffer: 16 * 1024 * 1024,
1884
- stdio: ['ignore', 'pipe', 'pipe'], env: process.env,
1885
- });
1928
+ // Explicit env preserves the augmented PATH under Bun (cron may start with a minimal PATH).
1929
+ // Authentication comes from the same automation policy used by the PR manager, not the user's keyring.
1930
+ // Failure details are bounded and redact the token before reaching the sweep result or debug log.
1931
+ function ghFailure(args: string[], error: unknown, token: string | undefined): Error {
1932
+ const failure = error as { status?: number | null; code?: number | string; stderr?: string | Buffer };
1933
+ const exit = failure.status ?? (typeof failure.code === 'number' ? failure.code : 'unknown');
1934
+ const stderr = String(failure.stderr ?? '');
1935
+ const stderrTail = (token ? stderr.replaceAll(token, '[REDACTED]') : stderr).slice(-300).replace(/[\r\n]+/g, ' ').trim();
1936
+ try { debug.log('harness.drafts', 'gh-failed', { args0: args[0], exit, stderrTail }); }
1937
+ catch { /* Logging must not hide the gh failure. */ }
1938
+ // gh never ran (e.g. «Executable not found: gh»): no exit or stderr to report, so keep the spawn error itself.
1939
+ if (typeof failure.status !== 'number' && !stderrTail && error instanceof Error) return error;
1940
+ return new Error(`gh ${args.slice(0, 2).join(' ')} exit=${exit}: ${stderrTail}`);
1941
+ }
1942
+
1943
+ const executeGh: GhExecute = (args) => {
1944
+ const env = ghAutomationEnv(process.env);
1945
+ try {
1946
+ return execFileSync('gh', args, {
1947
+ encoding: 'utf8', timeout: 30_000, maxBuffer: 16 * 1024 * 1024,
1948
+ stdio: ['ignore', 'pipe', 'pipe'], env,
1949
+ });
1950
+ } catch (error) { throw ghFailure(args, error, env.GH_TOKEN); }
1951
+ };
1886
1952
 
1887
1953
  /** Merge-time adapter: reuse the sweep's GitHub inventory and worktree liveness without a second triage implementation. */
1888
1954
  export async function supersedeMergedGoalDrafts(
@@ -1986,9 +2052,14 @@ function deliverResyncMemo(spaceId: string, memo: ControlMemoPayload,
1986
2052
  }
1987
2053
 
1988
2054
  const execFileAsync = promisify(execFile);
1989
- const executeGhAsync = async (args: string[]): Promise<string> => (await execFileAsync('gh', args, {
1990
- encoding: 'utf8', timeout: 30_000, maxBuffer: 16 * 1024 * 1024, env: process.env,
1991
- })).stdout;
2055
+ const executeGhAsync = async (args: string[]): Promise<string> => {
2056
+ const env = ghAutomationEnv(process.env);
2057
+ try {
2058
+ return (await execFileAsync('gh', args, {
2059
+ encoding: 'utf8', timeout: 30_000, maxBuffer: 16 * 1024 * 1024, env,
2060
+ })).stdout;
2061
+ } catch (error) { throw ghFailure(args, error, env.GH_TOKEN); }
2062
+ };
1992
2063
 
1993
2064
  /** RUN-TTL adapters: open self-impl PRs, head commit time (async, bounded by the core), owning run, memo, label. */
1994
2065
  export async function githubRunTtlAdapters(repository: string, execute: GhExecute = executeGh,
@@ -2467,12 +2538,16 @@ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: Dr
2467
2538
 
2468
2539
  export interface HarnessDraftSweepDeps {
2469
2540
  readonly adapters?: DraftSweepAdapters;
2541
+ /** Optional overlap source injection; tests do not read GitHub or logs.db. */
2542
+ readonly overlapAdapters?: OverlapMetricAdapters;
2470
2543
  readonly repository?: () => string;
2471
2544
  readonly write?: (line: string) => void;
2472
2545
  readonly execute?: GhExecute;
2473
2546
  readonly now?: () => Date;
2474
2547
  /** RUN-TTL step adapters. Omitted with injected `adapters` ⇒ the step is skipped (fakes never reach real gh). */
2475
2548
  readonly runTtl?: RunTtlAdapters;
2549
+ /** Test seam for `tools.selfImplement.draftSweepCloseCap`; omitted ⇒ the user config. */
2550
+ readonly closeCapConfig?: () => unknown;
2476
2551
  }
2477
2552
 
2478
2553
  /** RUN-TTL step inside the draft sweep — fail-soft: its failure never changes the sweep verdict. */
@@ -2566,10 +2641,22 @@ function installHarnessDraftSweepCommand(harnessCmd: Command, deps: HarnessDraft
2566
2641
  const repository = resolveRepositoryName({ repo: opts.repo ?? deps.repository?.(), executeGh: (args) => execute(args) });
2567
2642
  if (!DRAFT_SWEEP_REPOSITORY.test(repository) || repository.includes('..')) throw new HarnessCliInputError(`invalid --repo (expected owner/name): ${repository}`);
2568
2643
  const started = Date.now();
2569
- const metrics = await collectDraftMetrics(repository, deps.adapters ?? githubDraftSweepAdapters(deps.execute), deps.now?.() ?? new Date());
2644
+ const adapters = deps.adapters ?? githubDraftSweepAdapters(deps.execute);
2645
+ const now = deps.now?.() ?? new Date();
2646
+ const metrics = await collectDraftMetrics(repository, adapters, now);
2647
+ let overlap: Awaited<ReturnType<typeof collectOverlapMetrics>> | null = null;
2648
+ try {
2649
+ overlap = await collectOverlapMetrics(repository, deps.overlapAdapters ?? {
2650
+ ...adapters,
2651
+ listPreflightRows: readAskPreflightRowsSince,
2652
+ listSalvagedRows: readSalvagedRowsSince,
2653
+ }, now);
2654
+ } catch (error) {
2655
+ try { debug.log('self-dev.draft-metrics', 'overlap-failed', { repository, reason: sweepFailureReason(error) }); } catch { /* fail-soft */ }
2656
+ }
2570
2657
  try { debug.log('self-dev.draft-metrics', 'collected', { repository, ms: Date.now() - started, ...metrics }); } catch { /* fail-soft */ }
2571
2658
  const write = deps.write ?? console.log;
2572
- if (opts.json) write(JSON.stringify({ repository, ...metrics }));
2659
+ if (opts.json) write(JSON.stringify({ repository, ...metrics, overlap }));
2573
2660
  else write(`draft 재고 ${metrics.inventory} · 최장 나이 ${metrics.oldestAgeHours === null ? '해당 없음' : `${metrics.oldestAgeHours.toFixed(1)}h`} · needs-owner ${metrics.needsOwner} · 48h 전환율 ${metrics.conversion48h === null ? '표본 없음' : `${(metrics.conversion48h * 100).toFixed(1)}% (${metrics.converted48h}/${metrics.cohort48h})`}`);
2574
2661
  } catch (error) {
2575
2662
  console.error(humanErrorLine(error));
@@ -2580,7 +2667,8 @@ function installHarnessDraftSweepCommand(harnessCmd: Command, deps: HarnessDraft
2580
2667
  .option('--apply', '판정한 라벨 변경과 종료를 적용')
2581
2668
  .option('--json', '판정 결과를 JSON 으로 출력')
2582
2669
  .option('--repo <owner/name>', '조회할 GitHub 저장소')
2583
- .action(async (opts: { apply?: boolean; json?: boolean; repo?: string }) => {
2670
+ .option('--close-cap <n>', '이번 틱 최대 종료 수 (1~100 · config tools.selfImplement.draftSweepCloseCap 보다 우선 · 기본 10)')
2671
+ .action(async (opts: { apply?: boolean; json?: boolean; repo?: string; closeCap?: string }) => {
2584
2672
  try {
2585
2673
  const write = deps.write ?? console.log;
2586
2674
  const execute = deps.execute ?? executeGh;
@@ -2593,7 +2681,16 @@ function installHarnessDraftSweepCommand(harnessCmd: Command, deps: HarnessDraft
2593
2681
  }
2594
2682
  // One adapter instance per sweep: its open listing and ledger scan are shared with the RUN-TTL step.
2595
2683
  const github = deps.adapters ? undefined : githubDraftSweepAdapters(deps.execute);
2596
- const result: DraftSweepResult = await runDraftSweep({ repository, apply: opts.apply === true, adapters: deps.adapters ?? github! });
2684
+ const configCap = deps.closeCapConfig
2685
+ ? deps.closeCapConfig()
2686
+ : (() => { try { return getUserConfig().tools?.selfImplement?.draftSweepCloseCap; } catch { return undefined; } })();
2687
+ const cap = resolveDraftSweepCloseCap({ flag: opts.closeCap, config: configCap });
2688
+ for (const warning of cap.warnings) {
2689
+ console.error(`⚠ ${warning}`);
2690
+ try { debug.log('self-dev.draft-sweep', 'close-cap-rejected', { warning, closeCap: cap.closeCap, closeCapSource: cap.closeCapSource }); } catch { /* fail-soft */ }
2691
+ }
2692
+ const result: DraftSweepResult = await runDraftSweep({ repository, apply: opts.apply === true, adapters: deps.adapters ?? github!,
2693
+ closeCap: cap.closeCap, closeCapSource: cap.closeCapSource });
2597
2694
  const reason = result.error ? sweepFailureReason(result.error) : undefined;
2598
2695
  const ttlStarted = Date.now();
2599
2696
  const runTtl = await draftSweepRunTtl(repository, deps, github);
@@ -2944,7 +3041,8 @@ export function installHarnessCliCommand(program: Command, deps: HarnessCliComma
2944
3041
  harnessQueueReceiptPath(root, item.launchId!), queueDeps.launchCommand ?? join(import.meta.dir, '../../bin/elanous.mjs'),
2945
3042
  ...(resolve(root) === prodInstanceRoot() ? [] : [`--test=${root}`]), ...args], {
2946
3043
  cwd: item.launchCwd ?? resolve(import.meta.dir, '../..'), stdio: 'inherit',
2947
- env: { ...process.env, ELANOUS_STATE_DIR: root, ELANOUS_HARNESS_SEAT: item.seat,
3044
+ env: { ...process.env, ELANOUS_STATE_DIR: root,
3045
+ ELANOUS_STATE_DIR_SOURCE: stateDirSourceForChild(root), ELANOUS_HARNESS_SEAT: item.seat,
2948
3046
  ELANOUS_HARNESS_QUEUE_LAUNCH: item.launchId! },
2949
3047
  });
2950
3048
  // Capture the end at spawn time: a child that already closed (or died by a signal) must not leave the parent waiting.