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
@@ -18,6 +18,11 @@ import {
18
18
  isHarnessSessionOrigin,
19
19
  type SessionMeta,
20
20
  } from '../../session/index.js';
21
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmdirSync, statSync, writeFileSync } from 'node:fs';
22
+ import { join } from 'node:path';
23
+ import { randomUUID } from 'node:crypto';
24
+ import { getProject } from '../../project/project-store.js';
25
+ import { sessionRoot } from '../../session/index.js';
21
26
  import { checkAuth, type MetaApiOpts } from './meta-api.js';
22
27
  import { jsonResponse } from './http-server.js';
23
28
 
@@ -32,6 +37,7 @@ export interface SessionStoreCard {
32
37
  * `harness` = 하니스 자식이 만든 세션. 기본 목록에서 숨기고 includeHarness=1 로 포함. */
33
38
  origin?: string;
34
39
  sourceKind?: string;
40
+ projectId?: string;
35
41
  createdAt: string;
36
42
  updatedAt: string;
37
43
  messageCount: number;
@@ -50,6 +56,7 @@ function toCard(m: SessionMeta, preview: string, nowMs: number): SessionStoreCar
50
56
  source: m.source,
51
57
  ...(m.origin ? { origin: m.origin } : {}),
52
58
  ...(m.sourceKind ? { sourceKind: m.sourceKind } : {}),
59
+ ...(m.projectId !== undefined ? { projectId: m.projectId } : {}),
53
60
  createdAt: m.createdAt,
54
61
  updatedAt: m.updatedAt,
55
62
  messageCount: m.messageCount,
@@ -82,11 +89,13 @@ export function handleSessionsStoreList(req: Request, opts: MetaApiOpts): Respon
82
89
  // 하니스 자식 세션(origin:'harness')은 기본 숨김 — includeHarness=1 로 포함.
83
90
  // 신분이 없는 옛 세션은 사람 대화로 본다(숨기지 않음). 제목 문면으로 판정하지 않는다.
84
91
  const includeHarness = url.searchParams.get('includeHarness') === '1';
92
+ const projectId = url.searchParams.get('projectId');
85
93
  const limit = Math.min(Math.max(Number(url.searchParams.get('limit')) || 100, 1), 300);
86
94
  const nowMs = Date.now();
87
95
 
88
96
  let metas = listSessions({
89
97
  limit: 600,
98
+ ...(projectId !== null ? { projectId } : {}),
90
99
  ...(includeOperational ? {} : { excludeSourceKinds: ['scheduled'] }),
91
100
  ...(includeHarness ? {} : { excludeOrigins: [HARNESS_SESSION_ORIGIN] }),
92
101
  }); // 이미 updatedAt DESC
@@ -157,6 +166,60 @@ export async function handleSessionsStoreFork(
157
166
  );
158
167
  }
159
168
 
