elanous 0.2.18 → 0.2.20

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 (350) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/chunks/{1307-30dff22c9e1f3073.js → 1307-77fad80cf54477c6.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/1612-f60c0c93ec773f81.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/2116-6429c188eb846ab8.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/224-e7be8f762fe74986.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/2489-ff55a35b202c0f94.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/2549-89c518a4c8ed4d00.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/4144-fad095b48a1862ee.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/5755-ea7ed88eb956f47b.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/5977-94ed6f78e9814b2b.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/7748-6e51bfe927019f5c.js +25 -0
  14. package/apps/pwa/out/_next/static/chunks/8401-35e94e9091f5eb8d.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/9247-c1753a059e7bac08.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/a9f2b54d-33c0689b70ed8cf3.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/app/approvals/{page-d92f7ee3994e84c7.js → page-c855c24de7db74fe.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-4733c967b56a4277.js → page-85b316bb4235980b.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/board/{page-d22fd849cc22f104.js → page-9b2b26a33cb2cd42.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-bb742f995c6769e7.js → page-c9955fde67b1bcb5.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/app/bots/{page-02b073a2ac793b83.js → page-7a852956c74015a3.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/app/ceo/page-c5a5c621be0e04ae.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/app/chat/{page-8a9b3594bc23289b.js → page-4ba4abe096b6c2a8.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/consult/page-575d4a8b4f7becb2.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/control/{page-308111abadf35670.js → page-f5d56ff5f45ad164.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/app/decisions/page-2260e9eef0357421.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-5d97b2901e0d1ea6.js → page-7c058069c5e7fa63.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/editor/page-dc5da0b8a2edb8a4.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/exec/{page-8c3d8e63ae2e1c98.js → page-466bf0ee16ad1432.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/app/field/{page-9b72a07d424701c6.js → page-a23bc71713bcc08f.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/inside/page-ed4a6f799f005d09.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/intake/{page-a572f58cd3ca5864.js → page-9cd565005b93aca6.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/app/layout-55f0f40c6db95f15.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/app/live/{page-be5ea965a07fbc41.js → page-474cb5888ea0e8f3.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/loops/page-35bfec2e631d3eae.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/market/{page-fa244bd146a00ae8.js → page-9ca21f9ad3208a61.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-44d56d6391965a6a.js → page-f9ccb1b1adbea14d.js} +1 -1
  38. package/apps/pwa/out/_next/static/chunks/app/morning/{page-83a700d4c754d802.js → page-ec102c9b8b691e37.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/app/{not-found-7b3170f374f77b9d.js → not-found-a9c2cfa8dda922a5.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/observatory/page-6da35144878feb6d.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-f6055495e9ed7ea8.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-25204694fa9984a5.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a60e035f151d84d3.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/outputs/page-189c587f36874538.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/app/page-c56634bde705a65d.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-1467047104141297.js → page-db03ae3589a81c9e.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-4a6463b97f0ad94f.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/sessions/{page-102310930791197a.js → page-75bffecc9aa1004e.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a8d2fe0c669e0de4.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/settings/{page-6ed0b73bae8351d8.js → page-31d5b875760bb62d.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-c26fa10aac6c21c4.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/setup/{page-43336055451f6e37.js → page-68793c6e10331d66.js} +1 -1
  53. package/apps/pwa/out/_next/static/chunks/app/share/{page-3cc47dca0922ab9a.js → page-46d5201d2207a511.js} +1 -1
  54. package/apps/pwa/out/_next/static/chunks/app/showroom/{page-a77ee355a2304ff0.js → page-d81c07a7b4f618ae.js} +1 -1
  55. package/apps/pwa/out/_next/static/chunks/app/tasks/{page-b17cd46cf0e9b8cf.js → page-be1c4e8697f22360.js} +1 -1
  56. package/apps/pwa/out/_next/static/chunks/app/term/page-cf0a4b4abead71ea.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/today/page-a4717e2c4bf7a28f.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/trace/{page-7bf54c9bb9274af8.js → page-0fd895fab9b62c1b.js} +1 -1
  59. package/apps/pwa/out/_next/static/chunks/app/vault/page-8b6c1454a8e8f306.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-c6e4364d9de8f717.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/workflows/page-0acc8e198f7b0dfb.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-134da468538baf48.js → page-34882c84f84021c5.js} +1 -1
  63. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-0506a1e0e4bdb2f5.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/{webpack-c2782b330447ffb6.js → webpack-c260571513bf43c2.js} +1 -1
  65. package/apps/pwa/out/_next/static/css/{e99e3cff94ced78a.css → 599369d853c61df7.css} +1 -1
  66. package/apps/pwa/out/_next/static/css/c23d959d3ee15e57.css +1 -0
  67. package/apps/pwa/out/_next/static/css/d3427996da11e480.css +1 -0
  68. package/apps/pwa/out/approvals/index.html +1 -1
  69. package/apps/pwa/out/approvals/index.txt +14 -14
  70. package/apps/pwa/out/autopilot/index.html +1 -1
  71. package/apps/pwa/out/autopilot/index.txt +14 -14
  72. package/apps/pwa/out/board/index.html +1 -1
  73. package/apps/pwa/out/board/index.txt +14 -14
  74. package/apps/pwa/out/botlab/index.html +1 -1
  75. package/apps/pwa/out/botlab/index.txt +14 -14
  76. package/apps/pwa/out/bots/index.html +1 -1
  77. package/apps/pwa/out/bots/index.txt +14 -14
  78. package/apps/pwa/out/ceo/index.html +1 -1
  79. package/apps/pwa/out/ceo/index.txt +14 -14
  80. package/apps/pwa/out/chat/index.html +1 -1
  81. package/apps/pwa/out/chat/index.txt +14 -14
  82. package/apps/pwa/out/consult/index.html +1 -1
  83. package/apps/pwa/out/consult/index.txt +14 -14
  84. package/apps/pwa/out/control/index.html +1 -1
  85. package/apps/pwa/out/control/index.txt +14 -14
  86. package/apps/pwa/out/decisions/index.html +1 -1
  87. package/apps/pwa/out/decisions/index.txt +14 -14
  88. package/apps/pwa/out/design-check/index.html +1 -1
  89. package/apps/pwa/out/design-check/index.txt +14 -14
  90. package/apps/pwa/out/editor/index.html +1 -1
  91. package/apps/pwa/out/editor/index.txt +16 -15
  92. package/apps/pwa/out/exec/index.html +1 -1
  93. package/apps/pwa/out/exec/index.txt +14 -14
  94. package/apps/pwa/out/field/index.html +1 -1
  95. package/apps/pwa/out/field/index.txt +14 -14
  96. package/apps/pwa/out/healthz +1 -1
  97. package/apps/pwa/out/index.html +1 -1
  98. package/apps/pwa/out/index.txt +14 -14
  99. package/apps/pwa/out/inside/index.html +1 -1
  100. package/apps/pwa/out/inside/index.txt +16 -15
  101. package/apps/pwa/out/intake/index.html +1 -1
  102. package/apps/pwa/out/intake/index.txt +14 -14
  103. package/apps/pwa/out/live/index.html +1 -1
  104. package/apps/pwa/out/live/index.txt +14 -14
  105. package/apps/pwa/out/loops/index.html +1 -1
  106. package/apps/pwa/out/loops/index.txt +24 -23
  107. package/apps/pwa/out/market/index.html +1 -1
  108. package/apps/pwa/out/market/index.txt +14 -14
  109. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  110. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  111. package/apps/pwa/out/morning/index.html +1 -1
  112. package/apps/pwa/out/morning/index.txt +14 -14
  113. package/apps/pwa/out/observatory/index.html +1 -1
  114. package/apps/pwa/out/observatory/index.txt +14 -14
  115. package/apps/pwa/out/ops/checklist/index.html +1 -1
  116. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  117. package/apps/pwa/out/ops/release/index.html +1 -1
  118. package/apps/pwa/out/ops/release/index.txt +14 -14
  119. package/apps/pwa/out/ops/seats/index.html +1 -1
  120. package/apps/pwa/out/ops/seats/index.txt +14 -14
  121. package/apps/pwa/out/outputs/index.html +1 -1
  122. package/apps/pwa/out/outputs/index.txt +14 -14
  123. package/apps/pwa/out/reflection/index.html +1 -1
  124. package/apps/pwa/out/reflection/index.txt +15 -15
  125. package/apps/pwa/out/scheduler/index.html +1 -1
  126. package/apps/pwa/out/scheduler/index.txt +14 -14
  127. package/apps/pwa/out/sessions/index.html +1 -1
  128. package/apps/pwa/out/sessions/index.txt +14 -14
  129. package/apps/pwa/out/settings/devices/index.html +1 -1
  130. package/apps/pwa/out/settings/devices/index.txt +14 -14
  131. package/apps/pwa/out/settings/index.html +1 -1
  132. package/apps/pwa/out/settings/index.txt +14 -14
  133. package/apps/pwa/out/setup/done/index.html +1 -1
  134. package/apps/pwa/out/setup/done/index.txt +14 -14
  135. package/apps/pwa/out/setup/index.html +1 -1
  136. package/apps/pwa/out/setup/index.txt +14 -14
  137. package/apps/pwa/out/share/index.html +1 -1
  138. package/apps/pwa/out/share/index.txt +14 -14
  139. package/apps/pwa/out/showroom/index.html +1 -1
  140. package/apps/pwa/out/showroom/index.txt +14 -14
  141. package/apps/pwa/out/tasks/index.html +1 -1
  142. package/apps/pwa/out/tasks/index.txt +14 -14
  143. package/apps/pwa/out/term/index.html +1 -1
  144. package/apps/pwa/out/term/index.txt +14 -14
  145. package/apps/pwa/out/today/index.html +1 -1
  146. package/apps/pwa/out/today/index.txt +14 -14
  147. package/apps/pwa/out/trace/index.html +1 -1
  148. package/apps/pwa/out/trace/index.txt +14 -14
  149. package/apps/pwa/out/vault/index.html +1 -1
  150. package/apps/pwa/out/vault/index.txt +14 -14
  151. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  152. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  153. package/apps/pwa/out/workflows/index.html +1 -1
  154. package/apps/pwa/out/workflows/index.txt +14 -14
  155. package/apps/pwa/out/workspace/index.html +1 -1
  156. package/apps/pwa/out/workspace/index.txt +14 -14
  157. package/apps/pwa/out/worktrees/index.html +1 -1
  158. package/apps/pwa/out/worktrees/index.txt +14 -14
  159. package/graphs/demo/editor-recipes.yaml +22 -0
  160. package/graphs/ops/guardian.yaml +26 -0
  161. package/graphs/ops/recipes.yaml +3 -0
  162. package/graphs/retro/daily.yaml +25 -0
  163. package/graphs/retro/recipes.yaml +24 -0
  164. package/package.json +1 -1
  165. package/scripts/ask-marker-check.ts +3 -1
  166. package/scripts/coord-channel-watch.sh +2 -2
  167. package/scripts/discord-seat-probe.ts +3 -1
  168. package/scripts/land-global-checks.ts +89 -0
  169. package/scripts/lib/pch-regress-map.ts +24 -0
  170. package/scripts/morning-report.ts +18 -4
  171. package/scripts/pch-regress.ts +88 -0
  172. package/scripts/release-loop/gate-diff.ts +18 -0
  173. package/scripts/release-loop/gate-node.ts +628 -65
  174. package/scripts/release-loop/gate-partial.ts +106 -0
  175. package/scripts/release-loop/gate-timing.ts +229 -0
  176. package/scripts/release-loop/preflight.ts +6 -4
  177. package/scripts/release-loop/publish-window.ts +141 -0
  178. package/scripts/release-loop/release-docs.ts +1 -4
  179. package/scripts/release-loop/release-universe.ts +38 -0
  180. package/scripts/release-loop/resume-release.ts +67 -5
  181. package/scripts/release-loop/shard-plan.ts +65 -0
  182. package/scripts/release-loop/unattended-release.ts +12 -2
  183. package/scripts/release-story/draft.ts +5 -1
  184. package/scripts/retro/shadow-compare.ts +71 -0
  185. package/scripts/rhythm/daily.ts +6 -1
  186. package/scripts/video/scene-table.ts +159 -0
  187. package/src/acp/core-turn-bridge.ts +39 -2
  188. package/src/agent-mission/driver.ts +34 -6
  189. package/src/autopilot/build/llm-conflict-merge.ts +132 -7
  190. package/src/autopilot/build/merge-intent.ts +135 -7
  191. package/src/away/presence.ts +40 -0
  192. package/src/away/release-watch.ts +272 -0
  193. package/src/chat/index.ts +2 -1
  194. package/src/chat/short-question.ts +3 -40
  195. package/src/cli/away-cli.ts +68 -0
  196. package/src/cli/config-drift.ts +513 -0
  197. package/src/cli/guardian-cli.ts +49 -0
  198. package/src/cli/intake-cli.ts +13 -7
  199. package/src/cli/log-event-names.ts +5 -0
  200. package/src/cli/pod-cli.ts +5 -1
  201. package/src/cli/pr-cli.ts +20 -0
  202. package/src/cli/pty-drive-cli.ts +14 -3
  203. package/src/cli/release-cli.ts +52 -12
  204. package/src/cli/retro-cli.ts +78 -0
  205. package/src/cli/setup-cli.ts +31 -1
  206. package/src/cli/tasks-cli.ts +193 -14
  207. package/src/context-bus/context-now-public.ts +1 -1
  208. package/src/context-bus/context-now-surfaces.ts +16 -6
  209. package/src/context-bus/context-now.ts +97 -3
  210. package/src/core-turn/short-question.ts +100 -0
  211. package/src/dashboard/dashboard-status-lines.ts +24 -4
  212. package/src/dashboard/index.ts +50 -5
  213. package/src/dashboard/input/slash-executor.ts +6 -2
  214. package/src/dashboard/slash-runtime/dashboard-handlers.ts +68 -40
  215. package/src/dashboard/slash-runtime/decide-slash.ts +60 -0
  216. package/src/dashboard/slash-runtime/help-from-registry.ts +62 -23
  217. package/src/dashboard/slash-runtime/help-groups.ts +44 -0
  218. package/src/dashboard/slash-runtime/model-choices.ts +125 -0
  219. package/src/domains/guardian-tick.ts +138 -0
  220. package/src/domains/outbound-alert.ts +44 -13
  221. package/src/domains/outbound-log-probe.ts +6 -1
  222. package/src/domains/schedule-registry.ts +23 -0
  223. package/src/domains/telegram-kind-route.ts +39 -0
  224. package/src/domains/watch-default-scheduler.ts +95 -0
  225. package/src/esc-abort-gate.ts +44 -0
  226. package/src/graph-runner/graph-run-api.ts +205 -0
  227. package/src/graph-runner/runner.ts +36 -6
  228. package/src/harness/harness-cli-command.ts +420 -51
  229. package/src/harness/harness-queue.ts +315 -25
  230. package/src/harness/harness-salvage-cli.ts +140 -0
  231. package/src/harness/harness-worktree-add.ts +68 -0
  232. package/src/harness/ledger-write-guard.ts +6 -16
  233. package/src/hq/fence-outcomes.ts +63 -0
  234. package/src/hq/fence-wrapper.ts +33 -0
  235. package/src/hq/hq.ts +17 -2
  236. package/src/index.ts +118 -6
  237. package/src/instance/test-write-guard-spy-first.fixture.ts +13 -0
  238. package/src/instance/test-write-guard.ts +200 -0
  239. package/src/intake-plane/digest.ts +39 -15
  240. package/src/intake-plane/harness-target.ts +4 -3
  241. package/src/intake-plane/intake-sources.ts +1 -0
  242. package/src/intake-plane/items.ts +1 -0
  243. package/src/loops/orchestrator/degradation.ts +57 -0
  244. package/src/loops/orchestrator/finish-rate.ts +33 -3
  245. package/src/loops/orchestrator/tick.ts +166 -45
  246. package/src/maturity/feature-maturity.ts +6 -4
  247. package/src/maturity/tui-slash-maturity.ts +1 -0
  248. package/src/mss/logging/log-store.ts +3 -0
  249. package/src/nexus/api/graph-kinds.ts +10 -4
  250. package/src/nexus/api/graphs-api.ts +178 -6
  251. package/src/nexus/api/http-server.ts +54 -8
  252. package/src/nexus/api/loop-edges.ts +292 -17
  253. package/src/nexus/api/ops-api.ts +112 -29
  254. package/src/nexus/api/ops-board-route.ts +48 -0
  255. package/src/nexus/index.ts +7 -0
  256. package/src/onboarding/entry-hints.ts +5 -0
  257. package/src/onboarding.ts +24 -6
  258. package/src/release-loop/checklist.ts +2 -0
  259. package/src/release-loop/feature-store.ts +68 -18
  260. package/src/release-loop/gate-shards.ts +158 -0
  261. package/src/release-loop/light-rc.ts +173 -0
  262. package/src/release-loop/placement.ts +51 -17
  263. package/src/release-loop/release-schedule.ts +6 -2
  264. package/src/release-loop/rubric.ts +57 -1
  265. package/src/retro/daily.ts +372 -0
  266. package/src/seat-loop/op-decide.ts +173 -0
  267. package/src/seat-loop/seat-loop.ts +60 -3
  268. package/src/seat-loop/seat-report.ts +58 -3
  269. package/src/self-dev/ask-launch-flow.ts +5 -4
  270. package/src/self-dev/ask-launch-io.ts +49 -1
  271. package/src/self-dev/ask-path-candidates.ts +40 -2
  272. package/src/self-dev/dev-cli.ts +36 -0
  273. package/src/self-dev/draft-sweep.ts +371 -16
  274. package/src/self-dev/draft-triage-rules.ts +19 -4
  275. package/src/self-dev/orchestrate.ts +1 -0
  276. package/src/self-dev/pr-preview.ts +247 -0
  277. package/src/self-dev/shard-run.ts +23 -7
  278. package/src/self-implement/blocked-draft-policy.ts +101 -0
  279. package/src/self-implement/execution-clarification.ts +11 -0
  280. package/src/self-implement/gate-cli.ts +29 -8
  281. package/src/self-implement/gate-env-retry.ts +83 -0
  282. package/src/self-implement/goal-author-clarification.ts +88 -13
  283. package/src/self-implement/goal-clarification-escalation.ts +2 -0
  284. package/src/self-implement/orchestrator.ts +220 -6
  285. package/src/self-implement/run-stop.ts +123 -0
  286. package/src/self-implement/run-ttl.ts +258 -0
  287. package/src/self-implement/salvage-branches.ts +254 -0
  288. package/src/self-implement/salvage-retention.ts +123 -0
  289. package/src/self-implement/seams.ts +4 -0
  290. package/src/self-implement/self-implement-cli.ts +2 -0
  291. package/src/self-implement/sibling-resync.ts +223 -0
  292. package/src/self-implement/stop-autoheal.ts +204 -0
  293. package/src/task-agent/actions.ts +63 -2
  294. package/src/task-agent/board.ts +132 -0
  295. package/src/task-agent/card-evidence.ts +292 -0
  296. package/src/task-agent/completion-evidence.ts +126 -0
  297. package/src/task-agent/judge.ts +25 -0
  298. package/src/task-agent/live-moves.ts +276 -0
  299. package/src/task-agent/mission.ts +33 -7
  300. package/src/task-agent/shadow.ts +192 -15
  301. package/src/task-agent/task-hand.ts +124 -17
  302. package/src/task-orchestrator/surfaces/pod-command-job.ts +82 -8
  303. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +4 -0
  304. package/src/task-orchestrator/surfaces/pod-install-slots.ts +89 -0
  305. package/src/task-orchestrator/surfaces/pod-lease.ts +233 -21
  306. package/src/task-orchestrator/surfaces/pod-pool.ts +155 -34
  307. package/src/task-orchestrator/surfaces/self-implement-pod.ts +21 -5
  308. package/src/telegram-away-command.ts +38 -0
  309. package/src/telegram-commands.ts +52 -0
  310. package/src/telegram-report-log-probe.ts +21 -0
  311. package/src/telegram-report.ts +27 -3
  312. package/src/testing/real-cli-spawn.ts +106 -0
  313. package/src/tui-client/dashboard-session.ts +1 -0
  314. package/src/user-config.ts +190 -5
  315. package/src/version/packed-revision.json +1 -1
  316. package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +0 -1
  317. package/apps/pwa/out/_next/static/chunks/3857-4f936cdb30322aa6.js +0 -1
  318. package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +0 -1
  319. package/apps/pwa/out/_next/static/chunks/4554-5761612531d0146b.js +0 -1
  320. package/apps/pwa/out/_next/static/chunks/5977-a11f37254196b659.js +0 -1
  321. package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +0 -1
  322. package/apps/pwa/out/_next/static/chunks/8401-8615a660c84cdb03.js +0 -1
  323. package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +0 -25
  324. package/apps/pwa/out/_next/static/chunks/9247-269e4464cc4b491c.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/a9f2b54d-7865aba0f4d2ee49.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/app/ceo/page-60c3da708f9a0ccc.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/consult/page-a0eb02638b1e6f31.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/decisions/page-6eaf7bac37830c21.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/editor/page-c39554c321576982.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/inside/page-bd5d586429167fea.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/layout-59b7834d497c2255.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/loops/page-c130b63862437814.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/observatory/page-5f23d0c66a3b4793.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-a3c58f5c72a5f3f9.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a2c4625596f8d69c.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-0e9099531d239f11.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/app/outputs/page-e8e1ecaea7cd71d2.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/app/page-a1b7ffed710a93e0.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-2da6f9b8a38c6978.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-f331e38fefc00393.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-44d5f767491692bf.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/app/term/page-61553851aa606146.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/app/today/page-a1f585e154e61c59.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/app/vault/page-5150ef17b5383a57.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b9d8ce70792c1672.js +0 -1
  346. package/apps/pwa/out/_next/static/chunks/app/workflows/page-a5a3de231f2cc7d5.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31ac4afa2dcd915c.js +0 -1
  348. package/apps/pwa/out/_next/static/css/7fa358fec7da7c47.css +0 -1
  349. /package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → Bam1YziK2iH9bN4K0utGk}/_buildManifest.js +0 -0
  350. /package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → Bam1YziK2iH9bN4K0utGk}/_ssgManifest.js +0 -0
@@ -0,0 +1,106 @@
1
+ /**
2
+ * ⏱️ GATE-SPEED ③ — 실물 엔트리 spawn 을 «비동기 ⊕ 상한 있는 동시성»으로 돌리는 테스트 전용 도구.
3
+ *
4
+ * Situation: 배선 시험(`bin/elanous.mjs` 실물 spawn)은 1회 ~3–4초(전 모듈 그래프 콜드 스타트)이고,
5
+ * `spawnSync` 로 줄 세우면 파일 하나가 게이트 임계 경로(수백 초)를 잡는다.
6
+ * Answer: 시험이 문는 값(실물 프로세스의 stdout·stderr·종료 코드·시그널)은 그대로 두고,
7
+ * «기다리는 방식»만 바꾼다 — `describe.concurrent` 안에서 이 함수를 `await` 하면 서로 독립인
8
+ * spawn 들이 `REAL_CLI_SPAWN_CONCURRENCY` 개까지 겹쳐 돈다.
9
+ *
10
+ * ⛔ 반환 모양은 `spawnSync(..., { encoding: 'utf8' })` 와 같다 — 시그널로 죽으면 `status` 는 null 이고
11
+ * `signal` 이 찬다. spawn 자체가 실패하면 `error` 가 찬다(타임아웃 = 0바이트 산출과 구분하려고).
12
+ * ⛔ spawn 의 `timeoutMs` 는 «슬롯을 얻은 뒤»부터 잰다 — 대기열에서 기다린 시간은 실패로 세지 않는다.
13
+ * 그래서 이 도구를 쓰는 시험의 «테스트» 타임아웃은 대기열 몫까지 넉넉히 둔다.
14
+ */
15
+ import { availableParallelism } from 'node:os';
16
+
17
+ export interface RealCliSpawnResult {
18
+ stdout: string;
19
+ stderr: string;
20
+ status: number | null;
21
+ signal: string | null;
22
+ error?: Error;
23
+ }
24
+
25
+ export interface RealCliSpawnOptions {
26
+ cwd?: string;
27
+ env?: Record<string, string | undefined>;
28
+ timeoutMs: number;
29
+ }
30
+
31
+ function resolveConcurrency(): number {
32
+ const fromEnv = Number(process.env.ELANOUS_TEST_REAL_CLI_CONCURRENCY);
33
+ if (Number.isInteger(fromEnv) && fromEnv > 0) return fromEnv;
34
+ return Math.max(2, Math.min(6, Math.floor(availableParallelism() / 3)));
35
+ }
36
+
37
+ export const REAL_CLI_SPAWN_CONCURRENCY = resolveConcurrency();
38
+
39
+ let active = 0;
40
+ const waiters: Array<() => void> = [];
41
+
42
+ async function withSlot<T>(run: () => Promise<T>): Promise<T> {
43
+ while (active >= REAL_CLI_SPAWN_CONCURRENCY) await new Promise<void>((resolve) => waiters.push(resolve));
44
+ active += 1;
45
+ try {
46
+ return await run();
47
+ } finally {
48
+ active -= 1;
49
+ waiters.shift()?.();
50
+ }
51
+ }
52
+
53
+ function definedEnv(env: Record<string, string | undefined>): Record<string, string> {
54
+ const out: Record<string, string> = {};
55
+ for (const [key, value] of Object.entries(env)) if (value !== undefined) out[key] = value;
56
+ return out;
57
+ }
58
+
59
+ /** `spawnSync(cmd[0], cmd.slice(1), { encoding: 'utf8' })` 의 비동기 대응 — 동시성 상한 안에서 돈다. */
60
+ export async function spawnRealCli(cmd: string[], options: RealCliSpawnOptions): Promise<RealCliSpawnResult> {
61
+ const r = await spawnRealCliBytes(cmd, options);
62
+ const decoder = new TextDecoder();
63
+ return {
64
+ stdout: decoder.decode(r.stdout),
65
+ stderr: decoder.decode(r.stderr),
66
+ status: r.exitCode,
67
+ signal: r.signal,
68
+ ...(r.error ? { error: r.error } : {}),
69
+ };
70
+ }
71
+
72
+ export interface RealCliSpawnBytesResult {
73
+ /** `Bun.spawnSync` 와 같은 이름 — 시그널로 죽으면 null. */
74
+ exitCode: number | null;
75
+ stdout: Uint8Array;
76
+ stderr: Uint8Array;
77
+ signal: string | null;
78
+ error?: Error;
79
+ }
80
+
81
+ /** `Bun.spawnSync({ cmd, stdout: 'pipe', stderr: 'pipe' })` 의 비동기 대응 — 바이트 그대로 돌려준다. */
82
+ export function spawnRealCliBytes(cmd: string[], options: RealCliSpawnOptions): Promise<RealCliSpawnBytesResult> {
83
+ return withSlot(async () => {
84
+ let proc: ReturnType<typeof Bun.spawn>;
85
+ try {
86
+ proc = Bun.spawn(cmd, {
87
+ cwd: options.cwd,
88
+ env: definedEnv(options.env ?? { ...process.env }),
89
+ stdin: 'ignore',
90
+ stdout: 'pipe',
91
+ stderr: 'pipe',
92
+ timeout: options.timeoutMs,
93
+ killSignal: 'SIGKILL',
94
+ });
95
+ } catch (error) {
96
+ return { stdout: new Uint8Array(), stderr: new Uint8Array(), exitCode: null, signal: null, error: error instanceof Error ? error : new Error(String(error)) };
97
+ }
98
+ const [stdout, stderr] = await Promise.all([
99
+ new Response(proc.stdout as ReadableStream).arrayBuffer(),
100
+ new Response(proc.stderr as ReadableStream).arrayBuffer(),
101
+ ]);
102
+ await proc.exited;
103
+ const signal = proc.signalCode ?? null;
104
+ return { stdout: new Uint8Array(stdout), stderr: new Uint8Array(stderr), exitCode: signal ? null : proc.exitCode, signal };
105
+ });
106
+ }
@@ -195,6 +195,7 @@ export class DashboardSession {
195
195
  const pair = createInProcessTransportPair();
196
196
  const shutdownCtrl = new AbortController();
197
197
  const runTurn = bridgeCoreTurnToAcp({
198
+ tuiRouting: true,
198
199
  getMessages: opts.getMessages,
199
200
  getTools: opts.getTools,
200
201
  dispatchTool: opts.dispatchTool,
@@ -27,6 +27,7 @@
27
27
  // crashed wizard cannot corrupt an existing file. Mode 0600 when the
28
28
  // telegram bot token is present.
29
29
 
30
+ import { DRAFT_ON_STOP_MODES, parseDraftOnStopMode, type DraftOnStopMode } from './self-implement/blocked-draft-policy.js';
30
31
  import { isCodexQuotaPolicy } from './oauth/codex-quota-policy.js';
31
32
  import { defaultObsidianVault } from './obsidian/default-vault.js';
32
33
  import {
@@ -1146,6 +1147,12 @@ export interface SelfImplementToolConfig {
1146
1147
  decompositionShadow: SelfImplementDecompositionShadowConfig;
1147
1148
  /** Opt-in delivery of authored goal clarifications from unattended dev runs. */
1148
1149
  clarificationEscalation: SelfImplementClarificationEscalationConfig;
1150
+ /** RUN-TTL: hours an open self-impl PR may go without a commit before the draft sweep asks for a rebase. Absent or invalid = 24. */
1151
+ runTtlHours?: number;
1152
+ /** RUN-TTL sweep step: `shadow` (default) only logs; `live` sends the resync memo / adds `elanous:needs-rebase`. Cap absent or invalid = 30. */
1153
+ runTtl?: { mode: 'shadow' | 'live'; cap?: number };
1154
+ /** STOP-AUTOHEAL: `shadow` (default) only records what the stop dispatcher would do (`event='autoheal'`); `live` runs the injected heal seams once per run per stop class. */
1155
+ autoheal?: { mode: 'shadow' | 'live' };
1149
1156
  /** 사용자 자식 모델 선호. 없으면 해석기가 auto + fallbackChain 으로 만든다.
1150
1157
  * Overload failover (OVERLOAD-FAILOVER) may move an unpinned codex child to
1151
1158
  * grok. A pinned codex provider is never moved. */
@@ -1165,6 +1172,11 @@ export interface SelfImplementToolConfig {
1165
1172
  * clean)로 남는다 — draft PR 은 닫고 브랜치를 지우면 되돌릴 수 있지만 병합은 아니다.
1166
1173
  * `false` 로 두면 종전대로 `ux.confirm`(채널 없으면 fail-closed). */
1167
1174
  autoOpenPr: boolean;
1175
+ /** DRAFT-NOT-ARCHIVE — 멈춘 런의 산출을 어디에 남기나. `'always'`(기본 · 종전) = draft PR ·
1176
+ * `'needs-owner-only'` = 사람 판단이 필요한 멈춤만 draft PR(⊕ 주인 자리 라벨), 나머지는 수확 가지 `salvage/run-<6hex>/…` ⊕ 런 원장. */
1177
+ draftOnStop?: DraftOnStopMode;
1178
+ /** SALVAGE-RETENTION — 원격 `salvage/*` 보존 규칙(14일 초과 ⊕ 열린 PR·카드 참조 0). `'shadow'`(기본) = 수만 보고 · `'live'` = 다음 조각에서 삭제. */
1179
+ salvageRetention?: { mode: 'shadow' | 'live' };
1168
1180
  }
1169
1181
 
1170
1182
  export interface RunDevHarnessToolConfig {
@@ -2670,6 +2682,18 @@ function parseToolsConfig(raw: unknown): ToolsConfig {
2670
2682
  ...(clarificationTimeoutMs === undefined ? {} : { timeoutMs: clarificationTimeoutMs }),
2671
2683
  };
2672
2684
  const childLlm = parseChildLlmPreference(selfImplRaw.childLlm);
2685
+ const runTtlHoursRaw = selfImplRaw.runTtlHours;
2686
+ const runTtlHours = typeof runTtlHoursRaw === 'number' && Number.isFinite(runTtlHoursRaw) && runTtlHoursRaw > 0 ? runTtlHoursRaw : undefined;
2687
+ const runTtlRaw = selfImplRaw.runTtl && typeof selfImplRaw.runTtl === 'object' && !Array.isArray(selfImplRaw.runTtl)
2688
+ ? selfImplRaw.runTtl as Record<string, unknown> : undefined;
2689
+ const runTtl = runTtlRaw ? {
2690
+ mode: runTtlRaw.mode === 'live' ? 'live' as const : 'shadow' as const,
2691
+ ...(typeof runTtlRaw.cap === 'number' && Number.isSafeInteger(runTtlRaw.cap) && runTtlRaw.cap >= 0 ? { cap: runTtlRaw.cap } : {}),
2692
+ } : undefined;
2693
+ const autohealRaw = selfImplRaw.autoheal && typeof selfImplRaw.autoheal === 'object' && !Array.isArray(selfImplRaw.autoheal)
2694
+ ? selfImplRaw.autoheal as Record<string, unknown> : undefined;
2695
+ // ⛔ 모르는 값은 좁은 쪽(shadow) — 오타가 live 를 켜면 안 된다.
2696
+ const autoheal = autohealRaw ? { mode: autohealRaw.mode === 'live' ? 'live' as const : 'shadow' as const } : undefined;
2673
2697
  // ⭐ 관측(리뷰 should-fix) — 오타/비-boolean 은 기본값(ON)으로 수렴하는데, 그게 조용하면
2674
2698
  // "껐다고 믿었는데 자동 개설되는" 운영 사고가 된다. 값이 **있는데 boolean 이 아닐 때만**
2675
2699
  // 경고한다(부재는 정상). 로거 대신 stderr — 이 파일의 기존 경고와 동형(부트 시점·의존 0).
@@ -2686,13 +2710,26 @@ function parseToolsConfig(raw: unknown): ToolsConfig {
2686
2710
  const autoOpenPr = autoOpenPrRaw === false
2687
2711
  ? false
2688
2712
  : TOOLS_DEFAULTS.selfImplement.autoOpenPr;
2713
+ const draftOnStopParsed = parseDraftOnStopMode((selfImplRaw as { draftOnStop?: unknown }).draftOnStop);
2714
+ if (draftOnStopParsed.invalid) {
2715
+ try {
2716
+ process.stderr.write(`[user-config] tools.selfImplement.draftOnStop 값이 알 수 없습니다(${JSON.stringify((selfImplRaw as { draftOnStop?: unknown }).draftOnStop)}) — 'always' 로 진행합니다. 허용값: ${DRAFT_ON_STOP_MODES.join(' | ')}\n`);
2717
+ } catch { /* swallow */ }
2718
+ }
2719
+ const draftOnStop = draftOnStopParsed.mode;
2720
+ const salvageRetentionRaw = (selfImplRaw as { salvageRetention?: unknown }).salvageRetention;
2721
+ const salvageRetentionModeRaw = salvageRetentionRaw && typeof salvageRetentionRaw === 'object' ? (salvageRetentionRaw as { mode?: unknown }).mode : undefined;
2722
+ if (salvageRetentionModeRaw !== undefined && salvageRetentionModeRaw !== 'shadow' && salvageRetentionModeRaw !== 'live') {
2723
+ try { process.stderr.write(`[user-config] tools.selfImplement.salvageRetention.mode 값이 알 수 없습니다(${JSON.stringify(salvageRetentionModeRaw)}) — 'shadow' 로 진행합니다. 허용값: shadow | live\n`); } catch { /* swallow */ }
2724
+ }
2725
+ const salvageRetention = { mode: salvageRetentionModeRaw === 'live' ? 'live' as const : 'shadow' as const };
2689
2726
  return {
2690
2727
  deferred: { mode },
2691
2728
  agentSpawn: { hopCap },
2692
2729
  runDevHarness,
2693
2730
  selfOrchestrate,
2694
2731
  nativeStructure,
2695
- selfImplement: { worktreeRoot, childInstanceMode, prApprovalDelivery, observeOnly, goalRecordInDoc, goalAuthorPersistentGrounding, fabricDecompose, fabricDecomposeAutoPathThreshold, autoOpenPr, autoStop, autoAssist, screenStallTermination, reworkBudget, decompositionShadow, clarificationEscalation, ...(childLlm ? { childLlm } : {}) },
2732
+ selfImplement: { worktreeRoot, childInstanceMode, prApprovalDelivery, observeOnly, goalRecordInDoc, goalAuthorPersistentGrounding, fabricDecompose, fabricDecomposeAutoPathThreshold, autoOpenPr, draftOnStop, salvageRetention, autoStop, autoAssist, screenStallTermination, reworkBudget, decompositionShadow, clarificationEscalation, ...(childLlm ? { childLlm } : {}), ...(runTtlHours !== undefined ? { runTtlHours } : {}), ...(runTtl ? { runTtl } : {}), ...(autoheal ? { autoheal } : {}) },
2696
2733
  };
2697
2734
  }
2698
2735
 
@@ -3491,6 +3528,8 @@ export interface OrchestratorLoopConfig {
3491
3528
  mode: 'off' | 'shadow' | 'live';
3492
3529
  seatTrees: Partial<Record<OrchestratorSeat, string[]>>;
3493
3530
  seatCaps: Record<OrchestratorSeat, number>;
3531
+ /** Max new delegations per tick; absent means no limit. */
3532
+ maxDelegatePerTick?: number;
3494
3533
  /** Per-seat release gate; absent seats retain the spawn-budget default. */
3495
3534
  releaseGate?: Partial<Record<OrchestratorSeat, number>>;
3496
3535
  trafficMode: 'shadow' | 'live';
@@ -3502,6 +3541,9 @@ export interface OrchestratorLoopConfig {
3502
3541
  handToTaskAgent?: 'off' | 'shadow' | 'live';
3503
3542
  /** Demo: hand only this cell id (others keep the seat path). Absent = every delegatable cell. */
3504
3543
  handToTaskAgentCell?: string;
3544
+ /** COORD-HA — the tick CLI probes LLM ⊕ task board for the degradation ladder. Absent = shadow (probe and log only) ·
3545
+ * live = the tick acts on it (L1 rules only · L2 no new assignment) · off = no probe. */
3546
+ degradationSignal?: 'off' | 'shadow' | 'live';
3505
3547
  }
3506
3548
 
3507
3549
  export const ORCHESTRATOR_DEFAULTS: OrchestratorLoopConfig = {
@@ -3527,12 +3569,16 @@ export function parseOrchestratorLoopConfig(raw: unknown): OrchestratorLoopConfi
3527
3569
  if (typeof gate === 'number' && Number.isSafeInteger(gate) && gate >= 0) releaseGate[seat] = gate;
3528
3570
  }
3529
3571
  const mode = value.mode === 'off' || value.mode === 'live' ? value.mode : 'shadow';
3530
- return { mode, seatTrees, seatCaps, ...(Object.keys(releaseGate).length ? { releaseGate } : {}),
3572
+ const maxDelegatePerTick = value.maxDelegatePerTick;
3573
+ return { mode, seatTrees, seatCaps,
3574
+ ...(typeof maxDelegatePerTick === 'number' && Number.isSafeInteger(maxDelegatePerTick) && maxDelegatePerTick >= 0 ? { maxDelegatePerTick } : {}),
3575
+ ...(Object.keys(releaseGate).length ? { releaseGate } : {}),
3531
3576
  trafficMode: value.trafficMode === 'live' ? 'live' : 'shadow',
3532
3577
  idleRequest: value.idleRequest === 'off' || value.idleRequest === 'live' ? value.idleRequest : 'shadow',
3533
3578
  finishGate: value.finishGate === 'off' || value.finishGate === 'on' ? value.finishGate : 'shadow',
3534
3579
  ...(value.handToTaskAgent === 'shadow' || value.handToTaskAgent === 'live' ? { handToTaskAgent: value.handToTaskAgent } : {}),
3535
- ...(typeof value.handToTaskAgentCell === 'string' && value.handToTaskAgentCell.trim() ? { handToTaskAgentCell: value.handToTaskAgentCell.trim() } : {}) };
3580
+ ...(typeof value.handToTaskAgentCell === 'string' && value.handToTaskAgentCell.trim() ? { handToTaskAgentCell: value.handToTaskAgentCell.trim() } : {}),
3581
+ ...(value.degradationSignal === 'off' || value.degradationSignal === 'shadow' || value.degradationSignal === 'live' ? { degradationSignal: value.degradationSignal } : {}) };
3536
3582
  }
3537
3583
 
3538
3584
  export type StewardLoopMode = 'off' | 'shadow' | 'live' | 'rescue';
@@ -3555,6 +3601,8 @@ export interface SeatLoopConfig {
3555
3601
  stall?: { parents?: Record<string, string>; redMinutes?: number; blockedMinutes?: number };
3556
3602
  /** Unlanded attempts of the same seat cell before the loop stops relaunching and asks a person. Absent or invalid means 3. */
3557
3603
  repeatStop?: number;
3604
+ /** OP-LOOP-DECIDE: OP seat filters open decision cards (repeat-stop → b · harness scope → b unless money/security/secret/public paths). shadow records only; live decides/routes. Absent = shadow. */
3605
+ opDecide?: { mode: 'shadow' | 'live' };
3558
3606
  }
3559
3607
 
3560
3608
  export type EventSeat = 'OP' | 'TC' | 'MK' | 'UX';
@@ -3602,6 +3650,8 @@ export interface HqConfig {
3602
3650
  tailscaleBin?: string;
3603
3651
  /** Highest lease generation this host ever saw (default ~/.elanous-hq/seen-generation · 600). */
3604
3652
  seenGenerationFile?: string;
3653
+ /** Board writes only reject stale generations when explicitly enforced (default shadow). */
3654
+ boardFencing?: 'shadow' | 'enforce';
3605
3655
  /** Per-probe timeout in seconds (default 5). */
3606
3656
  probeTimeoutSeconds?: number;
3607
3657
  }
@@ -3636,7 +3686,64 @@ export function parseGateRemoteConfig(raw: unknown): GateRemoteConfig | undefine
3636
3686
  return Object.keys(out).length ? out : undefined;
3637
3687
  }
3638
3688
 
3689
+ export interface ProjectConfig {
3690
+ /** `axes` is absent when the configured axes are invalid; `error` then carries the config error text. */
3691
+ rubric?: { axes?: import('./release-loop/rubric.js').RubricAxis[]; error?: string };
3692
+ }
3693
+
3694
+ /** Validate one project's `rubric.axes` — throws the config error text (key duplicate · non-numeric weight). */
3695
+ export function parseProjectRubricAxes(id: string, axes: unknown): Array<{ key: string; weight: number }> {
3696
+ if (!Array.isArray(axes)) throw new Error(`[user-config] projects.${id}.rubric.axes 는 배열이어야 합니다`);
3697
+ const seen = new Set<string>();
3698
+ const parsed: Array<{ key: string; weight: number }> = [];
3699
+ for (const [index, axis] of axes.entries()) {
3700
+ const field = `projects.${id}.rubric.axes[${index}]`;
3701
+ if (!axis || typeof axis !== 'object' || Array.isArray(axis)) throw new Error(`[user-config] ${field} 는 {key,weight} 객체여야 합니다`);
3702
+ const { key, weight } = axis as Record<string, unknown>;
3703
+ if (typeof key !== 'string' || !key.trim()) throw new Error(`[user-config] ${field}.key 는 빈 문자열일 수 없습니다`);
3704
+ if (seen.has(key)) throw new Error(`[user-config] projects.${id}.rubric.axes 키 중복: ${key}`);
3705
+ if (typeof weight !== 'number' || !Number.isFinite(weight)) throw new Error(`[user-config] ${field}.weight 는 유한한 숫자여야 합니다: ${String(weight)}`);
3706
+ seen.add(key);
3707
+ parsed.push({ key, weight });
3708
+ }
3709
+ return parsed;
3710
+ }
3711
+
3712
+ /**
3713
+ * Project rubric axes (P3b CORE-GENERIC). Loading never throws — one bad project entry must not
3714
+ * stop every command — so an invalid entry keeps its error text and `resolveRubricAxes` raises it.
3715
+ */
3716
+ function parseProjectsConfig(raw: unknown): Record<string, ProjectConfig> | undefined {
3717
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return undefined;
3718
+ const projects: Record<string, ProjectConfig> = {};
3719
+ for (const [id, entry] of Object.entries(raw)) {
3720
+ if (id === '__proto__' || !entry || typeof entry !== 'object' || Array.isArray(entry)) continue;
3721
+ const rubric = (entry as Record<string, unknown>).rubric;
3722
+ if (!rubric || typeof rubric !== 'object' || Array.isArray(rubric)) continue;
3723
+ const axes = (rubric as Record<string, unknown>).axes;
3724
+ if (axes === undefined) continue;
3725
+ try {
3726
+ projects[id] = { rubric: { axes: parseProjectRubricAxes(id, axes) } };
3727
+ } catch (err) {
3728
+ const error = err instanceof Error ? err.message : String(err);
3729
+ console.warn(`${error} — 이 프로젝트의 루브릭 축 해석은 오류로 멈춘다`);
3730
+ projects[id] = { rubric: { error } };
3731
+ }
3732
+ }
3733
+ return Object.keys(projects).length ? projects : undefined;
3734
+ }
3735
+
3639
3736
  export interface UserConfig {
3737
+ projects?: Record<string, ProjectConfig>;
3738
+ /** Default personal topic watch; absent = enabled when user sources exist. */
3739
+ watch?: { enabled?: boolean };
3740
+ starter?: { topics?: string[] };
3741
+ /** GUARD-ONE-a: omitted means shadow. `live` is parsed for the later cutover, but `elanous guardian tick`
3742
+ * refuses it until a human-confirmed operational change (GUARD-ONE-a ships shadow only). */
3743
+ guardian?: { mode?: 'shadow' | 'live' };
3744
+ /** TA-JUDGE-LIVE-SAFE: task-agent judge moves executed for real. Only `review` · `propose-green` are honoured
3745
+ * (`src/task-agent/live-moves.ts` drops anything else with a warning). Absent = env ELANOUS_TASK_AGENT_LIVE_MOVES, else none (pure shadow). */
3746
+ taskAgent?: { liveMoves?: string[] };
3640
3747
  events?: EventsConfig;
3641
3748
  /** GATE-REMOTE — see GateRemoteConfig. Absent = defaults (auto on · node-b · load 20 · cap 2). */
3642
3749
  gateRemote?: GateRemoteConfig;
@@ -3661,7 +3768,7 @@ export interface UserConfig {
3661
3768
  loops?: { owners?: Record<string, EventSeat>; defaultOwner?: EventSeat; orchestrator?: OrchestratorLoopConfig; steward?: { mode?: StewardLoopMode; linearTeam?: string; roles?: Record<string, { maxConcurrent?: number }>; budget?: number; tracks?: Record<string, string>; alertAfterFailures?: number; launch?: 'off' | 'shadow' | 'live'; maxParallel?: number; podPool?: string }; seat?: SeatLoopConfig; persona?: PersonaLoopConfig };
3662
3769
  /** 🔐 Pod 의 Grok API 키 과금은 명시 opt-in 만. 생략·잘못된 값은 false.
3663
3770
  * budgetGate 와 같은 `harness` 객체다 — 중복 선언이면 뒤 타입이 앞을 지운다. */
3664
- harness?: { edgeRail?: { canaryOkRuns?: number; failureMultiplier?: number; minSamples?: number }; /** Goal authoring grade; absent or invalid config resolves to full. */ authorGrade?: 'full' | 'lite'; revertGuard?: { depth: number }; pod?: { grokApiKeyOptIn: boolean }; budgetGate?: HarnessBudgetGateConfig; exposeGate?: 'warn' | 'strict'; difficultyPlacement?: boolean; defaultRepo?: string; /** Optional GitHub repository name (owner/repo), distinct from the local defaultRepo path. */ repo?: string; substrate?: 'local' | 'pod'; podPool?: string; worktreeAddTimeoutSec?: number; queue?: { seatCap?: Partial<Record<'OP' | 'TC' | 'MK' | 'UX', number>>; /** Direct `harness say|ask` goes through the seat queue first (ONEDOOR-2). Absent = env ELANOUS_HARNESS_QUEUE_DIRECT_SAY, else off. */ directSay?: boolean; /** SEAT-CAP-STALE: a run whose last ledger progress is older than this stops holding its seat. Absent = env ELANOUS_HARNESS_QUEUE_STALE_RUN_MINUTES, else 30. */ staleRunMinutes?: number; /** Seat-less runs count in one bucket with this cap instead of every seat. Absent = env ELANOUS_HARNESS_QUEUE_UNKNOWN_SEAT_CAP, else 2. */ unknownSeatCap?: number }; /** `harness say` 문장을 Pod 안에서 저작부터 돌릴지. 생략·false = 끔(호스트가 저작). */ authorOnPod?: boolean; /** Stopped-PR repair goals: shadow records only (default); live enqueues them. */ helper?: { repair?: 'shadow' | 'live'; /** Live launches allowed per UTC day. Absent or invalid means 3. */ repairPerDay?: number };
3771
+ harness?: { edgeRail?: { canaryOkRuns?: number; failureMultiplier?: number; minSamples?: number }; /** Goal authoring grade; absent or invalid config resolves to full. */ authorGrade?: 'full' | 'lite'; revertGuard?: { depth: number }; pod?: { grokApiKeyOptIn: boolean }; budgetGate?: HarnessBudgetGateConfig; exposeGate?: 'warn' | 'strict'; difficultyPlacement?: boolean; defaultRepo?: string; /** Optional GitHub repository name (owner/repo), distinct from the local defaultRepo path. */ repo?: string; substrate?: 'local' | 'pod'; podPool?: string; worktreeAddTimeoutSec?: number; queue?: { seatCap?: Partial<Record<'OP' | 'TC' | 'MK' | 'UX', number>>; /** Direct `harness say|ask` goes through the seat queue first (ONEDOOR-2). Absent = env ELANOUS_HARNESS_QUEUE_DIRECT_SAY, else off. */ directSay?: boolean; /** SEAT-CAP-STALE: a run whose last ledger progress is older than this stops holding its seat. Absent = env ELANOUS_HARNESS_QUEUE_STALE_RUN_MINUTES, else 30. */ staleRunMinutes?: number; /** Seat-less runs count in one bucket with this cap instead of every seat. Absent = env ELANOUS_HARNESS_QUEUE_UNKNOWN_SEAT_CAP, else 2. */ unknownSeatCap?: number; /** QUEUE-BURST: most launches one scheduled tick may make (30 s apart, each within seat cap and Pod pool headroom). Absent or invalid = 5; 1 = one launch per tick. */ burstMax?: number }; /** `harness say` 문장을 Pod 안에서 저작부터 돌릴지. 생략·false = 끔(호스트가 저작). */ authorOnPod?: boolean; /** SIBLING-RESYNC-ON-MERGE: most open sibling PR file inventories fetched per merge. Absent or invalid = 40. */ siblingResync?: { maxFetch?: number }; /** Stopped-PR repair goals: shadow records only (default); live enqueues them. */ helper?: { repair?: 'shadow' | 'live'; /** Live launches allowed per UTC day. Absent or invalid means 3. */ repairPerDay?: number };
3665
3772
  /** Nested elanous launches. Only depth 0 may set `allow`. Absent or any other value refuses. A depth >= 1 `--nested-elanous allow` is ignored. */
3666
3773
  nestedElanous?: 'allow' | 'refuse';
3667
3774
  /** Maximum nested elanous depth; safe positive integer, default 2. */
@@ -3914,6 +4021,36 @@ function parseAutoReviewConfig(raw: unknown): AutoReviewConfig | undefined {
3914
4021
  return Object.keys(out).length > 0 ? out : undefined;
3915
4022
  }
3916
4023
 
4024
+ /** Keeps `taskAgent.liveMoves` as raw strings — the allow-list (and the unknown-entry warning) lives in the task agent. */
4025
+ function parseTaskAgentConfig(raw: unknown): UserConfig['taskAgent'] {
4026
+ if (raw === undefined) return undefined;
4027
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
4028
+ warnUserConfigDrop('taskAgent', '섹션이 객체가 아니다 — 버림');
4029
+ return undefined;
4030
+ }
4031
+ const liveMoves = (raw as Record<string, unknown>).liveMoves;
4032
+ if (liveMoves === undefined) return undefined;
4033
+ // 설정이 «있으면» 환경 변수로 내려가지 않는다 — 배열이 아니면 빈 목록(fail-closed), 문자열 밖 항목은 글자로 바꿔 넘겨
4034
+ // 실행부가 «모르는 항목»으로 경고하고 버리게 한다(유효한 항목은 산다).
4035
+ if (!Array.isArray(liveMoves)) {
4036
+ warnUserConfigDrop('taskAgent.liveMoves', '배열이 아니다 — 빈 목록(실행 0)');
4037
+ return { liveMoves: [] };
4038
+ }
4039
+ return { liveMoves: liveMoves.map((entry) => typeof entry === 'string' ? entry : JSON.stringify(entry) ?? String(entry)) };
4040
+ }
4041
+
4042
+ function parseGuardianConfig(raw: unknown): UserConfig['guardian'] {
4043
+ if (raw === undefined) return undefined;
4044
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
4045
+ warnUserConfigDrop('guardian', '섹션이 객체가 아니다 — 버림');
4046
+ return undefined;
4047
+ }
4048
+ const mode = (raw as Record<string, unknown>).mode;
4049
+ if (mode === 'shadow' || mode === 'live') return { mode };
4050
+ if (mode !== undefined) warnUserConfigDrop('guardian.mode', 'shadow 또는 live 가 아니다 — 버림');
4051
+ return undefined;
4052
+ }
4053
+
3917
4054
  /** Parse optional ops fields independently; invalid settings cannot silently select a cron cwd. */
3918
4055
  function parseOpsConfig(raw: unknown): UserConfig['ops'] | undefined {
3919
4056
  if (raw === undefined) return undefined;
@@ -4588,6 +4725,12 @@ function parseSeatLoopsConfig(input: unknown): SeatLoopConfig {
4588
4725
  })(),
4589
4726
  ...(typeof values.repeatStop === 'number' && Number.isSafeInteger(values.repeatStop) && values.repeatStop > 0
4590
4727
  ? { repeatStop: values.repeatStop } : {}),
4728
+ ...(() => {
4729
+ const raw = values.opDecide;
4730
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return {};
4731
+ const mode = (raw as Record<string, unknown>).mode;
4732
+ return mode === 'shadow' || mode === 'live' ? { opDecide: { mode } } : {};
4733
+ })(),
4591
4734
  };
4592
4735
  }
4593
4736
 
@@ -4635,6 +4778,7 @@ export function parseHqConfig(raw: unknown): HqConfig | undefined {
4635
4778
  }
4636
4779
  if (typeof r.ttlSeconds === 'number' && Number.isFinite(r.ttlSeconds) && r.ttlSeconds > 0) out.ttlSeconds = r.ttlSeconds;
4637
4780
  if (typeof r.probeTimeoutSeconds === 'number' && Number.isFinite(r.probeTimeoutSeconds) && r.probeTimeoutSeconds > 0) out.probeTimeoutSeconds = r.probeTimeoutSeconds;
4781
+ if (r.boardFencing === 'shadow' || r.boardFencing === 'enforce') out.boardFencing = r.boardFencing;
4638
4782
  if (r.autoPromote && typeof r.autoPromote === 'object' && !Array.isArray(r.autoPromote)) {
4639
4783
  const auto = r.autoPromote as Record<string, unknown>;
4640
4784
  const setting: NonNullable<HqConfig['autoPromote']> = {};
@@ -4771,7 +4915,15 @@ export function buildUserConfig(path: string = defaultPath()): UserConfig {
4771
4915
  const nexus = rawObj.nexus && typeof rawObj.nexus === 'object' && !Array.isArray(rawObj.nexus)
4772
4916
  ? rawObj.nexus as Record<string, unknown> : {};
4773
4917
  const gateRemoteConfig = parseGateRemoteConfig(rawObj.gateRemote);
4918
+ const projects = parseProjectsConfig(rawObj.projects);
4774
4919
  return {
4920
+ ...(projects ? { projects } : {}),
4921
+ ...(rawObj.starter && typeof rawObj.starter === 'object' && !Array.isArray(rawObj.starter)
4922
+ && Array.isArray((rawObj.starter as Record<string, unknown>).topics)
4923
+ ? { starter: { topics: ((rawObj.starter as { topics: unknown[] }).topics).filter((topic): topic is string => typeof topic === 'string' && !!topic.trim()) } } : {}),
4924
+ ...(rawObj.watch && typeof rawObj.watch === 'object' && !Array.isArray(rawObj.watch)
4925
+ && typeof (rawObj.watch as Record<string, unknown>).enabled === 'boolean'
4926
+ ? { watch: { enabled: (rawObj.watch as { enabled: boolean }).enabled } } : {}),
4775
4927
  nexus: { demoMode: nexus.demoMode === true },
4776
4928
  ...(Object.keys(seatBudgets).length ? { org: { budget: seatBudgets } } : {}),
4777
4929
  pod: {
@@ -4818,8 +4970,9 @@ export function buildUserConfig(path: string = defaultPath()): UserConfig {
4818
4970
  ...(() => {
4819
4971
  const queue = harness.queue && typeof harness.queue === 'object' && !Array.isArray(harness.queue)
4820
4972
  ? harness.queue as Record<string, unknown> : {};
4821
- const stale = queue.staleRunMinutes, unknownCap = queue.unknownSeatCap;
4973
+ const stale = queue.staleRunMinutes, unknownCap = queue.unknownSeatCap, burstMax = queue.burstMax;
4822
4974
  return {
4975
+ ...(typeof burstMax === 'number' && Number.isSafeInteger(burstMax) && burstMax > 0 ? { burstMax } : {}),
4823
4976
  ...(typeof stale === 'number' && Number.isSafeInteger(stale) && stale > 0 ? { staleRunMinutes: stale } : {}),
4824
4977
  ...(typeof unknownCap === 'number' && Number.isSafeInteger(unknownCap) && unknownCap >= 0 ? { unknownSeatCap: unknownCap } : {}),
4825
4978
  };
@@ -4845,6 +4998,12 @@ export function buildUserConfig(path: string = defaultPath()): UserConfig {
4845
4998
  })(),
4846
4999
  ...(harness.authorGrade === 'lite' || harness.authorGrade === 'full' ? { authorGrade: harness.authorGrade } : {}),
4847
5000
  ...(harness.authorOnPod === true ? { authorOnPod: true } : {}),
5001
+ ...(() => {
5002
+ const sibling = harness.siblingResync && typeof harness.siblingResync === 'object' && !Array.isArray(harness.siblingResync)
5003
+ ? harness.siblingResync as Record<string, unknown> : undefined;
5004
+ const maxFetch = sibling?.maxFetch;
5005
+ return typeof maxFetch === 'number' && Number.isSafeInteger(maxFetch) && maxFetch >= 0 ? { siblingResync: { maxFetch } } : {};
5006
+ })(),
4848
5007
  ...(harness.nestedElanous === 'allow' || harness.nestedElanous === 'refuse'
4849
5008
  ? { nestedElanous: harness.nestedElanous } : {}),
4850
5009
  nestedElanousMaxDepth: typeof harness.nestedElanousMaxDepth === 'number'
@@ -5507,6 +5666,8 @@ export function buildUserConfig(path: string = defaultPath()): UserConfig {
5507
5666
  loops: { ...(stewardLoops ?? {}), ...parseLoopOwners(rawObj.loops), orchestrator: parseOrchestratorLoopConfig((rawObj.loops as { orchestrator?: unknown } | undefined)?.orchestrator), seat: parseSeatLoopsConfig(rawObj.loops),
5508
5667
  persona: { enabled: (rawObj.loops as { persona?: { enabled?: unknown } } | undefined)?.persona?.enabled === true } },
5509
5668
  events: parseEventsConfig(rawObj.events),
5669
+ guardian: parseGuardianConfig(rawObj.guardian),
5670
+ taskAgent: parseTaskAgentConfig(rawObj.taskAgent),
5510
5671
  // M1-1: sparse — undefined when the user hasn't set anything, so
5511
5672
  // resolvers fall through to zero-config defaults.
5512
5673
  ...spreadIfDefined('modelTier', parseModelTierConfig(rawObj.modelTier)),
@@ -6045,6 +6206,21 @@ export function saveUserConfig(
6045
6206
  },
6046
6207
  };
6047
6208
  const rawRest = { ...(cfg.raw ?? {}) };
6209
+ // projects: raw entries are kept verbatim — an invalid entry loaded from disk is preserved, not blocked (its error
6210
+ // surfaces in resolveRubricAxes) — and typed axes handed to save are validated (invalid ones refuse the save).
6211
+ const rawProjectsValue = rawRest.projects;
6212
+ const rawProjects = rawRest.projects && typeof rawRest.projects === 'object' && !Array.isArray(rawRest.projects)
6213
+ ? rawRest.projects as Record<string, unknown> : undefined;
6214
+ const savedProjects: Record<string, unknown> | undefined = rawProjects || cfg.projects ? { ...(rawProjects ?? {}) } : undefined;
6215
+ for (const [id, project] of Object.entries(cfg.projects ?? {})) {
6216
+ if (project?.rubric?.axes === undefined) continue;
6217
+ const axes = parseProjectRubricAxes(id, project.rubric.axes);
6218
+ const rawProject = rawProjects?.[id] && typeof rawProjects[id] === 'object' && !Array.isArray(rawProjects[id])
6219
+ ? rawProjects[id] as Record<string, unknown> : {};
6220
+ const rawRubric = rawProject.rubric && typeof rawProject.rubric === 'object' && !Array.isArray(rawProject.rubric)
6221
+ ? rawProject.rubric as Record<string, unknown> : {};
6222
+ savedProjects![id] = { ...rawProject, rubric: { ...rawRubric, axes } };
6223
+ }
6048
6224
  // Strip typed sections from raw so we don't double-write stale copies.
6049
6225
  delete rawRest.skillRouter;
6050
6226
  delete rawRest.llm;
@@ -6079,6 +6255,10 @@ export function saveUserConfig(
6079
6255
  delete rawRest.smartDefaults;
6080
6256
  delete rawRest.grounding;
6081
6257
  delete rawRest.diagnostics;
6258
+ delete rawRest.projects;
6259
+ delete rawRest.starter;
6260
+ delete rawRest.watch;
6261
+ delete rawRest.guardian;
6082
6262
  const rawHarness = rawRest.harness && typeof rawRest.harness === 'object' && !Array.isArray(rawRest.harness)
6083
6263
  ? rawRest.harness as Record<string, unknown> : {};
6084
6264
  delete rawRest.harness;
@@ -6113,6 +6293,10 @@ export function saveUserConfig(
6113
6293
  helper: cfg.harness?.helper ?? rawHarness.helper,
6114
6294
  }),
6115
6295
  ...rawRest,
6296
+ ...(savedProjects ? { projects: savedProjects } : rawProjectsValue !== undefined ? { projects: rawProjectsValue } : {}),
6297
+ ...(cfg.starter ? { starter: cfg.starter } : {}),
6298
+ ...(cfg.watch ? { watch: cfg.watch } : {}),
6299
+ ...(cfg.guardian ? { guardian: cfg.guardian } : {}),
6116
6300
  ...(cfg.loops ? { loops: {
6117
6301
  ...rawLoops,
6118
6302
  owners: cfg.loops.owners ?? {}, defaultOwner: cfg.loops.defaultOwner ?? 'OP',
@@ -6127,6 +6311,7 @@ export function saveUserConfig(
6127
6311
  ...(cfg.loops.seat.reportPr === undefined ? {} : { reportPr: cfg.loops.seat.reportPr }),
6128
6312
  ...(cfg.loops.seat.stall ? { stall: cfg.loops.seat.stall } : {}),
6129
6313
  ...(cfg.loops.seat.repeatStop === undefined ? {} : { repeatStop: cfg.loops.seat.repeatStop }),
6314
+ ...(cfg.loops.seat.opDecide ? { opDecide: cfg.loops.seat.opDecide } : {}),
6130
6315
  } } : {}),
6131
6316
  } } : {}),
6132
6317
  ...(cfg.autopilot ? { autopilot: {
@@ -1 +1 @@
1
- {"commit":"aaa857963df78dc714502746d99e9ba8bddbae54"}
1
+ {"commit":"047da54ef704187c124a2969c6222d61fe01beb0"}
@@ -1 +0,0 @@
1
- "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[224],{1369:(e,r,t)=>{t.d(r,{QL:()=>p,WelcomeHome:()=>w});var n=t(55539),l=t(46124),a=t(3558),s=t.n(a),i=t(58603),o=t(6535),u=t(44373),c=t(97479),d=t(92161);let f=["/term","/chat","/intake","/live","/editor","/market","/setup"];var m=t(65650),h=t(57147),g=t(48087);let x=[...g.Ue.map(e=>({href:function(e){if(!(g.Ue.some(r=>r.href===e)||g.OE.some(r=>r.href===e)))throw Error("Unknown sidebar route: ".concat(e));return e}(e.href),label:e.label,navigable:!0})),...g.OE.map(e=>({href:e.href,label:e.href,navigable:!1,reason:e.reason}))],b=[...x.filter(e=>e.navigable),{href:"/setup",label:"Setup",navigable:!0}],v=[{href:"/morning",label:"아침 브리핑",navigable:!0},{href:"/settings/devices",label:"기기 목록",navigable:!0},{href:"/workflows/chat-ui",label:"워크플로 채팅",navigable:!0}];function p(e){let{ariaLabel:r,items:t=x}=e;return(0,n.jsx)("section",{"aria-label":r,className:"grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3",children:t.map(e=>{var r;return(0,n.jsx)("article",{"data-testid":(r=e.href,"route-guidance-".concat("/"===r?"root":r.slice(1).replaceAll("/","-"))),"data-route-kind":e.navigable?"destination":"reference",className:(0,u.cn)("rounded-2xl border border-border/60 bg-card/60 p-5 shadow-sm",!e.navigable&&"bg-muted/40"),children:e.navigable?(0,n.jsxs)(s(),{href:e.href,className:"font-semibold underline-offset-4 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",children:[e.label," ",(0,n.jsx)("code",{className:"text-xs text-muted-foreground",children:e.href})]}):(0,n.jsxs)("div",{className:"flex flex-col gap-2",children:[(0,n.jsxs)("span",{className:"font-semibold",children:["참고 주소 ",(0,n.jsx)("code",{className:"text-xs text-muted-foreground",children:e.href})]}),(0,n.jsx)("p",{className:"text-sm leading-relaxed text-muted-foreground",children:e.reason})]})},e.href)})})}function w(){let{role:e}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return null==(0,l.useContext)(o.AppRouterContext)?(0,n.jsx)(N,{role:e}):(0,n.jsx)(j,{role:e})}function j(e){let{role:r}=e,t=(0,i.useRouter)(),a=(0,i.usePathname)(),s=(0,m._r)(),o=null==a||s.isError||s.isPending||s.isLoading?null:(0,h.P)(s.data,a);return(0,l.useEffect)(()=>{"/setup"===o&&t.replace("/setup")},[o,t]),(0,n.jsx)(N,{role:r})}function N(e){var r;let{role:t}=e,l=(0,c.In)(),a=null!=t?t:l,{core:s,more:i}=(r=b.filter(e=>(0,d.EX)(a,e.href)),{core:f.flatMap(e=>r.filter(r=>r.href===e)),more:r.filter(e=>!f.some(r=>r===e.href))});return(0,n.jsx)("main",{"data-testid":"welcome-home",className:"min-h-screen bg-gradient-to-b from-background via-background to-muted/40 px-6 py-12 sm:px-10",children:(0,n.jsxs)("div",{className:"mx-auto flex w-full max-w-5xl flex-col gap-10",children:[(0,n.jsxs)("header",{className:"flex flex-col gap-3",children:[(0,n.jsx)("p",{className:"text-xs font-semibold uppercase tracking-[0.18em] text-muted-foreground",children:"elanous PWA"}),(0,n.jsx)("h1",{className:"text-3xl font-bold leading-tight sm:text-4xl",children:"웰컴 — 어디부터 시작할까요?"}),(0,n.jsx)("p",{className:"max-w-2xl text-sm text-muted-foreground sm:text-base",children:"처음이라면 아래 핵심 화면에서 시작하세요. 다른 화면은 필요할 때 펼쳐 볼 수 있습니다."})]}),(0,n.jsx)(p,{ariaLabel:"핵심 화면",items:s}),i.length>0&&(0,n.jsxs)("details",{className:"rounded-2xl border border-border/60 bg-card/60 p-5",children:[(0,n.jsxs)("summary",{className:"cursor-pointer font-semibold focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",children:["모든 화면 (",i.length,")"]}),(0,n.jsx)("div",{className:"mt-4",children:(0,n.jsx)(p,{ariaLabel:"나머지 화면",items:i})})]}),v.some(e=>(0,d.EX)(a,e.href))&&(0,n.jsxs)("details",{className:"rounded-2xl border border-border/60 bg-card/60 p-5",children:[(0,n.jsx)("summary",{className:"cursor-pointer font-semibold focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",children:"고치는 중인 화면"}),(0,n.jsx)("div",{className:"mt-4",children:(0,n.jsx)(p,{ariaLabel:"고치는 중인 화면",items:v.filter(e=>(0,d.EX)(a,e.href))})})]})]})})}},3704:(e,r,t)=>{t.d(r,{A:()=>u});var n=t(46124);let l=function(){for(var e=arguments.length,r=Array(e),t=0;t<e;t++)r[t]=arguments[t];return r.filter((e,r,t)=>!!e&&""!==e.trim()&&t.indexOf(e)===r).join(" ").trim()},a=e=>{let r=e.replace(/^([A-Z])|[\s-_]+(\w)/g,(e,r,t)=>t?t.toUpperCase():r.toLowerCase());return r.charAt(0).toUpperCase()+r.slice(1)};var s={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};let i=(0,n.createContext)({}),o=(0,n.forwardRef)((e,r)=>{var t,a,o;let{color:u,size:c,strokeWidth:d,absoluteStrokeWidth:f,className:m="",children:h,iconNode:g,...x}=e,{size:b=24,strokeWidth:v=2,absoluteStrokeWidth:p=!1,color:w="currentColor",className:j=""}=null!=(t=(0,n.useContext)(i))?t:{},N=(null!=f?f:p)?24*Number(null!=d?d:v)/Number(null!=c?c:b):null!=d?d:v;return(0,n.createElement)("svg",{ref:r,...s,width:null!=(a=null!=c?c:b)?a:s.width,height:null!=(o=null!=c?c:b)?o:s.height,stroke:null!=u?u:w,strokeWidth:N,className:l("lucide",j,m),...!h&&!(e=>{for(let r in e)if(r.startsWith("aria-")||"role"===r||"title"===r)return!0;return!1})(x)&&{"aria-hidden":"true"},...x},[...g.map(e=>{let[r,t]=e;return(0,n.createElement)(r,t)}),...Array.isArray(h)?h:[h]])}),u=(e,r)=>{let t=(0,n.forwardRef)((t,s)=>{let{className:i,...u}=t;return(0,n.createElement)(o,{ref:s,iconNode:r,className:l("lucide-".concat(a(e).replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase()),"lucide-".concat(e),i),...u})});return t.displayName=a(e),t}},44373:(e,r,t)=>{t.d(r,{cn:()=>a});var n=t(20852),l=t(30984);function a(){for(var e=arguments.length,r=Array(e),t=0;t<e;t++)r[t]=arguments[t];return(0,l.QP)((0,n.$)(r))}},57147:(e,r,t)=>{t.d(r,{C:()=>a,P:()=>l});let n="셋업 모드 — 먼저 LLM 을 정하세요";function l(e,r){return(null==e?void 0:e.setupMode)!==!0||"/"!==r?null:"/setup"}function a(e,r){if((null==e?void 0:e.setupMode)!==!0||"/setup"===r||r.startsWith("/setup/"))return null;let t=e.setupMissing;return t&&t.length>0?"".concat(n," (").concat(t.join(", "),")"):n}},58603:(e,r,t)=>{var n=t(99027);t.o(n,"useParams")&&t.d(r,{useParams:function(){return n.useParams}}),t.o(n,"usePathname")&&t.d(r,{usePathname:function(){return n.usePathname}}),t.o(n,"useRouter")&&t.d(r,{useRouter:function(){return n.useRouter}}),t.o(n,"useSearchParams")&&t.d(r,{useSearchParams:function(){return n.useSearchParams}})},65650:(e,r,t)=>{t.d(r,{_r:()=>a});var n=t(46124),l=t(94306);function a(){let e=(0,l.Qj)(),[r,t]=(0,n.useState)({data:void 0,isError:!1,isPending:!0});return(0,n.useEffect)(()=>{if(!e)return void t({data:void 0,isError:!1,isPending:!0});let r=!0,n=()=>{e.getHealth().then(e=>{r&&t({data:e,isError:!1,isPending:!1})}).catch(()=>{r&&t(e=>({...e,isError:!0,isPending:!1}))})};n();let l=setInterval(n,6e4);return()=>{r=!1,clearInterval(l)}},[e]),{...r,isLoading:r.isPending&&null!==e}}t(65354)},97479:(e,r,t)=>{t.d(r,{GC:()=>l,In:()=>u,OG:()=>i});var n=t(46124);let l="elanous.pwa.role",a="elanous:pwa-role";function s(){try{let e=window.localStorage.getItem(l);if("owner"===e||"contributor"===e||"general"===e)return e}catch(e){}return"general"}function i(e){try{window.localStorage.setItem(l,e)}catch(e){}"function"==typeof window.dispatchEvent&&window.dispatchEvent(new Event(a))}function o(e){if("function"!=typeof window.addEventListener)return()=>{};window.addEventListener(a,e);let r=r=>{(r.key===l||null===r.key)&&e()};return window.addEventListener("storage",r),()=>{window.removeEventListener(a,e),window.removeEventListener("storage",r)}}function u(){return(0,n.useSyncExternalStore)(o,s,()=>"general")}}}]);