elanous 0.2.9 → 0.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (361) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/2DsiDfDCks44g1cPUMhiW/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/24-e6d28380efab7faf.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/3615-740dd270764ffa24.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/3734-1015fe991eb9b7e7.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/5012-a7fe6f3c8b6fe45a.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/5783-ef40ebfc91d55230.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/62-ff227c3edb8b248b.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/7633-1b3709b0854c9412.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/7951-8b03dbb10b24cd06.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/8067-87ce253ec2f23697.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 8195-6fde3d7a39859206.js} +5 -5
  14. package/apps/pwa/out/_next/static/chunks/8894-b5fb36bab837cd29.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/9454-84d0d5d77446bd92.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/988-5f92e7ab7ccea3dd.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/9989-5582582f06429840.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/app/approvals/page-d2253d7e58ef1890.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-5441d4a9ab35e518.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/app/board/page-d9b12d680922d980.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-f972fc1c00a62e88.js → page-54b32bd27726588d.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/app/bots/page-e268d6d145827a22.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/app/chat/page-f8116559c7424da7.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/app/consult/page-1085399fd0f32112.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/control/page-1df686a14f65895a.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/app/design-check/page-54e1f966ab60a4dc.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/app/editor/{page-973996d584f008c1.js → page-7ed95f296f5ee8e9.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/exec/page-11edac668d77e5c3.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/field/page-59e2b9a22cd5a6d5.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/app/intake/page-2976df21ad74ab48.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/app/layout-34d211a67f16d510.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/live/page-8ff652d5d950639b.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/app/market/{page-8b0402a46a749a3d.js → page-b31610deda76337b.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-037f83e2a1edc8e2.js → page-6cb0e1bbefe5fb3d.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/morning/page-a286a152cf584576.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/not-found-d9237581e7003bcb.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/app/observatory/page-83274e761643fdd2.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-ceee6cfb1d4e6d68.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-9c9f8bb3e4a64091.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c17217603ea3426a.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/app/page-efb6177cbf1e8a51.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/app/reflection/page-d307523703bc4df3.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-462f4e4bab45ce7d.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/sessions/page-8990e96f33af3837.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-0df30e0c7e50148a.js → page-e168fa1030af0548.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/app/settings/page-1233e05a4068bc9c.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-13a1840b1fac337a.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/setup/page-64a29fc80b11ab55.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/share/page-68e787339a16b3dc.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/showroom/page-4fddc90b23feb215.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/tasks/page-886ba8260dc79c79.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/term/page-0f26507a1722e40b.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/today/page-109d4ccd42259c9d.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/trace/page-f5c2a4e510726774.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/vault/page-b2e0039416f42522.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-90670589b87abea8.js → page-26102fd7350f171e.js} +1 -1
  57. package/apps/pwa/out/_next/static/chunks/app/workflows/page-708948bc8a802794.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/workspace/page-5e5611620f78fb0b.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-35bedde6d885f08f.js → page-139ba5c367bbf60a.js} +1 -1
  60. package/apps/pwa/out/_next/static/chunks/main-app-202b4e81f33895ea.js +1 -0
  61. package/apps/pwa/out/_next/static/css/43feca5fd387dcbd.css +1 -0
  62. package/apps/pwa/out/approvals/index.html +1 -1
  63. package/apps/pwa/out/approvals/index.txt +14 -14
  64. package/apps/pwa/out/autopilot/index.html +1 -1
  65. package/apps/pwa/out/autopilot/index.txt +14 -14
  66. package/apps/pwa/out/board/index.html +1 -1
  67. package/apps/pwa/out/board/index.txt +14 -14
  68. package/apps/pwa/out/botlab/index.html +1 -1
  69. package/apps/pwa/out/botlab/index.txt +14 -14
  70. package/apps/pwa/out/bots/index.html +1 -1
  71. package/apps/pwa/out/bots/index.txt +14 -14
  72. package/apps/pwa/out/chat/index.html +1 -1
  73. package/apps/pwa/out/chat/index.txt +14 -14
  74. package/apps/pwa/out/consult/index.html +1 -1
  75. package/apps/pwa/out/consult/index.txt +14 -14
  76. package/apps/pwa/out/control/index.html +1 -1
  77. package/apps/pwa/out/control/index.txt +14 -14
  78. package/apps/pwa/out/design-check/index.html +1 -1
  79. package/apps/pwa/out/design-check/index.txt +14 -14
  80. package/apps/pwa/out/editor/index.html +1 -1
  81. package/apps/pwa/out/editor/index.txt +14 -14
  82. package/apps/pwa/out/exec/index.html +1 -1
  83. package/apps/pwa/out/exec/index.txt +14 -14
  84. package/apps/pwa/out/field/index.html +1 -1
  85. package/apps/pwa/out/field/index.txt +14 -14
  86. package/apps/pwa/out/healthz +1 -1
  87. package/apps/pwa/out/index.html +1 -1
  88. package/apps/pwa/out/index.txt +14 -14
  89. package/apps/pwa/out/intake/index.html +1 -1
  90. package/apps/pwa/out/intake/index.txt +14 -14
  91. package/apps/pwa/out/live/index.html +1 -1
  92. package/apps/pwa/out/live/index.txt +14 -14
  93. package/apps/pwa/out/market/index.html +1 -1
  94. package/apps/pwa/out/market/index.txt +14 -14
  95. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  96. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  97. package/apps/pwa/out/morning/index.html +1 -1
  98. package/apps/pwa/out/morning/index.txt +14 -14
  99. package/apps/pwa/out/observatory/index.html +1 -1
  100. package/apps/pwa/out/observatory/index.txt +14 -14
  101. package/apps/pwa/out/ops/checklist/index.html +1 -1
  102. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  103. package/apps/pwa/out/ops/release/index.html +1 -1
  104. package/apps/pwa/out/ops/release/index.txt +14 -14
  105. package/apps/pwa/out/ops/seats/index.html +1 -0
  106. package/apps/pwa/out/ops/seats/index.txt +30 -0
  107. package/apps/pwa/out/reflection/index.html +1 -1
  108. package/apps/pwa/out/reflection/index.txt +15 -15
  109. package/apps/pwa/out/scheduler/index.html +1 -1
  110. package/apps/pwa/out/scheduler/index.txt +14 -14
  111. package/apps/pwa/out/sessions/index.html +1 -1
  112. package/apps/pwa/out/sessions/index.txt +14 -14
  113. package/apps/pwa/out/settings/devices/index.html +1 -1
  114. package/apps/pwa/out/settings/devices/index.txt +14 -14
  115. package/apps/pwa/out/settings/index.html +1 -1
  116. package/apps/pwa/out/settings/index.txt +14 -14
  117. package/apps/pwa/out/setup/done/index.html +1 -1
  118. package/apps/pwa/out/setup/done/index.txt +14 -14
  119. package/apps/pwa/out/setup/index.html +1 -1
  120. package/apps/pwa/out/setup/index.txt +14 -14
  121. package/apps/pwa/out/share/index.html +1 -1
  122. package/apps/pwa/out/share/index.txt +14 -14
  123. package/apps/pwa/out/showroom/index.html +1 -1
  124. package/apps/pwa/out/showroom/index.txt +14 -14
  125. package/apps/pwa/out/tasks/index.html +1 -1
  126. package/apps/pwa/out/tasks/index.txt +14 -14
  127. package/apps/pwa/out/term/index.html +1 -1
  128. package/apps/pwa/out/term/index.txt +14 -14
  129. package/apps/pwa/out/today/index.html +1 -0
  130. package/apps/pwa/out/today/index.txt +33 -0
  131. package/apps/pwa/out/trace/index.html +1 -1
  132. package/apps/pwa/out/trace/index.txt +14 -14
  133. package/apps/pwa/out/vault/index.html +1 -1
  134. package/apps/pwa/out/vault/index.txt +14 -14
  135. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  136. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  137. package/apps/pwa/out/workflows/index.html +1 -1
  138. package/apps/pwa/out/workflows/index.txt +14 -14
  139. package/apps/pwa/out/workspace/index.html +1 -1
  140. package/apps/pwa/out/workspace/index.txt +14 -14
  141. package/apps/pwa/out/worktrees/index.html +1 -1
  142. package/apps/pwa/out/worktrees/index.txt +14 -14
  143. package/graphs/field/field-feed.yaml +2 -0
  144. package/graphs/implement-loop.yaml +1 -1
  145. package/package.json +1 -1
  146. package/plugins/card-followup/graphs/run-step.ts +48 -13
  147. package/plugins/doc-draft/graphs/doc-draft.yaml +3 -3
  148. package/plugins/doc-draft/graphs/run-step.ts +13 -6
  149. package/scripts/ci-pwa-build-gate.ts +160 -0
  150. package/scripts/coord-channel-watch.sh +42 -8
  151. package/scripts/coord-digest.ts +199 -0
  152. package/scripts/coord-post.sh +60 -3
  153. package/scripts/install.ps1 +68 -9
  154. package/scripts/install.sh +39 -12
  155. package/scripts/lib/pwa-scenarios/scenarios.ts +20 -0
  156. package/scripts/lib/tui-regress-checks.ts +146 -0
  157. package/scripts/memory-lifecycle-cycle.ts +2 -2
  158. package/scripts/ops-now-probe.ts +327 -0
  159. package/scripts/publish-internal-dist.ts +11 -1
  160. package/scripts/release-loop/cutoff.ts +19 -8
  161. package/scripts/release-loop/ops-upgrade-node.ts +72 -16
  162. package/scripts/release-loop/release-readiness.ts +9 -0
  163. package/scripts/release-loop/version-node.ts +58 -10
  164. package/scripts/repo-watch-cycle.ts +1 -1
  165. package/scripts/retro-cycle.ts +2 -2
  166. package/scripts/schedule-health-report.ts +1 -1
  167. package/scripts/storyboard/storyboard.ts +5 -161
  168. package/scripts/test-diet/lib.ts +30 -1
  169. package/scripts/test-diet/node.ts +82 -7
  170. package/scripts/test-timeout-audit.ts +114 -0
  171. package/scripts/tree-sync-alert.ts +1 -1
  172. package/scripts/tui-regress.ts +71 -0
  173. package/scripts/waitlist-daily-digest.ts +1 -1
  174. package/skills/explainer-video/SKILL.md +4 -0
  175. package/skills/explainer-video/engine/reel.mjs +18 -13
  176. package/skills/explainer-video/engine/reel.sh +24 -5
  177. package/skills/explainer-video/engine/stitch.mjs +145 -0
  178. package/skills/pitch-deck/SKILL.md +36 -0
  179. package/skills/pitch-deck/template.html +80 -0
  180. package/src/acp/codex-approval-adapter.ts +9 -3
  181. package/src/acp/core-turn-bridge.ts +117 -18
  182. package/src/acp/fact-question.ts +11 -0
  183. package/src/acp/server.ts +48 -7
  184. package/src/acp/tool-approval.ts +126 -0
  185. package/src/acp/turn-runner.ts +36 -0
  186. package/src/agent/monad-agent-turn.ts +8 -0
  187. package/src/ask-user-question/pending-questions.ts +25 -10
  188. package/src/ask-user-question/types.ts +9 -0
  189. package/src/auth/setup-link-tokens.ts +16 -5
  190. package/src/autopilot/build/integrity-gate.ts +12 -0
  191. package/src/boot/daemon-prompt-turn.ts +3 -0
  192. package/src/boot/daemon-tools/index.ts +8 -0
  193. package/src/boot/daemon-tools/types.ts +2 -0
  194. package/src/boot/daemon-tools/web-search.ts +1 -0
  195. package/src/chat/ctrl-c-in-input.ts +14 -0
  196. package/src/chat/index.ts +52 -0
  197. package/src/chat/pickers/state.ts +6 -2
  198. package/src/cli/agent-cli.ts +7 -0
  199. package/src/cli/decisions-cli.ts +25 -3
  200. package/src/cli/doctor-bundle-upload.ts +68 -0
  201. package/src/cli/doctor-cli.ts +38 -4
  202. package/src/cli/ops-cli.ts +20 -0
  203. package/src/cli/pod-cli.ts +49 -0
  204. package/src/cli/pr-cli.ts +31 -2
  205. package/src/cli/pwa-stop.ts +163 -13
  206. package/src/cli/pwa-test.ts +3 -0
  207. package/src/cli/release-cli.ts +63 -20
  208. package/src/cli/self-update.ts +14 -0
  209. package/src/cli/storyboard-cli.ts +37 -0
  210. package/src/cli/test-state-dir-flag.ts +11 -4
  211. package/src/connectors/linear.ts +146 -0
  212. package/src/context-bus/coord-events.ts +115 -0
  213. package/src/dashboard/dashboard-boot-fixture.ts +23 -0
  214. package/src/dashboard/index.ts +58 -21
  215. package/src/dashboard/slash-runtime/dashboard-handlers.ts +7 -0
  216. package/src/dashboard/slash-runtime/help-from-registry.ts +43 -28
  217. package/src/decisions/decision-cards.ts +49 -1
  218. package/src/decisions/decision-ledger.ts +75 -4
  219. package/src/decisions/decision-linear-projection.ts +157 -0
  220. package/src/decisions/web-push-decision-cards.ts +50 -0
  221. package/src/directive/interpretation-card.ts +36 -0
  222. package/src/discord.ts +2 -1
  223. package/src/domains/coo-admin-tool.ts +62 -0
  224. package/src/domains/core-tools.ts +11 -3
  225. package/src/domains/elanous-card.ts +29 -0
  226. package/src/domains/ops-facts-tool.ts +76 -0
  227. package/src/domains/outbound-alert.ts +41 -13
  228. package/src/domains/release-tool.ts +168 -0
  229. package/src/domains/telegram-kind-route.ts +19 -1
  230. package/src/exec-requests/planner.ts +98 -28
  231. package/src/exec-requests/runner.ts +107 -10
  232. package/src/exec-requests/store.ts +8 -2
  233. package/src/field/field-media.ts +10 -7
  234. package/src/field/field-reel.ts +14 -4
  235. package/src/git-fs/worktree.ts +45 -6
  236. package/src/graph-runner/graph-approval-card.ts +113 -0
  237. package/src/graph-runner/graph-cli.ts +129 -4
  238. package/src/graph-runner/graph-notify.ts +25 -3
  239. package/src/graph-runner/runner.ts +142 -16
  240. package/src/harness/browser-verify.ts +25 -2
  241. package/src/harness/harness-cli-command.ts +231 -35
  242. package/src/harness/harness-incidents-writer.fixture.ts +24 -0
  243. package/src/harness/harness-incidents.ts +129 -0
  244. package/src/harness/harness-stop.ts +53 -3
  245. package/src/hitl/clarification-policy.ts +3 -1
  246. package/src/index.ts +156 -37
  247. package/src/intake-plane/author-goals.ts +37 -4
  248. package/src/intake-plane/check.ts +3 -0
  249. package/src/intake-plane/discord-seat-work.ts +47 -2
  250. package/src/intake-plane/seat-doc-route.ts +170 -6
  251. package/src/intake-plane/telegram-seat-work.ts +44 -2
  252. package/src/intake-plane/telegram-work.ts +1 -0
  253. package/src/maturity/cli-maturity.ts +34 -0
  254. package/src/maturity/feature-maturity.ts +98 -1
  255. package/src/maturity/tui-slash-maturity.ts +31 -0
  256. package/src/memory.ts +32 -4
  257. package/src/nexus/api/exec-requests.ts +33 -3
  258. package/src/nexus/api/http-server.ts +53 -1
  259. package/src/nexus/api/meta-api.ts +11 -0
  260. package/src/nexus/api/ops-api.ts +10 -1
  261. package/src/nexus/api/ops-seats-sources.ts +37 -7
  262. package/src/nexus/api/ops-seats.ts +16 -6
  263. package/src/nexus/api/seat-requests.ts +88 -8
  264. package/src/nexus/api/sessions-store.ts +63 -0
  265. package/src/nexus/api/setup-codex-login.ts +160 -0
  266. package/src/nexus/api/usage-api.ts +102 -0
  267. package/src/nexus/index.ts +45 -4
  268. package/src/onboarding/first-run.ts +89 -0
  269. package/src/onboarding/lan-address.ts +45 -0
  270. package/src/onboarding/web-first.ts +119 -24
  271. package/src/onboarding.ts +4 -0
  272. package/src/ops-now/ops-now-refresh.ts +100 -0
  273. package/src/project/project-store.ts +115 -0
  274. package/src/release-loop/checklist.ts +78 -4
  275. package/src/release-loop/feature-store.ts +154 -21
  276. package/src/release-loop/release-schedule.ts +53 -0
  277. package/src/repl/index.ts +7 -0
  278. package/src/seat-dispatch/ceo-commands.ts +41 -8
  279. package/src/seat-dispatch/ceo-intent.ts +38 -0
  280. package/src/seat-loop/seat-loop.ts +231 -0
  281. package/src/seat-loop/seat-report.ts +64 -0
  282. package/src/self-dev/decompose.ts +66 -11
  283. package/src/self-dev/run-store.ts +10 -1
  284. package/src/self-implement/execution-clarification.ts +115 -0
  285. package/src/self-implement/gate-cli.ts +6 -3
  286. package/src/self-implement/orchestrator.ts +37 -3
  287. package/src/self-implement/pipeline-shape.ts +1 -1
  288. package/src/self-implement/run-status-mapping.ts +5 -3
  289. package/src/session/chat.ts +2 -0
  290. package/src/session/index.ts +9 -0
  291. package/src/shell-env-bootstrap.ts +17 -0
  292. package/src/steward/directive.ts +51 -2
  293. package/src/steward/launch.ts +250 -0
  294. package/src/steward/outcome-notify.ts +115 -0
  295. package/src/steward/steward-cards.ts +8 -2
  296. package/src/steward/triage-plan.ts +32 -4
  297. package/src/steward/triage.ts +93 -9
  298. package/src/steward/working-backwards.ts +64 -0
  299. package/src/storyboard/storyboard.ts +141 -0
  300. package/src/task-cards/card-store.ts +2 -2
  301. package/src/task-orchestrator/surfaces/pod-lease.ts +260 -0
  302. package/src/telegram-card-followup.ts +233 -0
  303. package/src/telegram-report.ts +15 -9
  304. package/src/telegram-run.ts +7 -0
  305. package/src/telegram.ts +29 -8
  306. package/src/tool-runtime/types.ts +4 -0
  307. package/src/tui-client/dashboard-session.ts +2 -0
  308. package/src/user-config.ts +118 -11
  309. package/src/version/packed-revision.json +1 -1
  310. package/src/web-push/notify-exec-request.ts +32 -0
  311. package/apps/pwa/out/_next/static/bFI5EEwg7T6aG88ibX4Zq/_buildManifest.js +0 -1
  312. package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
  313. package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
  314. package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
  315. package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +0 -1
  316. package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +0 -1
  317. package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
  318. package/apps/pwa/out/_next/static/chunks/7032-f4ef698518fba61c.js +0 -1
  319. package/apps/pwa/out/_next/static/chunks/8067-d976b441a104ab74.js +0 -1
  320. package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +0 -1
  321. package/apps/pwa/out/_next/static/chunks/8630-cdfc61e82c653ffc.js +0 -1
  322. package/apps/pwa/out/_next/static/chunks/8894-e263b332208e3957.js +0 -1
  323. package/apps/pwa/out/_next/static/chunks/9454-139040d37c79dbc9.js +0 -1
  324. package/apps/pwa/out/_next/static/chunks/9740-8f5ff96c522b5f55.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/app/approvals/page-fb7c2d458f32fcad.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-35e2713597dd24d1.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/board/page-918b656f064577ba.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/bots/page-5f30e00693527ebd.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/chat/page-7fbae173b3c103a7.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/consult/page-3b4edcce364062ea.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/control/page-c5794f484c251f44.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/design-check/page-1130d8eefcd87e76.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/exec/page-016e2bda04eecb1f.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/field/page-b4d6d9a3b56f2940.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/intake/page-ec49a466121233ba.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/app/layout-f72db356744ceade.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/app/live/page-a6fd9b50d1b85378.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/app/morning/page-adfbd2ba9598e59a.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/app/not-found-46fedff57d4bec67.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/app/observatory/page-0cf318e9dcee2bbf.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-899b327cb8b6845c.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-6c1e6ba743a29dfe.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/app/page-1a8e4807292f6b17.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/app/reflection/page-28deba6bcb834916.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-de68e2205794037d.js +0 -1
  346. package/apps/pwa/out/_next/static/chunks/app/sessions/page-c71c33a573230672.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/app/settings/page-4fc56790fe0d2682.js +0 -1
  348. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-59df2aebb38a7c1e.js +0 -1
  349. package/apps/pwa/out/_next/static/chunks/app/setup/page-4647be5dbcbd0795.js +0 -1
  350. package/apps/pwa/out/_next/static/chunks/app/share/page-e025892bda74b743.js +0 -1
  351. package/apps/pwa/out/_next/static/chunks/app/showroom/page-a0955de73b89f078.js +0 -1
  352. package/apps/pwa/out/_next/static/chunks/app/tasks/page-369846d2921af584.js +0 -1
  353. package/apps/pwa/out/_next/static/chunks/app/term/page-72cbc1c8cdd0f65d.js +0 -1
  354. package/apps/pwa/out/_next/static/chunks/app/trace/page-77b28bfb0996d5ea.js +0 -1
  355. package/apps/pwa/out/_next/static/chunks/app/vault/page-3220c4934f49c6c2.js +0 -1
  356. package/apps/pwa/out/_next/static/chunks/app/workflows/page-7df9243ed021a4cc.js +0 -1
  357. package/apps/pwa/out/_next/static/chunks/app/workspace/page-4498c3853f4f688d.js +0 -1
  358. package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +0 -1
  359. package/apps/pwa/out/_next/static/css/f92f63a5df0ba229.css +0 -1
  360. package/src/dashboard/turn-typeahead-echo.ts +0 -23
  361. /package/apps/pwa/out/_next/static/{bFI5EEwg7T6aG88ibX4Zq → 2DsiDfDCks44g1cPUMhiW}/_ssgManifest.js +0 -0