169
+ /** PATCH /v1/sessions/store/:id — only project membership; leave transcript and other meta untouched. */
170
+ export async function handleSessionsStorePatch(req: Request, id: string, opts: MetaApiOpts): Promise<Response> {
171
+ if (!checkAuth(req, opts)) return jsonResponse({ error: 'unauthorized' }, 401);
172
+ if (!id || id.includes('/') || id.includes('\\') || id.includes('..')) {
173
+ return jsonResponse({ ok: false, error: 'invalid_session_id' }, 400);
174
+ }
175
+ let body: unknown;
176
+ try { body = await req.json(); }
177
+ catch { return jsonResponse({ ok: false, error: 'invalid_json' }, 400); }
178
+ if (!body || typeof body !== 'object' || Array.isArray(body)
179
+ || !Object.hasOwn(body, 'projectId')
180
+ || Object.keys(body).some((key) => key !== 'projectId')) {
181
+ return jsonResponse({ ok: false, error: 'invalid_project_id' }, 400);
182
+ }
183
+ const projectId = (body as { projectId: unknown }).projectId;
184
+ if (projectId !== null && (typeof projectId !== 'string' || !getProject(projectId))) {
185
+ return jsonResponse({ ok: false, error: 'invalid_project_id' }, 400);
186
+ }
187
+
188
+ const root = sessionRoot();
189
+ const lock = join(root, '.index.lock');
190
+ const deadline = Date.now() + 10_000;
191
+ for (;;) {
192
+ try { mkdirSync(lock); break; }
193
+ catch {
194
+ if (!existsSync(lock)) continue;
195
+ try {
196
+ if (Date.now() - statSync(lock).mtimeMs > 30_000) { rmdirSync(lock); continue; }
197
+ } catch { continue; }
198
+ if (Date.now() >= deadline) throw new Error(`timed out acquiring lock ${lock}`);
199
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
200
+ }
201
+ }
202
+ try {
203
+ const path = join(root, 'index.json');
204
+ const entries = JSON.parse(readFileSync(path, 'utf8')) as SessionMeta[];
205
+ const pos = entries.findIndex((meta) => meta.id === id);
206
+ if (pos < 0) return jsonResponse({ ok: false, error: 'not_found' }, 404);
207
+ const meta = { ...entries[pos] };
208
+ if (projectId === null) delete meta.projectId;
209
+ else meta.projectId = projectId;
210
+ entries[pos] = meta;
211
+ const temp = `${path}.${process.pid}.${randomUUID().slice(0, 8)}.tmp`;
212
+ writeFileSync(temp, JSON.stringify(entries, null, 2) + '\n', 'utf8');
213
+ renameSync(temp, path);
214
+ return jsonResponse({ ok: true, meta }, 200);
215
+ } catch (error) {
216
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') return jsonResponse({ ok: false, error: 'not_found' }, 404);
217
+ throw error;
218
+ } finally {
219
+ rmdirSync(lock);
220
+ }
221
+ }
222
+
160
223
  /** DELETE /v1/sessions/store/:id — on-disk 세션 삭제(index.json + <id>.jsonl 제거).
161
224
  * 파괴적·복구불가. in-flight turn abort + in-memory history forget 도 fail-soft 로 동반. */
