elanous 0.2.11 → 0.2.13

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 (342) 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/{1394-556de6d71bbdb0fb.js → 1394-931c9827067b2276.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/3869-3cd46526879f454c.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/{3992-2e522a8ac5f2afde.js → 3992-fe5c06d6b31f8b25.js} +1 -1
  6. package/apps/pwa/out/_next/static/chunks/5250-c78ca295585bbf95.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/6511-ff8360be3d65aaa4.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/6588-ba380869b67c4fa7.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/7758-85c2b53f731da024.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/{8605-ea5bee9dfae512c7.js → 8605-9da4678f8a310d18.js} +1 -1
  11. package/apps/pwa/out/_next/static/chunks/9906-e7b8b5310a169dfe.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/app/approvals/page-e5b4e1b256f41076.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-6a061bd97f0b9398.js → page-e49b9b76c565a0b1.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/app/board/page-b4f032c893b21cc9.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/app/botlab/page-f14987d5850a31e0.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/app/bots/page-b433308aad13ceb6.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/app/chat/{page-7c97dcdfe2e37a60.js → page-4c1dbb21861beab2.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/consult/page-c85b816e3b269dbb.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/app/control/{page-5721537ca8a3598c.js → page-f6bc66b919b2ea7d.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-b654240a572e4c48.js → page-60603c1e056c9b25.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/app/editor/{page-b9750abc897508c3.js → page-a0ea8f5a2341ee6b.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/app/exec/page-45c5bf04b4a605e6.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/app/field/page-36c6c4183353ac9e.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/app/inside/page-cb2eb0fd809980da.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/intake/{page-c8b1ccda6ba7ae98.js → page-31012264da8a341f.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/app/{layout-d6b1d156fb95e74b.js → layout-50309aeb0bdd554a.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/app/live/page-bc00e964e855f4df.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/app/market/{page-9688f5b4f1ff950f.js → page-0696ee3fac9fe56c.js} +1 -1
  29. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66c27608f651b866.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/app/morning/{page-35414e0a007b4082.js → page-6ddd2109b7fc3a80.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/not-found-4e50b7298c1f69a5.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/observatory/{page-e001823c35c9dd67.js → page-7a2bf830038a2264.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-371423d1a573d64b.js → page-1f327f18694fabca.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-4b764b125339ac17.js → page-e5cef2fe6e4028df.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/ops/seats/{page-cdf8e4b01067bcf1.js → page-cf596128d2b420ba.js} +1 -1
  36. package/apps/pwa/out/_next/static/chunks/app/outputs/{page-3a24072b35470157.js → page-f672536f14f0969f.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/app/page-b52365662232db89.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/app/reflection/page-e7a6c80f288b1948.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-d37cb0d52f9b6bd5.js → page-29a709616df9b050.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/sessions/{page-686da0df9bf23e5c.js → page-fee842d7c2151129.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-4dc614f3ce043e63.js → page-78b40a344ed22f24.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/app/settings/page-75d199f448171014.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-d02c782fc792e2af.js → page-a011dd5b0bff29c5.js} +1 -1
  44. package/apps/pwa/out/_next/static/chunks/app/setup/{page-239c17b911b84e72.js → page-d92e32d51ab5157e.js} +1 -1
  45. package/apps/pwa/out/_next/static/chunks/app/share/{page-dc6a79fb003f746c.js → page-5f21aed6932ef375.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/app/showroom/{page-0fffb84cafdc9da6.js → page-1e1a53946804debf.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/app/tasks/{page-1218753c82d585f6.js → page-a1833cd43d832601.js} +1 -1
  48. package/apps/pwa/out/_next/static/chunks/app/term/{page-4b5935e67531efb5.js → page-63d14a4d8fe1e1c1.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/today/{page-3285bc87caf1eaa5.js → page-2a511d320f762e06.js} +1 -1
  50. package/apps/pwa/out/_next/static/chunks/app/trace/{page-ad553b0e06505ecd.js → page-1c3e03f88dac1e21.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/vault/{page-fca096e3e860306f.js → page-c4d5289c2ea97a89.js} +1 -1
  52. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-9ebfa78f0d9885c3.js → page-b91f8952deaeaf27.js} +1 -1
  53. package/apps/pwa/out/_next/static/chunks/app/workflows/{page-3dc5f21f7e39622d.js → page-71b43a2588489987.js} +1 -1
  54. package/apps/pwa/out/_next/static/chunks/app/workspace/page-2b256feed331546f.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-505bb96d69a8622c.js +1 -0
  56. package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +1 -0
  57. package/apps/pwa/out/approvals/index.html +1 -1
  58. package/apps/pwa/out/approvals/index.txt +14 -14
  59. package/apps/pwa/out/autopilot/index.html +1 -1
  60. package/apps/pwa/out/autopilot/index.txt +14 -14
  61. package/apps/pwa/out/board/index.html +1 -1
  62. package/apps/pwa/out/board/index.txt +14 -14
  63. package/apps/pwa/out/botlab/index.html +1 -1
  64. package/apps/pwa/out/botlab/index.txt +14 -14
  65. package/apps/pwa/out/bots/index.html +1 -1
  66. package/apps/pwa/out/bots/index.txt +14 -14
  67. package/apps/pwa/out/chat/index.html +1 -1
  68. package/apps/pwa/out/chat/index.txt +14 -14
  69. package/apps/pwa/out/consult/index.html +1 -1
  70. package/apps/pwa/out/consult/index.txt +14 -14
  71. package/apps/pwa/out/control/index.html +1 -1
  72. package/apps/pwa/out/control/index.txt +14 -14
  73. package/apps/pwa/out/design-check/index.html +1 -1
  74. package/apps/pwa/out/design-check/index.txt +14 -14
  75. package/apps/pwa/out/editor/index.html +1 -1
  76. package/apps/pwa/out/editor/index.txt +14 -14
  77. package/apps/pwa/out/exec/index.html +1 -1
  78. package/apps/pwa/out/exec/index.txt +14 -14
  79. package/apps/pwa/out/field/index.html +1 -1
  80. package/apps/pwa/out/field/index.txt +14 -14
  81. package/apps/pwa/out/healthz +1 -1
  82. package/apps/pwa/out/index.html +1 -1
  83. package/apps/pwa/out/index.txt +14 -14
  84. package/apps/pwa/out/inside/index.html +1 -1
  85. package/apps/pwa/out/inside/index.txt +14 -14
  86. package/apps/pwa/out/intake/index.html +1 -1
  87. package/apps/pwa/out/intake/index.txt +14 -14
  88. package/apps/pwa/out/live/index.html +1 -1
  89. package/apps/pwa/out/live/index.txt +14 -14
  90. package/apps/pwa/out/market/index.html +1 -1
  91. package/apps/pwa/out/market/index.txt +14 -14
  92. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  93. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  94. package/apps/pwa/out/morning/index.html +1 -1
  95. package/apps/pwa/out/morning/index.txt +14 -14
  96. package/apps/pwa/out/observatory/index.html +1 -1
  97. package/apps/pwa/out/observatory/index.txt +14 -14
  98. package/apps/pwa/out/ops/checklist/index.html +1 -1
  99. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  100. package/apps/pwa/out/ops/release/index.html +1 -1
  101. package/apps/pwa/out/ops/release/index.txt +14 -14
  102. package/apps/pwa/out/ops/seats/index.html +1 -1
  103. package/apps/pwa/out/ops/seats/index.txt +14 -14
  104. package/apps/pwa/out/outputs/index.html +1 -1
  105. package/apps/pwa/out/outputs/index.txt +14 -14
  106. package/apps/pwa/out/reflection/index.html +1 -1
  107. package/apps/pwa/out/reflection/index.txt +15 -15
  108. package/apps/pwa/out/scheduler/index.html +1 -1
  109. package/apps/pwa/out/scheduler/index.txt +14 -14
  110. package/apps/pwa/out/sessions/index.html +1 -1
  111. package/apps/pwa/out/sessions/index.txt +14 -14
  112. package/apps/pwa/out/settings/devices/index.html +1 -1
  113. package/apps/pwa/out/settings/devices/index.txt +14 -14
  114. package/apps/pwa/out/settings/index.html +1 -1
  115. package/apps/pwa/out/settings/index.txt +14 -14
  116. package/apps/pwa/out/setup/done/index.html +1 -1
  117. package/apps/pwa/out/setup/done/index.txt +14 -14
  118. package/apps/pwa/out/setup/index.html +1 -1
  119. package/apps/pwa/out/setup/index.txt +14 -14
  120. package/apps/pwa/out/share/index.html +1 -1
  121. package/apps/pwa/out/share/index.txt +14 -14
  122. package/apps/pwa/out/showroom/index.html +1 -1
  123. package/apps/pwa/out/showroom/index.txt +14 -14
  124. package/apps/pwa/out/tasks/index.html +1 -1
  125. package/apps/pwa/out/tasks/index.txt +14 -14
  126. package/apps/pwa/out/term/index.html +1 -1
  127. package/apps/pwa/out/term/index.txt +14 -14
  128. package/apps/pwa/out/today/index.html +1 -1
  129. package/apps/pwa/out/today/index.txt +14 -14
  130. package/apps/pwa/out/trace/index.html +1 -1
  131. package/apps/pwa/out/trace/index.txt +14 -14
  132. package/apps/pwa/out/vault/index.html +1 -1
  133. package/apps/pwa/out/vault/index.txt +14 -14
  134. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  135. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  136. package/apps/pwa/out/workflows/index.html +1 -1
  137. package/apps/pwa/out/workflows/index.txt +14 -14
  138. package/apps/pwa/out/workspace/index.html +1 -1
  139. package/apps/pwa/out/workspace/index.txt +14 -14
  140. package/apps/pwa/out/worktrees/index.html +1 -1
  141. package/apps/pwa/out/worktrees/index.txt +14 -14
  142. package/examples/teams/personas/backend.yaml +4 -0
  143. package/examples/teams/personas/pm.yaml +4 -0
  144. package/examples/teams/personas/qa.yaml +4 -0
  145. package/examples/teams/small-product.yaml +20 -0
  146. package/graphs/context/nightly-condense.yaml +21 -0
  147. package/graphs/context/recipes.yaml +12 -0
  148. package/graphs/demo/inside-seed.yaml +20 -0
  149. package/graphs/demo/recipes.yaml +12 -0
  150. package/graphs/lecture/lecture-note.yaml +28 -0
  151. package/graphs/lecture/recipes.yaml +17 -0
  152. package/package.json +2 -1
  153. package/plugins/card-followup/examples/sample-card.png +0 -0
  154. package/scripts/context-hooks/emit.ts +36 -14
  155. package/scripts/coord-channel-watch.sh +1 -0
  156. package/scripts/discord-seat-probe.ts +144 -0
  157. package/scripts/hq/hq-standby.sh +65 -0
  158. package/scripts/hq/standby-promote.ts +173 -0
  159. package/scripts/hq/standby-role.ts +59 -0
  160. package/scripts/hq/standby-snapshot.ts +226 -0
  161. package/scripts/hq/standby-verify.ts +219 -0
  162. package/scripts/lecture-note/node.ts +256 -0
  163. package/scripts/lib/telegram-inject-args.ts +31 -0
  164. package/scripts/measure-pod-memory-by-goal.ts +130 -0
  165. package/scripts/release-loop/auto-approve-node.ts +1 -1
  166. package/scripts/release-loop/checklist-gate-node.ts +2 -1
  167. package/scripts/release-loop/gate-node.ts +61 -17
  168. package/scripts/release-loop/ops-upgrade-node.ts +59 -6
  169. package/scripts/release-loop/publish-node.ts +4 -1
  170. package/scripts/release-loop/version-node.ts +13 -3
  171. package/scripts/telegram-inject.ts +14 -6
  172. package/scripts/test-deterministic.ts +53 -3
  173. package/scripts/test-diet/remeasure-list.ts +112 -0
  174. package/scripts/ux/demo-rehearsal.ts +17 -13
  175. package/scripts/ux/demo-seed.ts +108 -0
  176. package/scripts/ux/lib/rehearsal-verdict.ts +12 -5
  177. package/skills/omni-crawl/SKILL.md +16 -16
  178. package/skills/omni-crawl/package-lock.json +1 -544
  179. package/skills/omni-crawl/package.json +3 -4
  180. package/skills/omni-crawl/scripts/main.ts +13 -13
  181. package/skills/omni-crawl/scripts/monitor.ts +8 -8
  182. package/skills/omni-crawl/src/apify.ts +13 -2
  183. package/skills/omni-crawl/src/capture.ts +6 -0
  184. package/skills/omni-crawl/src/registry.ts +2 -2
  185. package/skills/omni-crawl/src/stdout-json.ts +4 -0
  186. package/skills/omni-digest/SKILL.md +10 -11
  187. package/skills/omni-digest/package-lock.json +0 -545
  188. package/skills/omni-digest/package.json +2 -5
  189. package/skills/omni-digest/scripts/main.ts +3 -3
  190. package/skills/omni-digest/src/delegate.ts +5 -5
  191. package/skills/omni-digest/src/fetch.ts +2 -2
  192. package/skills/omni-digest/src/media.ts +25 -11
  193. package/skills/omni-digest/src/visual.ts +3 -2
  194. package/skills/omni-market/scripts/main.ts +1 -1
  195. package/skills/omni-market/scripts/stdout-json.ts +4 -0
  196. package/src/agent/identity-env.ts +11 -0
  197. package/src/agent-mission/driver.ts +150 -23
  198. package/src/agent-mission/duo.ts +94 -0
  199. package/src/agent-mission/pty-decision.ts +2 -2
  200. package/src/autopilot/pty-control-loop.ts +4 -1
  201. package/src/autopilot/track-agent-shadow.ts +2 -1
  202. package/src/autopilot/track-agent.ts +4 -1
  203. package/src/boot/daemon-prompt-request.ts +3 -0
  204. package/src/boot/daemon-prompt-turn.ts +6 -2
  205. package/src/boot/daemon-runtime.ts +48 -16
  206. package/src/chat/index.ts +1 -0
  207. package/src/cli/decisions-cli.ts +51 -19
  208. package/src/cli/outputs-cli.ts +92 -0
  209. package/src/cli/persona-cli.ts +45 -13
  210. package/src/cli/pod-cli.ts +27 -3
  211. package/src/cli/pod-memory-advice.ts +35 -0
  212. package/src/cli/pr-cli.ts +15 -1
  213. package/src/cli/release-cli.ts +135 -15
  214. package/src/context-bus/condense.ts +194 -0
  215. package/src/context-bus/context-day.ts +82 -0
  216. package/src/context-bus/context-now-surfaces.ts +32 -2
  217. package/src/context-bus/context-now.ts +2 -2
  218. package/src/context-bus/coord-events.ts +2 -2
  219. package/src/context-bus/long-term-memory.ts +118 -22
  220. package/src/context-bus/nightly-condense.ts +153 -0
  221. package/src/context-bus/session-events.ts +35 -0
  222. package/src/dashboard/dashboard-status-lines.ts +2 -0
  223. package/src/dashboard/index.ts +8 -0
  224. package/src/dashboard/slash-runtime/dashboard-handlers.ts +24 -0
  225. package/src/decisions/decision-cards.ts +13 -7
  226. package/src/decisions/decision-ledger.ts +43 -17
  227. package/src/domains/outbound-alert.ts +99 -31
  228. package/src/domains/outbound-log-probe.ts +8 -0
  229. package/src/execution-loop/launch-gate.ts +17 -8
  230. package/src/field-note/telegram.ts +142 -0
  231. package/src/graph-kinds/registry.ts +2 -2
  232. package/src/graph-runner/graph-approval-card.ts +2 -2
  233. package/src/graph-runner/graph-grow-llm.ts +23 -13
  234. package/src/graph-runner/graph-grow.ts +69 -13
  235. package/src/graph-runner/graph-tick.ts +9 -2
  236. package/src/graph-runner/runner.ts +92 -24
  237. package/src/harness/harness-cli-command.ts +165 -17
  238. package/src/harness/harness-queue-child.ts +37 -0
  239. package/src/harness/harness-queue.ts +306 -0
  240. package/src/hooks/consult-intake.ts +153 -0
  241. package/src/hooks/github-shadow.ts +72 -2
  242. package/src/hooks/receiver.ts +82 -3
  243. package/src/hq/hq.ts +318 -0
  244. package/src/hq/lease.ts +210 -0
  245. package/src/hq/ledger-write-target.ts +41 -0
  246. package/src/index.ts +232 -16
  247. package/src/intake-plane/discord-seat-work.ts +36 -3
  248. package/src/intake-plane/linear-wish.ts +44 -0
  249. package/src/intake-plane/persona-answer.ts +5 -0
  250. package/src/intake-plane/telegram-seat-work.ts +13 -6
  251. package/src/intake-plane/wish-card.ts +33 -0
  252. package/src/llm/difficulty.ts +1 -1
  253. package/src/loops/checker.ts +178 -0
  254. package/src/loops/loop-cli.ts +37 -3
  255. package/src/loops/observe.ts +49 -0
  256. package/src/loops/orchestrator/overlap.ts +141 -0
  257. package/src/loops/orchestrator/traffic.ts +150 -0
  258. package/src/loops/package/verify.ts +125 -0
  259. package/src/loops/verdict.ts +22 -0
  260. package/src/maturity/feature-maturity.ts +5 -4
  261. package/src/memory.ts +37 -4
  262. package/src/nexus/api/card-followup-route.ts +118 -0
  263. package/src/nexus/api/decisions-route.ts +68 -0
  264. package/src/nexus/api/http-server.ts +78 -8
  265. package/src/nexus/api/outbound-report.ts +40 -7
  266. package/src/nexus/api/personas.ts +97 -54
  267. package/src/nexus/api/skill-exec-route.ts +97 -0
  268. package/src/nexus/api/task-cards-api.ts +19 -0
  269. package/src/nexus/api/workflow-history.ts +88 -0
  270. package/src/nexus/api/workflows.ts +52 -0
  271. package/src/nexus/outbound/delivery-ledger.ts +17 -8
  272. package/src/nexus/outbound/router.ts +8 -3
  273. package/src/persona/loader.ts +18 -0
  274. package/src/persona/persona-todo.ts +14 -9
  275. package/src/persona/presets.ts +10 -2
  276. package/src/persona/team.ts +105 -0
  277. package/src/persona/types.ts +4 -0
  278. package/src/plugins/maker/market-bundle.ts +2 -1
  279. package/src/pod-lease/host-lease.ts +130 -20
  280. package/src/release-loop/checklist.ts +15 -3
  281. package/src/release-loop/feature-store.ts +24 -10
  282. package/src/release-loop/landed-but-yellow.ts +60 -0
  283. package/src/seat-dispatch/seat-ask.ts +57 -12
  284. package/src/seat-loop/seat-loop.ts +253 -18
  285. package/src/seat-loop/seat-pack.ts +127 -0
  286. package/src/self-dev/ask-launch-flow.ts +3 -0
  287. package/src/self-dev/draft-sweep.ts +44 -10
  288. package/src/self-dev/draft-triage-rules.ts +54 -2
  289. package/src/self-implement/budget-gate.ts +29 -12
  290. package/src/self-implement/gate-candidate-ranking.ts +60 -0
  291. package/src/self-implement/gate-scope.ts +9 -8
  292. package/src/self-implement/headless-elanous-driver.ts +7 -2
  293. package/src/self-implement/l8-merge-queue.ts +157 -1
  294. package/src/self-implement/orchestrator.ts +15 -0
  295. package/src/self-implement/test-seams.ts +1 -0
  296. package/src/steward/launch.ts +5 -1
  297. package/src/steward/readiness.ts +125 -0
  298. package/src/steward/rescue.ts +75 -0
  299. package/src/steward/triage.ts +48 -9
  300. package/src/task-cards/card-cli.ts +78 -0
  301. package/src/task-orchestrator/surfaces/pod-command-job.ts +14 -10
  302. package/src/task-orchestrator/surfaces/pod-lease.ts +18 -6
  303. package/src/task-orchestrator/surfaces/pod-pool.ts +27 -10
  304. package/src/task-orchestrator/surfaces/self-implement-pod.ts +46 -8
  305. package/src/telegram-commands.ts +21 -0
  306. package/src/telegram-dispatch-forward.ts +1 -1
  307. package/src/telegram-fabric-plan.ts +90 -0
  308. package/src/telegram-project-command.ts +84 -4
  309. package/src/telegram.ts +37 -5
  310. package/src/tool-runtime/elanous-control-runtimes.ts +2 -1
  311. package/src/user-config.ts +101 -13
  312. package/src/version/packed-revision.json +1 -1
  313. package/src/workflow-runtime/executor.ts +4 -0
  314. package/src/workflow-runtime/node-catalog.ts +13 -0
  315. package/src/workflow-runtime/nodes/knowledge.ts +38 -0
  316. package/src/workflow-runtime/schema.ts +17 -1
  317. package/src/workflow-runtime/types.ts +13 -1
  318. package/apps/pwa/out/_next/static/chunks/2310-7a59f043f6db207e.js +0 -1
  319. package/apps/pwa/out/_next/static/chunks/2548-6e316138d347cefe.js +0 -1
  320. package/apps/pwa/out/_next/static/chunks/3869-a39d1d9921e10d43.js +0 -1
  321. package/apps/pwa/out/_next/static/chunks/507-5acd98bd6dab2d8a.js +0 -1
  322. package/apps/pwa/out/_next/static/chunks/5445-745eb795c5963d79.js +0 -1
  323. package/apps/pwa/out/_next/static/chunks/9906-482c6619b3fdda20.js +0 -1
  324. package/apps/pwa/out/_next/static/chunks/app/approvals/page-1dcdc58fc2de2454.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/app/board/page-cb95a7432c9ee744.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/app/botlab/page-b12b6d2f8d2d1aea.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/bots/page-b9efe868bfd36255.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/consult/page-8fd36984f549270a.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/exec/page-4cd1de371f8ad048.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/field/page-d99f89657828421c.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/inside/page-50cb2a0eb15e6c83.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/live/page-2a9932ddb62d76bd.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-6b15dd6456c5df09.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/not-found-e7a529e0507ac86c.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/page-94c82eb0ae5abb32.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/app/reflection/page-0be8d4b2ced75ba0.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/app/settings/page-53f80edc6b8dc07c.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/app/workspace/page-c6da2b468454532d.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-93bc70e36f595414.js +0 -1
  340. package/apps/pwa/out/_next/static/css/543f56bcb1f79fca.css +0 -1
  341. /package/apps/pwa/out/_next/static/{Y4zKzL62sakG5momz3SoQ → D51l8jUBplJrIpWvENsDK}/_buildManifest.js +0 -0
  342. /package/apps/pwa/out/_next/static/{Y4zKzL62sakG5momz3SoQ → D51l8jUBplJrIpWvENsDK}/_ssgManifest.js +0 -0
@@ -1,3 +1,6 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { DecisionLedger, type DecisionEntry } from '../decisions/decision-ledger.js';
3
+ import { effectiveInstanceRoot } from '../instance/resolve.js';
1
4
  import { createGraphVariant } from '../self-implement/graph-variant.js';
2
5
  import type { GraphEdgeSpec, GraphNodeSpec, GraphTemplateSpec } from '../self-implement/graph-yaml.js';
3
6
  import { createLLMGrowthProposer } from './graph-grow-llm.js';
@@ -10,6 +13,8 @@ export interface GrowthProposal {
10
13
 
11
14
  export type GrowthProposer = (input: { graph: GraphTemplateSpec; nodeId: string; outcome: string; runId: string; output?: unknown }) => GrowthProposal | undefined | Promise<GrowthProposal | undefined>;
12
15
  export type GrowthRecipeEffect = 'read-only' | 'external-effect' | 'unknown';
16
+ export interface GrowthCommand { command: string; dry_run_command?: string; timeout_ms?: number }
17
+ export interface GrowthRecipeClassification { effect: GrowthRecipeEffect; command?: GrowthCommand; approval?: string }
13
18
 
14
19
  export interface GraphGrowth {
15
20
  node: GraphNodeSpec;
@@ -18,30 +23,81 @@ export interface GraphGrowth {
18
23
  undo: { removeNode: string; removeOutcome: { from: string; outcome: string }; removeEdge?: GraphEdgeSpec };
19
24
  }
20
25
 
26
+ export interface GrowthDecisionDeps {
27
+ ledger?: Pick<DecisionLedger, 'raiseOnce'>;
28
+ root?: string;
29
+ now?: () => Date;
30
+ }
31
+
32
+ function proposalGrowth(input: Parameters<GrowthProposer>[0], proposal: GrowthProposal): GraphGrowth {
33
+ const edges: GraphEdgeSpec[] = [
34
+ { from: input.nodeId, on: 'outcome', map: { [input.outcome]: proposal.node.nodeId } },
35
+ { from: proposal.node.nodeId, to: proposal.returnTo as string },
36
+ ];
37
+ return { node: proposal.node, edges, reason: proposal.reason,
38
+ undo: { removeNode: proposal.node.nodeId, removeOutcome: { from: input.nodeId, outcome: input.outcome }, removeEdge: edges[1]! } };
39
+ }
40
+
41
+ /** The card's own ref commits to the entire growth and resolved recipe, independently of the parked run state. */
42
+ export function growthApprovalRef(identity: { graphId: string; runId: string; from: string; outcome: string },
43
+ growth: GraphGrowth, command?: GrowthCommand, approval?: string): string {
44
+ const hash = createHash('sha256').update(JSON.stringify({ identity, growth, command, approval })).digest('hex');
45
+ return `graph-growth:${hash}`;
46
+ }
47
+
48
+ export function growthDecisionRef(input: Parameters<GrowthProposer>[0], proposal: GrowthProposal, command?: GrowthCommand, approval?: string): string {
49
+ return growthApprovalRef({ graphId: input.graph.graphId, runId: input.runId, from: input.nodeId, outcome: input.outcome },
50
+ proposalGrowth(input, proposal), command, approval);
51
+ }
52
+
53
+ export function growthDecision(input: Parameters<GrowthProposer>[0], proposal: GrowthProposal, deps: GrowthDecisionDeps = {}, command?: GrowthCommand, approval?: string): DecisionEntry {
54
+ const ledger = deps.ledger ?? new DecisionLedger({ stateDir: deps.root ?? effectiveInstanceRoot() });
55
+ const ref = growthDecisionRef(input, proposal, command, approval);
56
+ const summary = `${proposal.node.nodeId} (${proposal.node.kind}, ${proposal.node.recipe}) → ${proposal.returnTo}`;
57
+ const commandDigest = command ? createHash('sha256').update(JSON.stringify(command)).digest('hex') : undefined;
58
+ return ledger.raiseOnce({ title: `${input.graph.graphId} / ${input.nodeId} / ${input.outcome} growth approval`, category: 'scope',
59
+ scqa: { s: `Graph ${input.graph.graphId}, node ${input.nodeId}, outcome ${input.outcome} has no mapped growth edge.`,
60
+ c: `Proposed node: ${summary}; command SHA-256: ${commandDigest ?? 'unresolved'}; reason: ${proposal.reason}`.slice(0, 240) },
61
+ options: [{ key: 'a', label: '승인', consequence: `Apply proposed node ${proposal.node.nodeId} and continue the run` },
62
+ { key: 'b', label: '거절', consequence: 'Keep the original fallback route' }],
63
+ recommendation: { skipped: true, reason: 'Side-effect recipe requires a human decision' },
64
+ ...(command || approval ? { pendingQuestion: [approval ? `Approval recipe: ${approval}` : undefined,
65
+ command ? `Proposed command (${commandDigest}):\n${command.command}` : undefined].filter(Boolean).join('\n') } : {}),
66
+ raisedBy: { agent: 'graph-runner' }, refs: [ref], dueAt: new Date((deps.now?.() ?? new Date()).getTime() + 8 * 60 * 60 * 1000).toISOString() }, ref);
67
+ }
68
+
21
69
  export async function proposeGrowth(input: Parameters<GrowthProposer>[0],
22
- proposer: GrowthProposer = createLLMGrowthProposer(), classifyRecipe?: (node: GraphNodeSpec) => GrowthRecipeEffect): Promise<{ ok: true; graph: GraphTemplateSpec; growth: GraphGrowth } | { ok: false; reason: string; park?: boolean }> {
70
+ proposer: GrowthProposer = createLLMGrowthProposer(), classifyRecipe?: (node: GraphNodeSpec) => GrowthRecipeEffect | GrowthRecipeClassification,
71
+ decisionDeps: GrowthDecisionDeps = {}): Promise<{ ok: true; graph: GraphTemplateSpec; growth: GraphGrowth } | { ok: false; reason: string; park?: boolean; growth?: GraphGrowth; decisionId?: string; decisionRef?: string; command?: GrowthCommand; approval?: string }> {
23
72
  let proposal: GrowthProposal | undefined;
24
73
  try { proposal = await proposer(input); }
25
74
  catch (error) { return { ok: false, reason: `proposer failed: ${String(error)}` }; }
26
75
  if (!proposal) return { ok: false, reason: 'no proposal' };
27
76
  if (!proposal.node || typeof proposal.reason !== 'string' || !proposal.reason.trim()) return { ok: false, reason: 'proposal requires a node and reason' };
28
- const result = createGraphVariant({ template: input.graph, goal: `g_${input.runId.replace(/[^a-z0-9_-]/gi, '_')}`,
29
- plan: { growth: { node: proposal.node, from: input.nodeId, outcome: input.outcome, returnTo: proposal.returnTo } } });
77
+ let result: ReturnType<typeof createGraphVariant>;
78
+ try {
79
+ result = createGraphVariant({ template: input.graph, goal: `g_${input.runId.replace(/[^a-z0-9_-]/gi, '_')}`,
80
+ plan: { growth: { node: proposal.node, from: input.nodeId, outcome: input.outcome, returnTo: proposal.returnTo } } });
81
+ } catch (error) { return { ok: false, reason: `growth rejected: ${String(error)}` }; }
30
82
  if (!result.ok) return { ok: false, reason: `growth rejected: ${JSON.stringify(result.rejections)}` };
31
83
  // Neither a recipe id nor its declared tools certify arbitrary executable code.
32
84
  // Only a trusted classification of the resolved recipe may authorize execution.
33
85
  let effect: GrowthRecipeEffect = 'unknown';
34
86
  if (proposal.node.recipe !== 'none') {
35
- try { effect = classifyRecipe?.(proposal.node) ?? 'unknown'; }
36
- catch { effect = 'unknown'; }
37
- if (effect !== 'read-only') {
38
- return { ok: false, park: true, reason: '사람 확인 필요: external or unclassified side-effect recipe' };
87
+ let command: GrowthCommand | undefined;
88
+ let approval: string | undefined;
89
+ try {
90
+ const classification = classifyRecipe?.(proposal.node) ?? 'unknown';
91
+ effect = typeof classification === 'string' ? classification : classification.effect;
92
+ command = typeof classification === 'string' ? undefined : classification.command;
93
+ approval = typeof classification === 'string' ? undefined : classification.approval;
94
+ } catch { effect = 'unknown'; }
95
+ if (effect !== 'read-only' || approval) {
96
+ const decision = growthDecision(input, proposal, decisionDeps, command, approval);
97
+ return { ok: false, park: true, reason: '사람 확인 필요: external or unclassified side-effect recipe',
98
+ decisionId: decision.id, decisionRef: growthDecisionRef(input, proposal, command, approval), command, approval,
99
+ growth: proposalGrowth(input, proposal) };
39
100
  }
40
101
  }
41
- const edges: GraphEdgeSpec[] = [
42
- { from: input.nodeId, on: 'outcome', map: { [input.outcome]: proposal.node.nodeId } },
43
- { from: proposal.node.nodeId, to: proposal.returnTo as string },
44
- ];
45
- return { ok: true, graph: result.template, growth: { node: proposal.node, edges, reason: proposal.reason,
46
- undo: { removeNode: proposal.node.nodeId, removeOutcome: { from: input.nodeId, outcome: input.outcome }, removeEdge: edges[1]! } } };
102
+ return { ok: true, graph: result.template, growth: proposalGrowth(input, proposal) };
47
103
  }
@@ -3,6 +3,7 @@ import { join } from 'node:path';
3
3
  import { parse as parseYaml } from 'yaml';
4
4
  import { debug } from '../debug/log.js';
5
5
  import { effectiveInstanceRoot } from '../instance/resolve.js';
6
+ import { DecisionLedger } from '../decisions/decision-ledger.js';
6
7
  import { latestGraphRun, runGraph, type GraphRunOptions, type GraphRunState } from './runner.js';
7
8
 
8
9
  export interface GraphTickOptions {
@@ -35,10 +36,16 @@ export async function graphTick(path: string, options: GraphTickOptions = {}): P
35
36
  acquired = true;
36
37
  await options.deps?.notify?.();
37
38
  const latest = latestGraphRun(graphId, root);
39
+ const growthCard = latest?.growthPark
40
+ ? (options.deps?.growthDecision?.list ?? ((filters: { status: 'all' }) => new DecisionLedger({ stateDir: root }).list(filters)))({ status: 'all' })
41
+ .find(entry => entry.id === latest.growthPark?.decisionId && entry.refs?.includes(latest.growthPark.decisionRef))
42
+ : undefined;
43
+ const growthDecided = growthCard?.status === 'decided' && growthCard.decidedBy?.kind === 'human' &&
44
+ (growthCard.choice === 'a' || growthCard.choice === 'b');
38
45
  let result: GraphTickResult;
39
- if (latest?.status === 'awaiting-approval' && !latest.pending?.decision) {
46
+ if (latest?.status === 'awaiting-approval' && !latest.pending?.decision && !growthDecided) {
40
47
  result = { action: 'waiting', runId: latest.runId, status: latest.status };
41
- } else if (latest?.status === 'running' || (latest?.status === 'awaiting-approval' && latest.pending?.decision)) {
48
+ } else if (latest?.status === 'running' || (latest?.status === 'awaiting-approval' && (latest.pending?.decision || growthDecided))) {
42
49
  const state = await runGraph(path, { resumeRunId: latest.runId, deps: { ...options.deps, root } });
43
50
  result = { action: 'resumed', runId: state.runId, status: state.status };
44
51
  } else if (options.startIfIdle) {
@@ -11,7 +11,8 @@ import { listInstalledPlugins } from '../plugins/install/plugin-install.js';
11
11
  import { credentialStatus, pluginEnv } from '../plugins/install/plugin-credentials.js';
12
12
  import { effectiveInstanceRoot } from '../instance/resolve.js';
13
13
  import { loadNodeCatalog } from '../self-implement/graph-catalog.js';
14
- import { proposeGrowth, type GraphGrowth, type GrowthProposer, type GrowthRecipeEffect } from './graph-grow.js';
14
+ import { growthApprovalRef, proposeGrowth, type GraphGrowth, type GrowthProposer, type GrowthRecipeEffect, type GrowthDecisionDeps, type GrowthCommand } from './graph-grow.js';
15
+ import { DecisionLedger, type DecisionEntry } from '../decisions/decision-ledger.js';
15
16
  import { createLLMGrowthProposer } from './graph-grow-llm.js';
16
17
  import { parseGraphTemplateYaml, type GraphEdgeSpec, type GraphNodeSpec, type GraphTemplateSpec } from '../self-implement/graph-yaml.js';
17
18
  import { createGraphVariant, type GraphVariantPlan } from '../self-implement/graph-variant.js';
@@ -43,7 +44,7 @@ export interface GraphRunState {
43
44
  variant?: { overlayId: string; goal: string; plan: GraphVariantPlan };
44
45
  growth?: GraphGrowth[];
45
46
  growthRejections?: Array<{ from: string; outcome: string; reason: string }>;
46
- growthPark?: { from: string; outcome: string; reason: string };
47
+ growthPark?: { from: string; outcome: string; reason: string; decisionId: string; decisionRef: string; growth: GraphGrowth; command?: GrowthCommand; approval?: string };
47
48
  resume?: { from: string; at: string; previousStatus: GraphRunState['status']; graph?: 'snapshot' | 'current' };
48
49
  executed: number;
49
50
  dryRun: boolean;
@@ -59,7 +60,7 @@ export interface GraphRunOptions {
59
60
  resumeGraphId?: string;
60
61
  fromNodeId?: string;
61
62
  useCurrentGraph?: boolean;
62
- 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 };
63
+ 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[] } };
63
64
  }
64
65
 
65
66
  function safeSegment(value: string): string {
@@ -69,14 +70,17 @@ function safeSegment(value: string): string {
69
70
  return value;
70
71
  }
71
72
 
72
- type CommandRecipe = { command: string; timeout_ms?: number };
73
- type Recipe = CommandRecipe | { approval: string };
73
+ type CommandRecipe = { command: string; dry_run_command?: string; timeout_ms?: number };
74
+ type Recipe = CommandRecipe | ({ approval: string } & Partial<CommandRecipe>);
74
75
 
75
76
  /** 접두 없는 recipe 가 카탈로그 역할이고, 그래프 옆 recipes.yaml 에 같은 키의 `{ command }` 가 있을 때만 cmd 처럼 실행한다. */
76
77
  function roleCommand(recipe: string, recipes: Record<string, Recipe>, catalogRoles: ReadonlySet<string>): CommandRecipe | undefined {
77
78
  if (!recipe || recipe === 'none' || recipe.includes(':') || !catalogRoles.has(recipe)) return undefined;
78
79
  const entry = recipes[recipe];
79
- return entry && 'command' in entry ? entry : undefined;
80
+ // An entry that carries an approval only runs through `approval:` — never as a bare command.
81
+ return entry && !('approval' in entry) && typeof entry.command === 'string' ? { command: entry.command,
82
+ ...(entry.dry_run_command ? { dry_run_command: entry.dry_run_command } : {}),
83
+ ...(entry.timeout_ms ? { timeout_ms: entry.timeout_ms } : {}) } : undefined;
80
84
  }
81
85
 
82
86
  function resolveNodeRecipe(node: GraphNodeSpec, recipes: Record<string, Recipe>, catalogRoles: ReadonlySet<string>): { command?: CommandRecipe; approval?: { approval: string } } {
@@ -84,12 +88,17 @@ function resolveNodeRecipe(node: GraphNodeSpec, recipes: Record<string, Recipe>,
84
88
  if (node.recipe.startsWith('cmd:')) {
85
89
  const id = node.recipe.slice(4);
86
90
  const entry = recipes[id];
87
- return entry && 'command' in entry ? { command: entry } : {};
91
+ return entry && !('approval' in entry) && typeof entry.command === 'string' ? { command: { command: entry.command,
92
+ ...(entry.dry_run_command ? { dry_run_command: entry.dry_run_command } : {}),
93
+ ...(entry.timeout_ms ? { timeout_ms: entry.timeout_ms } : {}) } } : {};
88
94
  }
89
95
  if (node.recipe.startsWith('approval:')) {
90
96
  const id = node.recipe.slice(9);
91
97
  const entry = recipes[id];
92
- return entry && 'approval' in entry ? { approval: entry } : {};
98
+ return entry && 'approval' in entry ? { approval: entry,
99
+ ...('command' in entry && typeof entry.command === 'string' ? { command: { command: entry.command,
100
+ ...(entry.dry_run_command ? { dry_run_command: entry.dry_run_command } : {}),
101
+ ...(entry.timeout_ms ? { timeout_ms: entry.timeout_ms } : {}) } } : {}) } : {};
93
102
  }
94
103
  const command = roleCommand(node.recipe, recipes, catalogRoles);
95
104
  return command ? { command } : {};
@@ -113,14 +122,25 @@ function recipesFor(path: string, recipeSource: string): Record<string, Recipe>
113
122
  if (!raw || typeof raw !== 'object' || Array.isArray(raw)) throw new Error(`invalid recipe: ${id}`);
114
123
  const item = raw as Record<string, unknown>;
115
124
  if (Object.hasOwn(item, 'approval')) {
116
- if (typeof item.approval !== 'string' || !item.approval.trim() || Object.keys(item).length !== 1) throw new Error(`invalid recipe: ${id}`);
117
- recipes[id] = { approval: item.approval };
125
+ if (typeof item.approval !== 'string' || !item.approval.trim() ||
126
+ (item.command !== undefined && (typeof item.command !== 'string' || !item.command.trim())) ||
127
+ (item.timeout_ms !== undefined && (!Number.isSafeInteger(item.timeout_ms) || (item.timeout_ms as number) <= 0)) ||
128
+ (item.dry_run_command !== undefined && (typeof item.dry_run_command !== 'string' || !item.dry_run_command.trim())) ||
129
+ (item.command === undefined && (item.timeout_ms !== undefined || item.dry_run_command !== undefined)) ||
130
+ Object.keys(item).some(key => !['approval', 'command', 'dry_run_command', 'timeout_ms'].includes(key))) throw new Error(`invalid recipe: ${id}`);
131
+ recipes[id] = { approval: item.approval,
132
+ ...(item.command === undefined ? {} : { command: item.command as string }),
133
+ ...(item.dry_run_command === undefined ? {} : { dry_run_command: item.dry_run_command as string }),
134
+ ...(item.timeout_ms === undefined ? {} : { timeout_ms: item.timeout_ms as number }) };
118
135
  } else {
119
136
  if (typeof item.command !== 'string' || !item.command.trim() ||
120
- (item.timeout_ms !== undefined && (!Number.isSafeInteger(item.timeout_ms) || (item.timeout_ms as number) <= 0))) {
137
+ (item.timeout_ms !== undefined && (!Number.isSafeInteger(item.timeout_ms) || (item.timeout_ms as number) <= 0)) ||
138
+ (item.dry_run_command !== undefined && (typeof item.dry_run_command !== 'string' || !item.dry_run_command.trim()))) {
121
139
  throw new Error(`invalid recipe: ${id}`);
122
140
  }
123
- recipes[id] = { command: item.command, ...(item.timeout_ms === undefined ? {} : { timeout_ms: item.timeout_ms as number }) };
141
+ recipes[id] = { command: item.command,
142
+ ...(item.dry_run_command === undefined ? {} : { dry_run_command: item.dry_run_command as string }),
143
+ ...(item.timeout_ms === undefined ? {} : { timeout_ms: item.timeout_ms as number }) };
124
144
  }
125
145
  }
126
146
  return recipes;
@@ -490,8 +510,51 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
490
510
  // Persist the restart boundary before executing it, so previous outputs remain durable.
491
511
  persistOwnedRun(state, saved!, true);
492
512
  }
513
+ let resumedGrowth = false;
493
514
  if (options.resumeRunId && state.growthPark) {
494
- throw new Error(`growth is parked for human confirmation: ${graphId}/${runId}`);
515
+ if (state.graphId !== graphId || state.runId !== runId || (graphMode === 'current' && state.sourceHash !== approvalSourceHash)) {
516
+ throw new Error(`growth source changed since run was paused: ${graphId}/${runId}`);
517
+ }
518
+ const parked = state.growthPark;
519
+ const decision = (options.deps?.growthDecision?.list?.({ status: 'all' }) ??
520
+ new DecisionLedger({ stateDir: root }).list({ status: 'all' }))
521
+ .find(entry => entry.id === parked.decisionId && entry.refs?.includes(parked.decisionRef));
522
+ if (!decision || decision.status !== 'decided' || decision.decidedBy?.kind !== 'human' ||
523
+ (decision.choice !== 'a' && decision.choice !== 'b')) {
524
+ throw new Error(`growth is parked for human confirmation: ${graphId}/${runId}`);
525
+ }
526
+ if (typeof parked.decisionRef !== 'string' ||
527
+ growthApprovalRef({ graphId, runId, from: parked.from, outcome: parked.outcome },
528
+ parked.growth, parked.command, parked.approval) !== parked.decisionRef) {
529
+ throw new Error(`growth approval target changed since card was raised: ${graphId}/${runId}`);
530
+ }
531
+ if (state.status !== 'awaiting-approval' || state.dryRun || state.path.at(-1) !== parked.from ||
532
+ state.path.length !== state.nodes.length || state.nodes.at(-1)?.nodeId !== parked.from ||
533
+ state.pending?.nodeId !== parked.from || state.growthRejections?.at(-1)?.from !== parked.from ||
534
+ state.growthRejections.at(-1)?.outcome !== parked.outcome ||
535
+ parked.growth.undo.removeOutcome.from !== parked.from || parked.growth.undo.removeOutcome.outcome !== parked.outcome) {
536
+ throw new Error(`invalid parked growth: ${graphId}/${runId}`);
537
+ }
538
+ if (decision.choice === 'a') {
539
+ const currentCommand = resolveNodeRecipe(parked.growth.node, recipes, catalogRoles).command;
540
+ const currentApproval = resolveNodeRecipe(parked.growth.node, recipes, catalogRoles).approval?.approval;
541
+ // The approved target is the (approval, command) pair as raised — a command appearing, vanishing or changing all fail.
542
+ if (parked.growth.node.recipe !== 'none' &&
543
+ (currentApproval !== parked.approval ||
544
+ JSON.stringify(currentCommand ?? null) !== JSON.stringify(parked.command ?? null) ||
545
+ (parked.approval === undefined && !parked.command))) {
546
+ throw new Error(`growth command changed since approval: ${graphId}/${runId}`);
547
+ }
548
+ graph = withGrowth(graph, [parked.growth]);
549
+ state.growth ??= [];
550
+ state.growth.push(parked.growth);
551
+ state.growthRejections!.pop();
552
+ }
553
+ resumedGrowth = true;
554
+ delete state.pending;
555
+ delete state.growthPark;
556
+ state.status = 'running';
557
+ persistOwnedRun(state, saved!);
495
558
  }
496
559
  if (options.resumeRunId && state.status === 'running' && state.pending) {
497
560
  throw new Error(`run is not awaiting approval: ${graphId}/${runId}`);
@@ -590,7 +653,7 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
590
653
  let exit: number | null = null;
591
654
  let error: string | undefined;
592
655
  let output: unknown;
593
- if (command && !state.dryRun && !resumingCompleted) {
656
+ if (command && (!approval || state.dryRun || ok) && (!state.dryRun || command.dry_run_command) && !resumingCompleted) {
594
657
  const outputs: Record<string, unknown> = Object.create(null);
595
658
  // 구조 산출(마지막 JSON 줄)이 있으면 그것을, 없으면 원문을 준다 — 다음 노드가 판단을 «값»으로 받는다.
596
659
  for (const previous of state.nodes) outputs[previous.nodeId] = lastJsonObject(previous.output) ?? previous.output ?? null;
@@ -635,9 +698,10 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
635
698
  debug.log('graph-runner', 'child-universe-pinned', { graphId, runId, node: current, configDir: env.ELANOUS_CONFIG_DIR, stateDir: env.ELANOUS_STATE_DIR });
636
699
  }
637
700
  env.ELANOUS_GRAPH_CONTEXT = contextPath;
701
+ env.ELANOUS_GRAPH_DRY_RUN = state.dryRun ? '1' : '0';
638
702
  env.ELANOUS_GRAPH_DIR = dirname(resolve(path));
639
703
  const ctx = { arguments: '', artifactsDir: dirname(statePath), outputs: {}, resolvedProvider: undefined, resolvedModel: undefined, toolPolicy: {}, env } as NodeExecContext;
640
- const result = await executeBashNode({ id: current, type: 'bash', bash: command.command, ...(command.timeout_ms ? { idle_timeout: command.timeout_ms } : {}) } as BashNode, ctx, { runBash } as WorkflowDeps);
704
+ const result = await executeBashNode({ id: current, type: 'bash', bash: state.dryRun ? command.dry_run_command! : command.command, ...(command.timeout_ms ? { idle_timeout: command.timeout_ms } : {}) } as BashNode, ctx, { runBash } as WorkflowDeps);
641
705
  output = result.output;
642
706
  ok = result.ok;
643
707
  exit = code;
@@ -654,14 +718,14 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
654
718
  if (approval && !state.dryRun && !command) {
655
719
  output = JSON.stringify({ outcome: ok ? 'approved' : 'rejected', ...(state.pending?.decidedBy === undefined ? {} : { decidedBy: state.pending.decidedBy }), decidedAt: state.pending?.decidedAt ?? null });
656
720
  }
657
- state.nodes.push({ nodeId: current, ok, exit, executed: !!command && !state.dryRun, ...((command || approval) && !state.dryRun && output !== undefined ? { output } : {}), ...(error ? { error } : {}),
721
+ 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 } : {}),
658
722
  ...(approval && state.pending ? { ...(state.pending.decidedBy === undefined ? {} : { decidedBy: state.pending.decidedBy }), decidedAt: state.pending.decidedAt } : {}) });
659
723
  }
660
724
  if (approval) {
661
725
  delete state.pending;
662
726
  delete state.approvalSourceHash;
663
727
  }
664
- const reported = command && !state.dryRun ? lastJsonObject(resumingCompleted ? completed?.output : output)?.outcome : undefined;
728
+ const reported = command && (!state.dryRun || command.dry_run_command) ? lastJsonObject(resumingCompleted ? completed?.output : output)?.outcome : undefined;
665
729
  const namedOutcome = typeof reported === 'string' ? reported : undefined;
666
730
  const seconds = Number(((performance.now() - startedAt) / 1000).toFixed(2));
667
731
  console.error(`[graph] ${current} ${ok ? 'ok' : 'fail'} (${seconds.toFixed(2)}s)`);
@@ -677,7 +741,7 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
677
741
  }
678
742
  const fromNodeId = current;
679
743
  const edge = graph.edges.find((candidate) => candidate.from === current);
680
- if (document.grow === 'on' && !state.dryRun && namedOutcome !== undefined &&
744
+ if (!resumedGrowth && document.grow === 'on' && !state.dryRun && namedOutcome !== undefined &&
681
745
  edge?.on === 'outcome' && edge.map && !Object.hasOwn(edge.map, namedOutcome) &&
682
746
  !state.growth?.some((item) => item.undo.removeOutcome.from === current && item.undo.removeOutcome.outcome === namedOutcome) &&
683
747
  !state.growthRejections?.some((item) => item.from === current && item.outcome === namedOutcome)) {
@@ -685,9 +749,10 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
685
749
  : await proposeGrowth({ graph, nodeId: current, outcome: namedOutcome, runId, output: state.nodes.at(-1)?.output },
686
750
  options.deps?.growthProposer ?? createLLMGrowthProposer(options.deps?.growthLLM), (candidate) => {
687
751
  const resolved = resolveNodeRecipe(candidate, recipes, catalogRoles);
688
- if (resolved.approval || (!resolved.command && candidate.recipe !== 'none')) return 'unknown';
689
- return options.deps?.classifyGrowthRecipe?.(candidate, { command: resolved.command?.command }) ?? 'unknown';
690
- });
752
+ if (!resolved.command && !resolved.approval && candidate.recipe !== 'none') return 'unknown';
753
+ return { effect: resolved.approval ? 'unknown' : options.deps?.classifyGrowthRecipe?.(candidate, { command: resolved.command?.command }) ?? 'unknown',
754
+ command: resolved.command, approval: resolved.approval?.approval };
755
+ }, { ...options.deps?.growthDecision, root });
691
756
  if (result.ok) {
692
757
  graph = result.graph;
693
758
  state.growth ??= [];
@@ -699,17 +764,20 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
699
764
  } else {
700
765
  state.growthRejections ??= [];
701
766
  state.growthRejections.push({ from: current, outcome: namedOutcome, reason: result.reason });
702
- if (result.park) {
767
+ if (result.park && result.growth && result.decisionId && result.decisionRef) {
703
768
  state.status = 'awaiting-approval';
704
769
  state.pending = { nodeId: current, message: result.reason, since: new Date().toISOString() };
705
- state.growthPark = { from: current, outcome: namedOutcome, reason: result.reason };
770
+ state.growthPark = { from: current, outcome: namedOutcome, reason: result.reason,
771
+ growth: result.growth, decisionId: result.decisionId, decisionRef: result.decisionRef,
772
+ ...(result.command ? { command: result.command } : {}), ...(result.approval ? { approval: result.approval } : {}) };
706
773
  persist();
707
774
  break;
708
775
  }
709
776
  }
710
777
  persist();
711
778
  }
712
- current = nextNode(graph.edges, current, state.dryRun ? 'ok' : ok ? 'ok' : 'fail', state.dryRun ? undefined : namedOutcome);
779
+ current = nextNode(graph.edges, current, state.dryRun && !command?.dry_run_command ? 'ok' : ok ? 'ok' : 'fail', namedOutcome);
780
+ resumedGrowth = false;
713
781
  if (current !== undefined) publishInsideEvent({ kind: 'edge', graphId, runId, from: fromNodeId, to: current });
714
782
  if (current === undefined) {
715
783
  state.status = 'failed';