@@ -1,16 +1,20 @@
1
- import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
1
+ import { existsSync, mkdirSync, readFileSync, realpathSync, statSync, writeFileSync } from 'node:fs';
2
2
  import { basename, dirname, isAbsolute, join, resolve, sep } from 'node:path';
3
3
  import { createHash, randomUUID } from 'node:crypto';
4
4
  import { debug } from '../debug/log.js';
5
+ import { notifyExecRequestTransition } from '../web-push/notify-exec-request.js';
5
6
  import { effectiveInstanceRoot } from '../instance/resolve.js';
6
7
  import { lastJsonObject, runGraph, type GraphRunState } from '../graph-runner/runner.js';
7
8
  import { installedGraphs, planExecRequest, type ExecPlanItem, type InstalledGraph } from './planner.js';
8
- import { ExecRequestStore, type ExecRequest, type ExecResult, type ExecSeat } from './store.js';
9
+ import { ExecRequestStore, type ExecAttachment, type ExecRequest, type ExecResult, type ExecSeat } from './store.js';
10
+ import { answerAsSeat } from '../intake-plane/seat-answer.js';
11
+ import { redactSecrets } from '../task-cards/card-store.js';
9
12
 
10
13
  export interface ExecRunnerDeps {
11
14
  store?: ExecRequestStore;
12
15
  graphs?: () => Promise<InstalledGraph[]>;
13
- plan?: (text: string) => Promise<ExecPlanItem[]>;
16
+ plan?: (text: string, attachments?: readonly ExecAttachment[]) => Promise<ExecPlanItem[]>;
17
+ answer?: typeof answerAsSeat;
14
18
  run?: typeof runGraph;
15
19
  root?: string;
16
20
  }