162
225
  export function handleSessionsStoreDelete(req: Request, id: string, opts: MetaApiOpts): Response {
@@ -0,0 +1,160 @@
1
+ import { debug } from '../../debug/log.js';
2
+ import { loginWithCodexBrowser } from '../../oauth/codex-browser-login.js';
3
+ import {
4
+ CODEX_DEVICE_LOGIN_URL,
5
+ exchangeDeviceAuthCode,
6
+ pollDeviceCode,
7
+ requestDeviceCode,
8
+ } from '../../oauth/codex.js';
9
+ import { expiresAtFromSeconds, saveTokens } from '../../oauth/store.js';
10
+ import { getUserConfig, reloadUserConfig, saveUserConfig } from '../../user-config.js';
11
+
12
+ type Mode = 'browser' | 'device';
13
+ type CodexLoginStatus =
14
+ | { state: 'idle' }
15
+ | { state: 'pending'; mode: Mode; authorizeUrl?: string; userCode?: string; verificationUrl?: string }
16
+ | { state: 'ok'; mode: Mode }
17
+ | { state: 'error'; mode: Mode; error: string };
18
+
19
+ const TIMEOUT_MS = 5 * 60_000;
20
+ const SAFE_ERRORS = [
21
+ 'timeout', 'authorization-denied', 'browser-unavailable', 'port-unavailable',
22
+ 'token-exchange', 'device-code-failed', 'device-poll-failed', 'config-save-failed',
23
+ ] as const;
24
+
25
+ type Failure = typeof SAFE_ERRORS[number];
26
+ function reason(error: unknown, mode: Mode): Failure {
27
+ const message = error instanceof Error ? error.message : '';
28
+ for (const value of SAFE_ERRORS) if (message === value || message === `codex browser login failed: ${value}`) return value;
29
+ return mode === 'browser' ? 'browser-unavailable' : 'device-poll-failed';
30
+ }
31
+
32
+ interface LoginDeps {
33
+ browser: (openBrowser: (url: string) => boolean) => Promise<unknown>;
34
+ device: (ready: (userCode: string, verificationUrl: string) => void) => Promise<unknown>;
35
+ saveProvider: () => void;
36
+ observe: (event: 'started' | 'ok' | 'error', mode: Mode) => void;
37
+ }
38
+
39
+ async function deviceLogin(ready: (userCode: string, verificationUrl: string) => void): Promise<void> {
40
+ const controller = new AbortController();
41
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
42
+ const fetchImpl = ((input, init) => fetch(input, { ...init, signal: controller.signal })) as typeof fetch;
43
+ try {
44
+ let code: Awaited<ReturnType<typeof requestDeviceCode>>;
45
+ try { code = await requestDeviceCode({ fetchImpl }); }
46
+ catch { throw new Error('device-code-failed'); }
47
+ if (controller.signal.aborted) throw new Error('timeout');
48
+ ready(code.user_code, code.verification_uri || CODEX_DEVICE_LOGIN_URL);
49
+ const interval = Math.max(3000, (code.interval ?? 0) * 1000);
50
+ const deadline = Date.now() + TIMEOUT_MS;
51
+ while (!controller.signal.aborted && Date.now() < deadline) {
52
+ await new Promise<void>((resolve) => setTimeout(resolve, Math.min(interval, Math.max(0, deadline - Date.now()))));
53
+ if (controller.signal.aborted || Date.now() >= deadline) break;
54
+ let result: Awaited<ReturnType<typeof pollDeviceCode>>;
55
+ try { result = await pollDeviceCode(code.device_auth_id, code.user_code, { fetchImpl }); }
56
+ catch { throw new Error('device-poll-failed'); }
57
+ if (!result) continue;
58
+ let token: Awaited<ReturnType<typeof exchangeDeviceAuthCode>>;
59
+ try { token = await exchangeDeviceAuthCode(result.authorization_code, result.code_verifier, { fetchImpl }); }
60
+ catch { throw new Error('token-exchange'); }
61
+ if (controller.signal.aborted) break;
62
+ saveTokens('openai-codex', {
63
+ accessToken: token.access_token,
64
+ refreshToken: token.refresh_token,
65
+ ...(token.id_token ? { idToken: token.id_token } : {}),
66
+ expiresAt: expiresAtFromSeconds(token.expires_in),
67
+ scope: token.scope,
68
+ tokenType: token.token_type ?? 'Bearer',
69
+ }, { authMode: 'chatgpt' });
70
+ return;
71
+ }
72
+ throw new Error('timeout');
73
+ } catch (error) {
74
+ if (controller.signal.aborted) throw new Error('timeout');
75
+ throw error;
76
+ } finally {
77
+ clearTimeout(timer);
78
+ }
79
+ }
80
+
81
+ export function saveCodexProvider(): void {
82
+ const cur = getUserConfig();
83
+ saveUserConfig({ ...cur, llm: { ...cur.llm, provider: 'openai-codex', apiKey: undefined, baseUrl: undefined, model: undefined } });
84
+ reloadUserConfig();
85
+ }
86
+
87
+ const defaultDeps: LoginDeps = {
88
+ browser: (openBrowser) => loginWithCodexBrowser({ openBrowser, timeoutMs: TIMEOUT_MS }),
89
+ device: deviceLogin,
90
+ saveProvider: saveCodexProvider,
91
+ observe: (event, mode) => { debug.log('setup.codex-login', event, { mode }); },
92
+ };
93
+
94
+ function json(body: CodexLoginStatus | { error: string }, status = 200): Response {
95
+ return new Response(JSON.stringify(body), {
96
+ status, headers: { 'content-type': 'application/json; charset=utf-8', 'access-control-allow-origin': '*' },
97
+ });
98
+ }
99
+
100
+ /** One in-process login survives leaving or reloading the setup page. */
101
+ export function createCodexLoginHandlers(deps: LoginDeps = defaultDeps) {
102
+ let status: CodexLoginStatus = { state: 'idle' };
103
+ let preparing: Promise<void> | undefined;
104
+ let generation = 0;
105
+
106
+ const get = (): Response => json(status);
107
+
108
+ const post = async (req: Request): Promise<Response> => {
109
+ let body: unknown;
110
+ try { body = await req.json(); } catch { return json({ error: 'invalid-json' }, 400); }
111
+ const mode = body && typeof body === 'object' && 'mode' in body ? body.mode : undefined;
112
+ if (mode !== 'browser' && mode !== 'device') return json({ error: 'invalid-mode' }, 400);
113
+ // Same method again → the login already in flight. A different method → start over; the older
114
+ // attempt's result is ignored (generation). Browser → device is the recovery path when a popup
115
+ // was blocked; device needs no local port, so it never collides with the browser's callback server.
116
+ if (status.state === 'pending' && status.mode === mode) {
117
+ if (preparing) await preparing;
118
+ return json(status);
119
+ }
120
+
121
+ status = { state: 'pending', mode };
122
+ const attempt = ++generation;
123
+ deps.observe('started', mode);
124
+ let resolveReady!: () => void;
125
+ preparing = new Promise<void>((resolve) => { resolveReady = resolve; });
126
+ const ready = (fields: Pick<Extract<CodexLoginStatus, { state: 'pending' }>, 'authorizeUrl' | 'userCode' | 'verificationUrl'>) => {
127
+ if (generation === attempt && status.state === 'pending' && status.mode === mode) {
128
+ status = { ...status, ...fields };
129
+ resolveReady();
130
+ }
131
+ };
132
+ void (async () => {
133
+ try {
134
+ if (mode === 'browser') {
135
+ await deps.browser((url) => { ready({ authorizeUrl: url }); return true; });
136
+ } else {
137
+ await deps.device((userCode, verificationUrl) => ready({ userCode, verificationUrl }));
138
+ }
139
+ if (generation !== attempt) return;
140
+ try { deps.saveProvider(); } catch { throw new Error('config-save-failed'); }
141
+ status = { state: 'ok', mode };
142
+ deps.observe('ok', mode);
143
+ } catch (error) {
144
+ if (generation !== attempt) return;
145
+ status = { state: 'error', mode, error: reason(error, mode) };
146
+ deps.observe('error', mode);
147
+ } finally {
148
+ resolveReady();
149
+ if (generation === attempt) preparing = undefined;
150
+ }
151
+ })();
152
+ await preparing;
153
+ return json(status);
154
+ };
155
+ return { get, post };
156
+ }
157
+
158
+ const login = createCodexLoginHandlers();
159
+ export const handleSetupCodexLoginGet = login.get;
160
+ export const handleSetupCodexLoginPost = login.post;
@@ -0,0 +1,102 @@
1
+ import { collectUnifiedUsage } from '../../budget/unified-usage.js';
2
+ import type { UnifiedUsageReport } from '../../budget/types.js';
3
+ import { jsonResponse } from './json-response.js';
4
+
5
+ const CACHE_MS = 30_000;
6
+
7
+ // TC 조건(10-03 01:21): 계정 이름·이메일·토큰은 응답에 «하나도» 싣지 않는다 — 공급자·사용률·리셋만.
8
+ // 계정이 여럿이면 공급자 안 순번(«계정 1»)으로만 가른다(이름에서 비밀을 걸러 내는 방식은 쓰지 않는다).
9
+ function accountLabel(index: number, count: number): string {
10
+ return count > 1 ? `계정 ${index + 1}` : '계정';
11
+ }
12
+
13
+ const SAFE_REASONS = new Set(['query-does-not-supply', 'not-a-subscription']);
14
+ function safeReason(reason: unknown): string {
15
+ return typeof reason === 'string' && SAFE_REASONS.has(reason) ? reason : 'unknown';
16
+ }
17
+
18
+ function periodType(value: string | null): string | null {
19
+ return value && /^(?:session|weekly|monthly|daily|prepaid-usd|USAGE_PERIOD_TYPE_(?:SESSION|WEEKLY|MONTHLY|DAILY))$/.test(value) ? value : null;
20
+ }
21
+
22
+ function isoDate(value: string | null): string | null {
23
+ if (!value) return null;
24
+ const epoch = Date.parse(value);
25
+ return Number.isFinite(epoch) && epoch > 0 ? new Date(epoch).toISOString() : null;
26
+ }
27
+
28
+ function resetsInMs(resetsAt: number, nowMs: number): number | null {
29
+ return Number.isFinite(resetsAt) && resetsAt > 0 ? Math.max(0, resetsAt - nowMs) : null;
30
+ }
31
+
32
+ function projectUsage(report: UnifiedUsageReport, nowMs: number) {
33
+ return {
34
+ ok: true as const,
35
+ accountCounts: {
36
+ codex: report.accountCounts.codex,
37
+ grok: report.accountCounts.grok,
38
+ openrouter: report.accountCounts.openrouter,
39
+ },
40
+ rows: report.rows.map((row, _i, rows) => ({
41
+ provider: row.provider,
42
+ account: accountLabel(rows.filter((r) => r.provider === row.provider).indexOf(row), rows.filter((r) => r.provider === row.provider).length),
43
+ accountCount: row.accountCount,
44
+ soleAccount: row.soleAccount,
45
+ credits: row.credits.status === 'ok' ? {
46
+ status: 'ok' as const,
47
+ usedPercent: row.credits.usedPercent,
48
+ periodType: periodType(row.credits.periodType),
49
+ periodStart: isoDate(row.credits.periodStart),
50
+ periodEnd: isoDate(row.credits.periodEnd),
51
+ resetsInMs: resetsInMs(row.credits.periodEnd ? Date.parse(row.credits.periodEnd) : 0, nowMs),
52
+ monthlyLimit: row.credits.monthlyLimit,
53
+ used: row.credits.used,
54
+ onDemandCap: row.credits.onDemandCap,
55
+ onDemandUsed: row.credits.onDemandUsed,
56
+ prepaidBalance: row.credits.prepaidBalance,
57
+ balance: row.credits.balance,
58
+ hasCredits: row.credits.hasCredits,
59
+ unlimited: row.credits.unlimited,
60
+ } : { status: row.credits.status },
61
+ subscription: row.subscription.status === 'available' ? {
62
+ status: 'available' as const,
63
+ remainingPercent: row.subscription.remainingPercent,
64
+ resetsAt: row.subscription.resetsAt,
65
+ resetsInMs: resetsInMs(row.subscription.resetsAt, nowMs),
66
+ windowKind: row.subscription.windowKind,
67
+ } : { status: 'unavailable' as const, reason: safeReason(row.subscription.reason) },
68
+ resetCredits: row.resetCredits.status === 'available' || row.resetCredits.status === 'expiring-soon' || row.resetCredits.status === 'expired' ? {
69
+ status: row.resetCredits.status,
70
+ expiresAt: isoDate(row.resetCredits.expiresAt),
71
+ hasUnknownExpiry: row.resetCredits.hasUnknownExpiry,
72
+ } : { status: row.resetCredits.status },
73
+ })),
74
+ };
75
+ }
76
+
77
+ /** A per-daemon 30-second snapshot; concurrent requests share the same collection. */
78
+ export function createUsageHandler(
79
+ collect: () => Promise<UnifiedUsageReport> = collectUnifiedUsage,
80
+ now: () => number = Date.now,
81
+ ): () => Promise<Response> {
82
+ let cached: ReturnType<typeof projectUsage> | undefined;
83
+ let cachedAt = 0;
84
+ let pending: Promise<ReturnType<typeof projectUsage>> | undefined;
85
+ return async () => {
86
+ if (cached && now() - cachedAt < CACHE_MS) return jsonResponse(cached);
87
+ pending ??= Promise.resolve().then(collect).then((report) => {
88
+ const value = projectUsage(report, now());
89
+ cached = value;
90
+ cachedAt = now();
91
+ return value;
92
+ }).finally(() => { pending = undefined; });
93
+ try {
94
+ return jsonResponse(await pending);
95
+ } catch {
96
+ // Upstream exceptions can contain credentials; never echo their messages.
97
+ return jsonResponse({ ok: false, reason: 'usage-collection-failed' });
98
+ }
99
+ };
100
+ }
101
+
102
+ export const handleUsageGet = createUsageHandler();
@@ -15,7 +15,10 @@
15
15
  // In PR α `elanous nexus` is opt-in only; `elanous` continues to launch
16
16
  // the legacy dashboard.
17
17
 
18
+ import { getElanousConfigDir } from '../elanous-config-dir.js';
18
19
  import { hostname } from 'node:os';
20
+ import { request as httpRequest } from 'node:http';
21
+ import { isIP } from 'node:net';
19
22
  import { resolveToolCwd } from '../boot/tool-cwd.js';
20
23
  import { acquireNexusLock, NexusLockError, readNexusLock, findNexusLifecycleState } from './supervisor/lock.js';
21
24
  import { classifyNexusStatus, type NexusHttpHealth } from './status-line.js';
@@ -1267,6 +1270,7 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
1267
1270
  let coordinatorPushWatcher: { stop(): void } | undefined;
1268
1271
  // V2.2-3 (2026-05-12) — NEXUS-hosted Discord workflow trigger bot.
1269
1272
  // Wired below once the workflow daemon is constructed.
1273
+ let webPushDecisionTimer: ReturnType<typeof setInterval> | undefined;
1270
1274
  let workflowDiscordBot: NexusDiscordTriggerBotHandle | undefined;
1271
1275
  // V2.2-4 (2026-05-12) — NEXUS-hosted Telegram workflow trigger bot.
1272
1276
  let workflowTelegramBot: NexusTelegramTriggerBotHandle | undefined;
@@ -2108,7 +2112,9 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
2108
2112
  },
2109
2113
  } : {}),
2110
2114
  voiceAdapter,
2111
- ...buildNexusWsBridgeAuth(bearerToken),
2115
+ // The verifier reads the token envelope from the same config dir the boot token was written to
2116
+ // (ensureAuthToken) — default ~/.elanous would make an isolated daemon check the production envelope.
2117
+ ...buildNexusWsBridgeAuth(bearerToken, { configDir: getElanousConfigDir() }),
2112
2118
  };
2113
2119
 
2114
2120
  metaApiOpts = {
@@ -2729,6 +2735,28 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
2729
2735
  console.warn(`[nexus] workflow daemon boot skipped: ${msg}`);
2730
2736
  }
2731
2737
 
2738
+ // Decision cards use the same ledger and first-start baseline as the messaging transports.
2739
+ if ((getUserConfig().raw?.decisions as { cards?: unknown } | undefined)?.cards !== false && !isAutonomousRunContext()) {
2740
+ try {
2741
+ const { DecisionCardService } = await import('../decisions/decision-cards.js');
2742
+ const { webPushDecisionTransport } = await import('../decisions/web-push-decision-cards.js');
2743
+ const { listSubscriptions } = await import('../web-push/subscriptions.js');
2744
+ const { DecisionLedger } = await import('../decisions/decision-ledger.js');
2745
+ const ledger = new DecisionLedger();
2746
+ const service = new DecisionCardService({ transport: webPushDecisionTransport(ledger), ownerIds: [], ledger });
2747
+ await service.tick();
2748
+ webPushDecisionTimer = setInterval(() => {
2749
+ void service.tick().catch((error: unknown) => {
2750
+ debug.log('decisions.webpush', 'tick-failed', { reason: error instanceof Error ? error.message.slice(0, 80) : 'unknown' }, { level: 'warn' });
2751
+ });
2752
+ }, 20_000);
2753
+ webPushDecisionTimer.unref();
2754
+ debug.log('decisions.webpush', 'attached', { subscribers: listSubscriptions().length });
2755
+ } catch (error) {
2756
+ debug.log('decisions.webpush', 'attach-failed', { reason: error instanceof Error ? error.message.slice(0, 80) : 'unknown' }, { level: 'warn' });
2757
+ }
2758
+ }
2759
+
2732
2760
  // W7-후속 (2026-05-12) — Outbound substrate boot. Builds the