@@ -38,7 +42,11 @@ function safeFile(path: string, root: string, graphId: string, runId: string): s
38
42
  ? realpathSync(artifactsDir) : null;
39
43
  const inRunDirectory = realArtifactsDir === join(base, `${runId}.json.artifacts`)
40
44
  && real.startsWith(`${realArtifactsDir}${sep}`);
41
- if ((!local && !inRunDirectory) || !statSync(real).isFile()) return null;
45
+ // EXEC2 — plugin graphs (doc-draft · geo-check) write into `<graphId>/<runId>/`; that directory is this run's own too.
46
+ const runDir = join(allowed, runId);
47
+ const realRunDir = existsSync(runDir) && statSync(runDir).isDirectory() ? realpathSync(runDir) : null;
48
+ const inOwnRunDir = realRunDir === join(base, runId) && real.startsWith(`${realRunDir}${sep}`);
49
+ if ((!local && !inRunDirectory && !inOwnRunDir) || !statSync(real).isFile()) return null;
42
50
  return real;
43
51
  } catch { return null; }
44
52
  }
@@ -56,8 +64,32 @@ function fileName(path: string, runId: string): string {
56
64
  return `${runId}--${digest}--${basename(path)}`;
57
65
  }
58
66
 
67
+ /** EXEC2 — `report` is how geo-check names its file; treat it like `file`/`path`. */
68
+ function fileCandidate(output: Record<string, unknown>): string | null {
69
+ if (typeof output.file === 'string') return output.file;
70
+ if (typeof output.path === 'string') return output.path;
71
+ if (typeof output.report === 'string' && /\.(md|txt|pdf)$/i.test(output.report)) return output.report;
72
+ return null;
73
+ }
74
+
75
+ const TEXT_RESULT_LIMIT = 200_000;
76
+ const TEXT_RESULT_NAME = 'result.md';
77
+
78
+ /** EXEC2 — a done seat whose graph only answered in text gets that text as a run artifact (redacted), so the result is viewable. */
79
+ function textResultFile(seat: ExecSeat, state: GraphRunState, root: string): string | null {
80
+ const outputs = [...(state.nodes ?? [])].reverse().map(node => lastJsonObject(node.output)).filter((o): o is Record<string, unknown> => !!o);
81
+ const text = outputs.map(o => o.markdown ?? o.text).find((v): v is string => typeof v === 'string' && v.trim().length > 0);
82
+ if (!text || !SEGMENT.test(seat.graphId) || !SEGMENT.test(seat.runId)) return null;
83
+ const dir = join(root, 'graph-runs', seat.graphId, `${seat.runId}.json.artifacts`);
84
+ const path = join(dir, TEXT_RESULT_NAME);
85
+ try {
86
+ if (!existsSync(path)) { mkdirSync(dir, { recursive: true }); writeFileSync(path, redactSecrets(text.slice(0, TEXT_RESULT_LIMIT)), { mode: 0o600 }); }
87
+ return path;
88
+ } catch { return null; }
89
+ }
90
+
59
91
  function resultOf(output: Record<string, unknown>, seat: ExecSeat, id: string, root: string): ExecResult | null {
60
- const candidate = typeof output.file === 'string' ? output.file : typeof output.path === 'string' ? output.path : null;
92
+ const candidate = fileCandidate(output);
61
93
  let url: string;
62
94
  let kind: ExecResult['kind'];
63
95
  let name: string;
@@ -95,6 +127,7 @@ function syncRun(item: ExecRequest, seat: ExecSeat, state: GraphRunState, root:
95
127
  if (state.status === 'awaiting-approval' && state.pending && !state.pending.decision && !existsSync(claim)) {
96
128
  if (!item.approvals.some(a => a.graphId === seat.graphId && a.runId === seat.runId)) {
97
129
  item.approvals.push({ graphId: seat.graphId, runId: seat.runId, message: state.pending.message });
130
+ notifyTransition(item, item.status, item.approvals.length);
98
131
  }
99
132
  } else item.approvals = item.approvals.filter(a => a.graphId !== seat.graphId || a.runId !== seat.runId);
100
133
  for (const node of state.nodes ?? []) {
@@ -107,9 +140,28 @@ function syncRun(item: ExecRequest, seat: ExecSeat, state: GraphRunState, root:
107
140
  if (result && !item.results.some(existing => existing.seat === result.seat && existing.url === result.url)) item.results.push(result);
108
141
  }
109
142
  }
143
+ if (seat.status === 'done' && !item.results.some(existing => existing.seat === seat.seat)) {
144
+ const file = textResultFile(seat, state, root);
145
+ const result = file ? resultOf({ file, title: seat.title }, seat, item.id, root) : null;
146
+ if (result) item.results.push(result);
147
+ else {
148
+ const keys = [...new Set((state.nodes ?? []).flatMap(node => Object.keys(lastJsonObject(node.output) ?? {})))];
149
+ debug.log('exec.result', 'none', { seat: seat.seat, graphId: seat.graphId, keys });
150
+ }
151
+ }
110
152
  const summary = [...(state.nodes ?? [])].reverse().map(node => lastJsonObject(node.output)?.summary).find(value => typeof value === 'string');
111
153
  if (typeof summary === 'string') item.summary = summary;
112
- if (seat.status === 'failed') seat.reason = state.nodes?.at(-1)?.error ?? `그래프 런 ${state.status}`;
154
+ if (seat.status === 'failed') seat.reason = failureReason(state);
155
+ }
156
+
157
+ /** EXEC2 — the failing node's own error (e.g. doc-draft `check` {outcome:'fail', error}) beats the terminal gate's empty one. */
158
+ function failureReason(state: GraphRunState): string {
159
+ for (const node of [...(state.nodes ?? [])].reverse()) {
160
+ if (typeof node.error === 'string' && node.error) return node.error;
161
+ const output = lastJsonObject(node.output);
162
+ if (output && typeof output.error === 'string' && output.error) return redactSecrets(output.error);
163
+ }
164
+ return `그래프 런 ${state.status}`;
113
165
  }
114
166
 
115
167
  const PRIOR_TEXT_LIMIT = 4_000;
@@ -129,8 +181,14 @@ export function withPriorResults(inputs: Record<string, unknown> | undefined, pr
129
181
  return { ...base, context: `${previous}앞 자리 결과:\n${parts.join('\n\n')}` };
130
182
  }
131
183
 
184
+ function notifyTransition(item: ExecRequest, from: ExecRequest['status'], pendingApprovals: number): void {
185
+ void notifyExecRequestTransition({ id: item.id, text: item.text, from, to: item.status,
186
+ summary: item.summary, pendingApprovals }).catch(() => { /* Push never interrupts execution. */ });
187
+ }
188
+
132
189
  function aggregate(item: ExecRequest): void {
133
190
  if (item.status === 'planning' || item.seats.length === 0) return;
191
+ const previous = item.status;
134
192
  if (item.seats.some(seat => seat.status === 'waiting' || seat.status === 'running')) item.status = 'running';
135
193
  else item.status = item.seats.some(seat => seat.status === 'failed') ? 'failed' : 'done';
136
194
  // 실패면 요약은 «실패 사유»가 이긴다 — 먼저 끝난 자리의 산출 설명이 남아 있어도 덮는다(혼합 결과에서 성공처럼 보이지 않게).
@@ -138,6 +196,7 @@ function aggregate(item: ExecRequest): void {
138
196
  const reasons = item.seats.filter(seat => seat.status === 'failed').map(seat => seat.reason ?? `${seat.seat}: 그래프 실행 실패`);
139
197
  if (reasons.length > 0) item.summary = reasons.join('; ');
140
198
  }
199
+ if (previous !== item.status && (item.status === 'done' || item.status === 'failed')) notifyTransition(item, previous, item.approvals.length);
141
200
  }
142
201
 
143
202
  export class ExecRequestRunner {
@@ -156,10 +215,12 @@ export class ExecRequestRunner {
156
215
  if (item.status === 'planning') {
157
216
  item.status = 'failed';
158
217
  item.summary = '데몬 재시작으로 COO 계획이 중단됐습니다';
218
+ notifyTransition(item, 'planning', item.approvals.length);
159
219
  } else {
160
220
  if (item.seats.length === 0) {
161
221
  item.status = 'failed';
162
222
  item.summary = '데몬 재시작으로 자리 배정이 중단됐습니다';
223
+ notifyTransition(item, 'running', item.approvals.length);
163
224
  }
164
225
  for (const seat of item.seats) {
165
226
  if (seat.status === 'done' || seat.status === 'failed') continue;
@@ -192,14 +253,16 @@ export class ExecRequestRunner {
192
253
  }
193
254
  }
194
255
 
195
- submit(text: string): ExecRequest {
196
- const item = this.store.create(text);
256
+ submit(text: string, attachments?: ExecAttachment[]): ExecRequest {
257
+ const item = this.store.create(text, attachments);
197
258
  void this.execute(item.id).catch(error => {
198
259
  const current = this.store.get(item.id);
199
260
  if (!current) return;
261
+ const previous = current.status;
200
262
  current.status = 'failed';
201
263
  current.summary = error instanceof Error ? error.message : String(error);
202
264
  this.store.save(current);
265
+ if (previous !== current.status) notifyTransition(current, previous, current.approvals.length);
203
266
  debug.log('exec-requests', 'failed', { id: item.id, reason: current.summary });
204
267
  });
205
268
  return item;
@@ -209,8 +272,40 @@ export class ExecRequestRunner {
209
272
  const item = this.store.get(id);
210
273
  if (!item) return;
211
274
  const graphs = await (this.deps.graphs ?? installedGraphs)();
212
- const plans = await (this.deps.plan ?? ((text: string) => planExecRequest(text, { graphs: async () => graphs })))(item.text);
275
+ const plans = await (this.deps.plan ?? ((text: string, attachments?: readonly ExecAttachment[]) => planExecRequest(text, { graphs: async () => graphs, attachments })))(item.text, item.attachments);
213
276
  const byId = new Map(graphs.map(graph => [graph.id, graph]));
277
+ // Every clause must be a question — «상황 알려줘? 카드 만들어줘» carries a work item (review r1).
278
+ const clauses = [...item.text.trim().matchAll(/([^??.!。\n]+)([??]?)/g)].map(m => ({ body: m[1]!.trim(), asked: m[2] !== '' })).filter(c => c.body);
279
+ const isQuestion = clauses.length > 0 && clauses.every(c => c.asked
280
+ || /(?:알려\s*줘|알려\s*주세요|보여\s*줘|보여\s*주세요|어떻게|무엇|뭐|어떤|언제|누구|질문|인가요|나요|습니까)$/.test(c.body));
281
+ if (isQuestion && plans.length > 0 && plans.every(plan => !plan.graphId
282
+ && !/\s—\s.+\s필요\s*$/.test(plan.title) && !/\s—\s.+\s필요\s*$/.test(plan.reason ?? '')
283
+ && !/(?:초안|작성|제작|생성|게시|분석|전략|메일|보고서)/.test(plan.title)) && !(item.attachments?.length)) {
284
+ for (const plan of plans) {
285
+ const answered = await (this.deps.answer ?? answerAsSeat)(plan.seat, item.text);
286
+ if (!answered) {
287
+ item.seats.push({ seat: plan.seat, title: plan.title, status: 'failed', graphId: '', runId: '',
288
+ reason: plan.reason ?? `${plan.seat}: 요청에 맞는 설치된 실행 그래프가 없습니다` });
289
+ continue;
290
+ }
291
+ const seat: ExecSeat = { seat: plan.seat, title: plan.title, status: 'done', graphId: 'seat-answer', runId: randomUUID() };
292
+ const dir = join(this.root, 'graph-runs', seat.graphId);
293
+ const statePath = join(dir, `${seat.runId}.json`);
294
+ const artifactDir = join(dir, `${seat.runId}.json.artifacts`);
295
+ mkdirSync(artifactDir, { recursive: true });
296
+ const file = join(artifactDir, TEXT_RESULT_NAME);
297
+ writeFileSync(file, redactSecrets(answered.text.slice(0, TEXT_RESULT_LIMIT)), { mode: 0o600 });
298
+ const state: GraphRunState = { graphId: seat.graphId, runId: seat.runId, status: 'done', path: [], executed: 0, dryRun: false, statePath, nodes: [] };
299
+ writeFileSync(statePath, JSON.stringify(state), { mode: 0o600 });
300
+ item.seats.push(seat);
301
+ const result = resultOf({ file, title: answered.title }, seat, id, this.root);
302
+ if (result) item.results.push(result);
303
+ }
304
+ item.status = 'running';
305
+ aggregate(item);
306
+ this.store.save(item);
307
+ return;
308
+ }
214
309
  item.seats = plans.map(plan => ({ seat: plan.seat, title: plan.title, status: plan.reason || !byId.has(plan.graphId) ? 'failed' : 'waiting', graphId: plan.graphId,
215
310
  runId: plan.reason || !byId.has(plan.graphId) ? '' : randomUUID(), inputs: plan.inputs, ...(plan.after?.length ? { after: plan.after } : {}),
216
311
  ...(plan.reason || !byId.has(plan.graphId) ? { reason: plan.reason ?? `${plan.seat}: 요청에 맞는 설치된 실행 그래프가 없습니다` } : {}) }));
@@ -366,12 +461,14 @@ export class ExecRequestRunner {
366
461
  for (const entry of Array.isArray(output.artifacts) ? output.artifacts : [output]) {
367
462
  if (!entry || typeof entry !== 'object' || Array.isArray(entry)) continue;
368
463
  const artifact = entry as Record<string, unknown>;
369
- const candidate = typeof artifact.file === 'string' ? artifact.file : artifact.path;
464
+ const candidate = fileCandidate(artifact);
370
465
  if (typeof candidate !== 'string') continue;
371
466
  const path = safeFile(candidate, this.root, seat.graphId, seat.runId);
372
467
  if (path && fileName(path, seat.runId) === name) return path;
373
468
  }
374
469
  }
470
+ const text = safeFile(join(this.root, 'graph-runs', seat.graphId, `${seat.runId}.json.artifacts`, TEXT_RESULT_NAME), this.root, seat.graphId, seat.runId);
471
+ if (text && fileName(text, seat.runId) === name) return text;
375
472
  } catch { return null; }
376
473
  return null;
377
474
  }
@@ -22,9 +22,15 @@ export interface ExecResult {
22
22
  url: string;
23
23
  sources?: Array<{ title: string; url: string }>;
24
24
  }
25
+ export interface ExecAttachment {
26
+ name: string;
27
+ path: string;
28
+ }
29
+
25
30
  export interface ExecRequest {
26
31
  id: string;
27
32
  text: string;
33
+ attachments?: ExecAttachment[];
28
34
  createdAt: string;
29
35
  status: 'planning' | 'running' | 'done' | 'failed';
30
36
  summary: string;
@@ -39,8 +45,8 @@ export class ExecRequestStore {
39
45
  readonly dir: string;
40
46
  constructor(configDir = getElanousConfigDir()) { this.dir = join(configDir, 'exec-requests'); }
41
47
 
42
- create(text: string): ExecRequest {
43
- const item: ExecRequest = { id: randomUUID(), text, createdAt: new Date().toISOString(), status: 'planning', summary: '', seats: [], results: [], approvals: [] };
48
+ create(text: string, attachments?: ExecAttachment[]): ExecRequest {
49
+ const item: ExecRequest = { id: randomUUID(), text, ...(attachments !== undefined ? { attachments } : {}), createdAt: new Date().toISOString(), status: 'planning', summary: '', seats: [], results: [], approvals: [] };
44
50
  this.save(item);
45
51
  return item;
46
52
  }
@@ -219,19 +219,22 @@ function uniqueName(dir: string, name: string, size: number, taken: Set<string>)
219
219
 
220
220
  export const FIELD_CAPTION_TAG = '#현장';
221
221
 
222
- /** 캡션에 독립 토큰 `#현장` 이 있으면 `{ event? }`, 없으면 null.
223
- * `#현장 <slug>` — 바로 다음 토큰을 소문자로 바꿔 슬러그 규칙에 맞으면 행사 이름, 아니면 기본 행사. */
224
- export function parseFieldCaption(caption: string | undefined | null): { event?: string; text?: string } | null {
222
+ /** 캡션에 독립 토큰 `#현장` 이 있으면 `{ event?, mode?, text? }`, 없으면 null.
223
+ * `#현장 <slug>` — 모드 토큰을 건너뛴 바로 다음 토큰을 소문자로 바꿔 슬러그 규칙에 맞으면 행사 이름, 아니면 기본 행사. */
224
+ export function parseFieldCaption(caption: string | undefined | null): { event?: string; mode?: 'instant'; text?: string } | null {
225
225
  if (!caption) return null;
226
226
  const tokens = caption.trim().split(/\s+/);
227
227
  const at = tokens.indexOf(FIELD_CAPTION_TAG);
228
228
  if (at < 0) return null;
229
- const next = tokens[at + 1]?.toLowerCase();
229
+ const isInstant = (token: string) => token === '즉석' || token.toLowerCase() === 'instant';
230
+ const mode = tokens.some(isInstant) ? 'instant' : undefined;
231
+ const eventAt = at + 1 + (tokens[at + 1] && isInstant(tokens[at + 1]) ? 1 : 0);
232
+ const next = tokens[eventAt]?.toLowerCase();
230
233
  const hasEvent = !!next && isFieldSlug(next);
231
- // 태그·행사 이름을 뺀 나머지가 «글»(자막) — 태그 앞에 쓴 글도 같이.
232
- const rest = tokens.filter((_, i) => i !== at && !(hasEvent && i === at + 1)).join(' ');
234
+ // 태그·행사 이름·모드를 뺀 나머지가 «글»(자막) — 태그 앞에 쓴 글도 같이.
235
+ const rest = tokens.filter((token, i) => i !== at && !(hasEvent && i === eventAt) && !isInstant(token)).join(' ');
233
236
  const text = normalizeFieldCaption(rest);
234
- return { ...(hasEvent ? { event: next } : {}), ...(text ? { text } : {}) };
237
+ return { ...(hasEvent ? { event: next } : {}), ...(mode ? { mode } : {}), ...(text ? { text } : {}) };
235
238
  }
236
239
 
237
240
  export const FIELD_CAPTIONS_FILE = 'captions.txt';
@@ -4,11 +4,12 @@ import { join, resolve } from 'node:path';
4
4
  import { randomUUID } from 'node:crypto';
5
5
  import { spawn, execFileSync } from 'node:child_process';
6
6
  import { debug } from '../debug/log.js';
7
+ import { mergeCapturedPath } from '../shell-env-bootstrap.js';
7
8
  import { listFieldMedia } from './field-media.js';
8
9
  import { startFieldFeed, type FieldFeedStart } from './field-feed-auto.js';
9
10
 
10
11
  export interface FieldReelResult { ok: boolean; file?: string; seconds: number; error?: string; feed?: FieldFeedStart }
11
- export type FieldReelRunner = (dir: string, opts: { title: string; sub: string }) => Promise<FieldReelResult>;
12
+ export type FieldReelRunner = (dir: string, opts: { title: string; sub: string; mode?: 'standard' | 'instant' }) => Promise<FieldReelResult>;
12
13
  export interface FieldReelStatus {
13
14
  state: 'waiting' | 'rendering' | 'done' | 'failed';
14
15
  items: number;
@@ -23,6 +24,7 @@ export interface FieldReelOptions {
23
24
  quietMs?: number;
24
25
  title?: string;
25
26
  sub?: string;
27
+ mode?: 'standard' | 'instant';
26
28
  runner?: FieldReelRunner;
27
29
  onDone?: (result: FieldReelResult) => void | Promise<void>;
28
30
  /** 같은 수신자가 앨범 항목마다 예약해도 한 렌더당 한 번만 통지한다. */
@@ -96,10 +98,12 @@ export function readFieldReelStatus(dir: string): FieldReelStatus | null {
96
98
  }
97
99
  }
98
100
 
99
- export const defaultFieldReelRunner: FieldReelRunner = (dir, { title, sub }) => new Promise((finish) => {
101
+ export const defaultFieldReelRunner = (dir: string, { title, sub, mode = 'standard' }: { title: string; sub: string; mode?: 'standard' | 'instant' }, spawnReel: typeof spawn = spawn): Promise<FieldReelResult> => new Promise((finish) => {
100
102
  const script = resolve(import.meta.dir, '../../skills/explainer-video/engine/reel.sh');
101
103
  const started = Date.now();
102
- const child = spawn('zsh', [script, dir, '--title', title, '--sub', sub], { stdio: 'ignore' });
104
+ const env = { ...process.env };
105
+ mergeCapturedPath(env, { capture: true });
106
+ const child = spawnReel('zsh', [script, dir, '--title', title, '--sub', sub, ...(mode === 'instant' ? ['--instant'] : [])], { stdio: 'ignore', env });
103
107
  let settled = false;
104
108
  const done = (error?: string) => {
105
109
  if (settled) return;
@@ -147,6 +151,7 @@ async function render(dir: string, job: Job): Promise<void> {
147
151
  result = await (renderOpts.runner ?? defaultFieldReelRunner)(dir, {
148
152
  title: titleLines[0]?.trim() || renderOpts.title || event,
149
153
  sub: titleLines[1]?.trim() || renderOpts.sub || sub,
154
+ ...(renderOpts.mode ? { mode: renderOpts.mode } : {}),
150
155
  });
151
156
  } catch (err) {
152
157
  result = { ok: false, seconds: 0, error: err instanceof Error ? err.message : String(err) };
@@ -160,10 +165,15 @@ async function render(dir: string, job: Job): Promise<void> {
160
165
  debug.log('field.reel', result.ok ? 'done' : 'failed', { event, items, seconds: result.seconds });
161
166
  if (result.ok) {
162
167
  // EV10b: 엔진이 timeline.json 에 남긴 비전 호출 수·성공·«현장 N» 강등·걸린 ms — codex 잔량과 겹치므로 관측한다(OP 10-01).
168
+ let cardsCache: 'hit' | 'miss' | 'n/a' = 'n/a';
163
169
  try {
164
- const t = JSON.parse(readFileSync(join(dir, 'reel', 'timeline.json'), 'utf8')) as { vision?: Record<string, unknown>; music?: unknown };
170
+ const t = JSON.parse(readFileSync(join(dir, 'reel', 'timeline.json'), 'utf8')) as { vision?: Record<string, unknown>; music?: unknown; cardsCache?: 'hit' | 'miss' };
165
171
  if (t.vision) debug.log('field.reel', 'vision', { event, ...t.vision, music: t.music === true });
172
+ if (renderOpts.mode === 'instant' && (t.cardsCache === 'hit' || t.cardsCache === 'miss')) cardsCache = t.cardsCache;
166
173
  } catch { debug.log('field.reel', 'vision-unread', { event }); }
174
+ // A custom runner without an explicit mode may render any way — say «unknown» instead of guessing the default.
175
+ const mode = renderOpts.mode ?? (renderOpts.runner ? 'unknown' : 'standard');
176
+ debug.log('field.reel', 'rendered', { mode, seconds: result.seconds, cardsCache });
167
177
  }
168
178
  // 뒤이은 업로드로 다시 렌더할 예정이면(dirty) 피드는 그 마지막 렌더 뒤에 한 번만 띄운다.
169
179
  if (result.ok && !job.dirty) result = { ...result, feed: (renderOpts.startFeed ?? ((d: string) => startFieldFeed(d, { enabled: renderOpts.autoFeed })))(dir) };
@@ -9,7 +9,7 @@
9
9
  // the pattern; we trim the hook-based fallback (sandboxed envs)
10
10
  // because elanous's primary surface runs native git.
11
11
 
12
- import { spawnSync, execFileSync } from 'node:child_process';
12
+ import { spawnSync, execFileSync, type SpawnSyncReturns } from 'node:child_process';
13
13
  import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, writeFileSync, rmSync, symlinkSync, lstatSync } from 'node:fs';
14
14
  import { homedir } from 'node:os';
15
15
  import { basename, dirname, join, resolve } from 'node:path';
@@ -17,8 +17,33 @@ import { findGitDir } from './locate.js';
17
17
  import { isTransientGitError } from './retry.js';
18
18
  import { runGitCommand } from './runner.js';
19
19
  import { debug } from '../debug/log.js';
20
+ import { getUserConfig } from '../user-config.js';
20
21
 
21
22
  const GIT_TIMEOUT_MS = 30_000;
23
+ const WORKTREE_ADD_TIMEOUT_MS = 5 * 60_000;
24
+
25
+ export interface WorktreeAddDeps {
26
+ spawn?: (command: string, args: string[], options: { cwd: string; encoding: 'utf8'; timeout: number; maxBuffer: number }) => SpawnSyncReturns<string>;
27
+ }
28
+
29
+ function worktreeAddTimeoutMs(): number {
30
+ const seconds = getUserConfig().harness?.worktreeAddTimeoutSec;
31
+ return typeof seconds === 'number' && Number.isSafeInteger(seconds) && seconds > 0 && seconds <= 2_147_483
32
+ ? seconds * 1000 : WORKTREE_ADD_TIMEOUT_MS;
33
+ }
34
+
35
+ function worktreeAddFailureLines(stderr: string): { lastProgress: string | null; details: string[] } {
36
+ let lastProgress: string | null = null;
37
+ const details: string[] = [];
38
+ for (const line of stderr.split(/[\r\n]+/).map((value) => value.trim()).filter(Boolean)) {
39
+ if (/^(?:Updating files|Checking out files|Receiving objects|Resolving deltas):\s*\d+%/.test(line)) {
40
+ lastProgress = line;
41
+ } else if (!/^Preparing worktree\b/.test(line)) {
42
+ details.push(line);
43
+ }
44
+ }
45
+ return { lastProgress, details: details.slice(-3) };
46
+ }
22
47
 
23
48
  /** Git metadata left by an interrupted operation in one checkout. */
24
49
  /** ⚠️ 공개인 이유(무인 리뷰 질의) — `GitResidueObservation.residues` 의 **원소 타입**이라,
@@ -618,7 +643,7 @@ function worktreeHoldingBranch(repoRoot: string, branch: string): string | null
618
643
  return null;
619
644
  }
620
645
 
621
- export function createWorktree(opts: CreateWorktreeOpts): CreateWorktreeResult {
646
+ export function createWorktree(opts: CreateWorktreeOpts, deps: WorktreeAddDeps = {}): CreateWorktreeResult {
622
647
  const { repoRoot, branch, worktreeRoot } = opts;
623
648
  validateBranchName(branch);
624
649
  const branchHolder = worktreeHoldingBranch(repoRoot, branch);
@@ -684,25 +709,39 @@ export function createWorktree(opts: CreateWorktreeOpts): CreateWorktreeResult {
684
709
  // ★ 병렬 안전 재시도(2026-07-21) — 동시 worktree add 가 git 락에서 경쟁하면 즉시 실패 → 락/일시적
685
710
  // 에러면 백오프 재시도(패자가 승자의 락 해제를 기다렸다 성공). non-transient 는 재시도 없이 throw.
686
711
  const MAX_ATTEMPTS = 6;
687
- let res!: ReturnType<typeof spawnSync>;
712
+ const addTimeoutMs = worktreeAddTimeoutMs();
713
+ const addSpawn = deps.spawn ?? spawnSync;
688
714
  for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
689
- res = spawnSync(
715
+ const res = addSpawn(
690
716
  'git',
691
717
  ['worktree', 'add', branchFlag, branch, wtPath, baseCheckout],
692
- { cwd: repoRoot, encoding: 'utf8', timeout: GIT_TIMEOUT_MS, maxBuffer: 4 * 1024 * 1024 },
718
+ { cwd: repoRoot, encoding: 'utf8', timeout: addTimeoutMs, maxBuffer: 4 * 1024 * 1024 },
693
719
  );
694
720
  if (res.status === 0) break;
695
721
  const err = (res.stderr || res.stdout || '').toString();
722
+ const { lastProgress, details } = worktreeAddFailureLines(err);
723
+ const timedOut = (res.error as NodeJS.ErrnoException | undefined)?.code === 'ETIMEDOUT';
724
+ if (timedOut) {
725
+ debug.log('git-fs.worktree', 'add.failed', { reason: 'timeout', ms: addTimeoutMs, signal: res.signal ?? null, lastProgress }, { level: 'warn' });
726
+ throw new Error(`git worktree add timed out after ${addTimeoutMs / 1000}s (${res.signal ?? 'none'}) — ${[...details, ...(lastProgress ? [`checkout was at «${lastProgress}»`] : [])].join(' | ') || `git exited ${res.status}`}`);
727
+ }
728
+ if (res.signal) {
729
+ debug.log('git-fs.worktree', 'add.failed', { reason: 'error', ms: addTimeoutMs, signal: res.signal, lastProgress }, { level: 'warn' });
730
+ throw new Error(`git worktree add failed (${res.signal}) — ${details.join(' | ') || lastProgress || `git exited ${res.status}`}`);
731
+ }
696
732
  const blockingPath = blockingWorktreePath(err);
697
733
  if (blockingPath) {
734
+ debug.log('git-fs.worktree', 'add.failed', { reason: 'error', ms: addTimeoutMs, signal: res.signal ?? null, lastProgress }, { level: 'warn' });
698
735
  debug.log('git-fs.worktree', 'add.branch-held', { attempt, branch, blockingPath }, { level: 'warn' });
699
736
  throw new Error(`git worktree add failed — branch ${branch} is already used by worktree at ${blockingPath}`);
700
737
  }
701
738
  if (isUnbornHeadError(err)) {
739
+ debug.log('git-fs.worktree', 'add.failed', { reason: 'error', ms: addTimeoutMs, signal: res.signal ?? null, lastProgress }, { level: 'warn' });
702
740
  throw new Error('git worktree add failed — this repository has no commits yet; create the first commit with: git commit --allow-empty -m "Initial commit"');
703
741
  }
704
742
  if (!isTransientGitError(err) || attempt === MAX_ATTEMPTS) {
705
- throw new Error(`git worktree add failed — ${err.trim() || `git exited ${res.status}`}`);
743
+ debug.log('git-fs.worktree', 'add.failed', { reason: 'error', ms: addTimeoutMs, signal: res.signal ?? null, lastProgress }, { level: 'warn' });
744
+ throw new Error(`git worktree add failed — ${details.join(' | ') || lastProgress || `git exited ${res.status}`}`);
706
745
  }
707
746
  debug.log('git-fs.worktree', 'add.retry', { attempt, branch, err: err.trim().slice(0, 120) });
708
747
  sleepSyncMs(80 * attempt); // 선형 백오프(80·160·240…ms)
@@ -0,0 +1,113 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { debug } from '../debug/log.js';
4
+ import { DecisionLedger, type DecisionEntry } from '../decisions/decision-ledger.js';
5
+ import { effectiveInstanceRoot } from '../instance/resolve.js';
6
+ import { decideGraphApproval, type GraphRunState } from './runner.js';
7
+
8
+ export interface GraphApprovalCardDeps {
9
+ root?: string;
10
+ ledger?: Pick<DecisionLedger, 'list' | 'raise'>;
11
+ }
12
+
13
+ type Pending = NonNullable<GraphRunState['pending']>;
14
+ const REF = /^graph-approval:([A-Za-z0-9][A-Za-z0-9._-]*):([A-Za-z0-9][A-Za-z0-9._-]*):([A-Za-z0-9][A-Za-z0-9._-]*):([1-9]\d*)$/;
15
+
16
+ function ledgerFor(deps: GraphApprovalCardDeps, root: string): Pick<DecisionLedger, 'list' | 'raise'> {
17
+ return deps.ledger ?? new DecisionLedger({ stateDir: root });
18
+ }
19
+
20
+ function observation(event: 'raised' | 'dedup' | 'applied' | 'skipped', state: Pick<GraphRunState, 'graphId' | 'runId'> & { nodeId: string }, decisionId?: string, outcome?: string, reason?: string): void {
21
+ debug.log('graph.approval-card', event, { graphId: state.graphId, runId: state.runId, nodeId: state.nodeId, decisionId, outcome, ...(reason ? { reason } : {}) });
22
+ }
23
+
24
+ export function raiseApprovalCard(state: GraphRunState, pending: Pending, deps: GraphApprovalCardDeps = {}): DecisionEntry | undefined {
25
+ const root = deps.root ?? effectiveInstanceRoot();
26
+ const nodeId = pending.nodeId;
27
+ const identity = { graphId: state.graphId, runId: state.runId, nodeId };
28
+ const visit = state.path.filter(node => node === nodeId).length;
29
+ if (state.dryRun || state.status !== 'awaiting-approval' || state.pending?.nodeId !== nodeId ||
30
+ state.pending.decision || state.path.at(-1) !== nodeId || visit < 1 ||
31
+ existsSync(`${join(root, 'graph-runs', state.graphId, `${state.runId}.json`)}.${state.path.length}.decision.json`)) {
32
+ observation('skipped', identity);
33
+ return undefined;
34
+ }
35
+ const ref = `graph-approval:${state.graphId}:${state.runId}:${nodeId}:${visit}`;
36
+ const ledger = ledgerFor(deps, root);
37
+ const existing = ledger.list({ status: 'all' }).find(entry => entry.refs?.includes(ref));
38
+ if (existing) {
39
+ observation('dedup', identity, existing.id, existing.choice);
40
+ return existing;
41
+ }
42
+ const publish = state.graphId === 'release-loop' && nodeId === 'approve-publish';
43
+ const firstLine = pending.message.split(/\r?\n/, 1)[0]?.trim();
44
+ const version = state.input && typeof state.input === 'object' && 'version' in state.input && typeof state.input.version === 'string'
45
+ ? state.input.version : state.runId;
46
+ const title = publish && firstLine ? firstLine : `${state.graphId} ${version} — ${nodeId} 승인`;
47
+ const entry = ledger.raise({ title, category: publish ? 'publish' : 'scope',
48
+ scqa: { s: `${state.graphId} / ${state.runId} 런이 ${nodeId} 승인에서 대기 중이다.`, c: pending.message.slice(0, 220) || '승인 대기 중이다.' },
49
+ options: [{ key: 'a', label: '승인', consequence: `${nodeId} 승인 후 런을 계속할 수 있다` },
50
+ { key: 'b', label: '거절', consequence: `${nodeId} 거절로 기록한다` }],
51
+ recommendation: { skipped: true, reason: '사람 판단' }, raisedBy: { agent: 'graph-runner' }, refs: [ref] });
52
+ observation('raised', identity, entry.id);
53
+ return entry;
54
+ }
55
+
56
+ /** A card answer only claims the same pending visit; runner owns the atomic decision file. */
57
+ export function applyDecidedApprovals(deps: GraphApprovalCardDeps = {}): number {
58
+ const root = deps.root ?? effectiveInstanceRoot();
59
+ let applied = 0;
60
+ for (const entry of ledgerFor(deps, root).list({ status: 'decided' })) {
61
+ for (const ref of entry.refs ?? []) {
62
+ const match = REF.exec(ref);
63
+ if (!match) continue;
64
+ const [, graphId, runId, nodeId, rawVisit] = match;
65
+ const identity = { graphId: graphId!, runId: runId!, nodeId: nodeId! };
66
+ // One unreadable run must not block every later card (L11 review round 1 should-fix).
67
+ try { if (applyOne(entry, identity, Number(rawVisit), root)) applied++; }
68
+ catch (error) {
69
+ observation('skipped', identity, entry.id, entry.choice, error instanceof Error ? error.message : String(error));
70
+ }
71
+ }
72
+ }
73
+ return applied;
74
+ }
75
+
76
+ function applyOne(entry: DecisionEntry, identity: { graphId: string; runId: string; nodeId: string }, visit: number, root: string): boolean {
77
+ const { graphId, runId, nodeId } = identity;
78
+ // Graph approvals gate irreversible steps (publish) — only a person's answer approves or rejects, never a delegated auto decision.
79
+ if ((entry.choice !== 'a' && entry.choice !== 'b') || entry.decidedBy?.kind !== 'human' || !Number.isSafeInteger(visit)) {
80
+ observation('skipped', identity, entry.id, entry.choice);
81
+ return false;
82
+ }
83
+ const statePath = join(root, 'graph-runs', graphId, `${runId}.json`);
84
+ let state: GraphRunState;
85
+ try { state = JSON.parse(readFileSync(statePath, 'utf8')) as GraphRunState; }
86
+ catch (error) {
87
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
88
+ observation('skipped', identity, entry.id, entry.choice);
89
+ return false;
90
+ }
91
+ if (state.graphId !== graphId || state.runId !== runId || state.dryRun || state.status !== 'awaiting-approval' ||
92
+ !state.pending || state.pending.nodeId !== nodeId || state.pending.decision || state.path.at(-1) !== nodeId ||
93
+ state.path.filter(node => node === nodeId).length !== visit ||
94
+ existsSync(`${statePath}.${state.path.length}.decision.json`)) {
95
+ observation('skipped', identity, entry.id, entry.choice);
96
+ return false;
97
+ }
98
+ const outcome = entry.choice === 'a' ? 'approved' : 'rejected';
99
+ try { decideGraphApproval(graphId, runId, outcome, `owner:${entry.decidedBy.kind}`, root,
100
+ { nodeId, visit: visit }); }
101
+ catch (error) {
102
+ // A CLI approver (or another notifier) may win the atomic hard-link claim.
103
+ // A resumed run may also have left this visit before the writer reads it.
104
+ const current = JSON.parse(readFileSync(statePath, 'utf8')) as GraphRunState;
105
+ if (!existsSync(`${statePath}.${state.path.length}.decision.json`) &&
106
+ current.status === 'awaiting-approval' && current.pending?.nodeId === nodeId && !current.pending.decision &&
107
+ current.path.at(-1) === nodeId && current.path.filter(node => node === nodeId).length === visit) throw error;
108
+ observation('skipped', identity, entry.id, outcome);
109
+ return false;
110
+ }
111
+ observation('applied', identity, entry.id, outcome);
112
+ return true;
113
+ }