2733
2761
  // OutboundRouter + iOS push channel + token store from user-config
2734
2762
  // `notifications.apns.*`. The token store is passed to the http
@@ -3339,6 +3367,7 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
3339
3367
  console.warn(`[nexus] continuation scheduler stop failed: ${(err as Error).message}`);
3340
3368
  }
3341
3369
  }
3370
+ if (webPushDecisionTimer) { clearInterval(webPushDecisionTimer); webPushDecisionTimer = undefined; }
3342
3371
  // R5 — stop the dig-goal armer poller (no-op when never armed).
3343
3372
  if (digArmerHandle) { clearInterval(digArmerHandle); digArmerHandle = undefined; }
3344
3373
  if (replayArmerHandle) { clearInterval(replayArmerHandle); replayArmerHandle = undefined; }
@@ -3876,9 +3905,21 @@ async function probeNexusStatusHealth(runtime: NexusRuntimeMeta | null): Promise
3876
3905
  const healthUrl = new URL('http://localhost/v1/health');
3877
3906
  healthUrl.hostname = host.includes(':') && !host.startsWith('[') ? `[${host}]` : host;
3878
3907
  healthUrl.port = String(runtime.httpPort);
3879
- const response = await fetch(healthUrl.href, {
3880
- signal: AbortSignal.timeout(NEXUS_STATUS_HEALTH_TIMEOUT_MS),
3881
- });
3908
+ const signal = AbortSignal.timeout(NEXUS_STATUS_HEALTH_TIMEOUT_MS);
3909
+ if (healthUrl.hostname === 'localhost' || healthUrl.hostname === '[::1]'
3910
+ || (isIP(healthUrl.hostname) === 4 && healthUrl.hostname.startsWith('127.'))) {
3911
+ // Bun fetch inherits HTTP_PROXY even for loopback; node:http opens a direct socket.
3912
+ const ok = await new Promise<boolean>((resolve, reject) => {
3913
+ const req = httpRequest(healthUrl, { signal }, (response) => {
3914
+ response.resume();
3915
+ resolve(response.statusCode !== undefined && response.statusCode >= 200 && response.statusCode < 300);
3916
+ });
3917
+ req.on('error', reject);
3918
+ req.end();
3919
+ });
3920
+ return ok ? 'responsive' : 'silent';
3921
+ }
3922
+ const response = await fetch(healthUrl.href, { signal });
3882
3923
  return response.ok ? 'responsive' : 'silent';
3883
3924
  } catch {
3884
3925
  // The request could not complete, so retain unknown rather than infer listener silence.
@@ -0,0 +1,89 @@
1
+ import * as readline from 'node:readline/promises';
2
+ import { detectProviders, type DetectedProvider } from '../llm/provider-detect.js';
3
+ import { debug } from '../debug/log.js';
4
+ import { resolveProviderCredential } from '../llm/provider-credentials.js';
5
+ import {
6
+ getUserConfig, saveUserConfig, userConfigPath,
7
+ type LLMProviderName, type UserConfig,
8
+ } from '../user-config.js';
9
+
10
+ export interface FirstRunDeps {
11
+ config?: UserConfig;
12
+ path?: string;
13
+ detectProviders?: () => Promise<DetectedProvider[]>;
14
+ isTTY?: boolean;
15
+ chooseProvider?: () => Promise<LLMProviderName | null>;
16
+ print?: (message: string) => void;
17
+ saveConfig?: (config: UserConfig, path: string) => void;
18
+ }
19
+
20
+ async function chooseProvider(): Promise<LLMProviderName | null> {
21
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
22
+ try {
23
+ const answer = (await rl.question('Choose LLM (1 Codex, 2 Grok, 3 Claude; Enter to cancel): ')).trim();
24
+ return ({ '1': 'openai-codex', '2': 'grok', '3': 'anthropic' } as Record<string, LLMProviderName>)[answer] ?? null;
25
+ } finally {
26
+ rl.close();
27
+ }
28
+ }
29
+
30
+ export async function runFirstRun(deps: FirstRunDeps = {}): Promise<{
31
+ outcome: 'ready' | 'needs-llm' | 'cancelled'; provider?: string; inputs: number;
32
+ }> {
33
+ const path = deps.path ?? userConfigPath();
34
+ const cfg = deps.config ?? getUserConfig(path);
35
+ const print = deps.print ?? console.log;
36
+ let detected: DetectedProvider[] = [];
37
+ try { detected = await (deps.detectProviders ?? detectProviders)(); }
38
+ catch { /* Detection is advisory; a TTY can still choose. */ }
39
+ const subscriptions = detected.filter((d) => d.available && (d.auth === 'oauth' || d.auth === 'agent-cli'));
40
+ const providers = subscriptions.map((d) => d.provider);
41
+ const observe = (event: 'detected' | 'chosen' | 'ready' | 'needs-llm', inputs: number) =>
42
+ debug.log('onboarding.first-run', event, { providers, inputs });
43
+ observe('detected', 0);
44
+ // An incomplete onboarding marker does not mean the user's LLM is unconfigured.
45
+ // Keep an already usable provider, key and model rather than replacing them with a detected subscription.
46
+ const existing = cfg.llm.provider !== 'auto'
47
+ ? resolveProviderCredential({ provider: cfg.llm.provider, rotation: cfg.llm.rotation,
48
+ baseProvider: cfg.llm.provider, baseApiKey: cfg.llm.apiKey })
49
+ : undefined;
50
+ const configured = existing && existing.source !== 'none'
51
+ ? cfg.llm.provider : undefined;
52
+ const picked = (['openai-codex', 'grok', 'claude-code', 'anthropic', 'gemini', 'openai', 'openrouter'] as const)
53
+ .find((name) => providers.includes(name));
54
+ let provider: LLMProviderName | null | undefined = configured ??
55
+ (picked === 'claude-code' ? 'anthropic' : picked as LLMProviderName | undefined);
56
+ let inputs = 0;
57
+ if (!provider) {
58
+ if ((deps.isTTY ?? process.stdin.isTTY) !== true) {
59
+ print('eln setup llm 으로 고르세요');
60
+ observe('needs-llm', 0);
61
+ return { outcome: 'needs-llm', inputs: 0 };
62
+ }
63
+ inputs = 1;
64
+ provider = await (deps.chooseProvider ?? chooseProvider)();
65
+ if (!provider) return { outcome: 'cancelled', inputs };
66
+ observe('chosen', inputs);
67
+ }
68
+ const credential = inputs === 1 || provider !== cfg.llm.provider
69
+ ? resolveProviderCredential({
70
+ provider, rotation: cfg.llm.rotation,
71
+ baseProvider: cfg.llm.provider, baseApiKey: cfg.llm.apiKey,
72
+ })
73
+ : undefined;
74
+ if (inputs === 1 && credential?.source === 'none') {
75
+ print('eln setup llm 으로 고르세요');
76
+ observe('needs-llm', inputs);
77
+ return { outcome: 'needs-llm', inputs };
78
+ }
79
+ (deps.saveConfig ?? saveUserConfig)({
80
+ ...cfg,
81
+ llm: { ...cfg.llm, provider,
82
+ ...(provider !== cfg.llm.provider ? { apiKey: credential?.apiKey, baseUrl: credential?.baseUrl, model: undefined } : {}),
83
+ ...(credential?.apiKey ? { apiKey: credential.apiKey } : {}),
84
+ ...(credential?.baseUrl ? { baseUrl: credential.baseUrl } : {}) },
85
+ onboarding: { ...cfg.onboarding, ready: true },
86
+ }, path);
87
+ observe('ready', inputs);
88
+ return { outcome: 'ready', provider, inputs };
89
+ }
@@ -0,0 +1,45 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { networkInterfaces } from 'node:os';
3
+
4
+ export function isPrivateIpv4(addr: string): boolean {
5
+ const parts = addr.split('.');
6
+ if (parts.length !== 4 || parts.some(part => !/^(0|[1-9]\d{0,2})$/.test(part) || Number(part) > 255)) return false;
7
+ const [a, b] = parts.map(Number);
8
+ return a === 10 || (a === 172 && b! >= 16 && b! <= 31) || (a === 192 && b === 168);
9
+ }
10
+
11
+ export function defaultRouteInterface(platform: NodeJS.Platform = process.platform): string | undefined {
12
+ try {
13
+ if (platform === 'darwin') {
14
+ return /^\s*interface:\s*(\S+)/m.exec(execFileSync('route', ['-n', 'get', 'default'], { encoding: 'utf8', timeout: 1000, stdio: ['ignore', 'pipe', 'ignore'] }))?.[1];
15
+ }
16
+ if (platform === 'linux') {
17
+ return /^default\s+.*?\bdev\s+(\S+)/m.exec(execFileSync('ip', ['route', 'show', 'default'], { encoding: 'utf8', timeout: 1000, stdio: ['ignore', 'pipe', 'ignore'] }))?.[1];
18
+ }
19
+ } catch { /* Route lookup is optional. */ }
20
+ return undefined;
21
+ }
22
+
23
+ export function pickLanAddress({ interfaces, defaultRouteInterface: route }: {
24
+ interfaces: ReturnType<typeof networkInterfaces>;
25
+ defaultRouteInterface?: string | (() => string | undefined);
26
+ }): string | null {
27
+ const candidates: { name: string; address: string }[] = [];
28
+ for (const [name, addresses] of Object.entries(interfaces)) {
29
+ for (const info of addresses ?? []) {
30
+ if (info.family === 'IPv4' && !info.internal && isPrivateIpv4(info.address)) {
31
+ candidates.push({ name, address: info.address });
32
+ }
33
+ }
34
+ }
35
+ if (candidates.length === 0) return null;
36
+ let preferred: string | undefined;
37
+ try { preferred = typeof route === 'function' ? route() : route ?? defaultRouteInterface(); } catch { /* Use sorted candidates. */ }
38
+ candidates.sort((a, b) => a.name.localeCompare(b.name) || a.address.localeCompare(b.address));
39
+ return (candidates.find(candidate => candidate.name === preferred) ?? candidates[0])?.address ?? null;
40
+ }
41
+
42
+ export function isLanReachableBind(httpHost: string | undefined): boolean {
43
+ const host = httpHost?.trim() ?? '';
44
+ return host === '' || host === '0.0.0.0' || host === '::' || host === '[::]' || isPrivateIpv4(host);
45
+ }