elanous 0.2.8 → 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 (385) 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-d4b6df905f185055.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-186fc6952f027acc.js → page-f8116559c7424da7.js} +1 -1
  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-cbbe03c0da7b081a.js → page-54e1f966ab60a4dc.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/app/editor/page-7ed95f296f5ee8e9.js +1 -0
  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-c0c0d19d51342d3e.js → page-b31610deda76337b.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-2e002ad8faa43291.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-7464c1987dd76e6d.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-d8ab4d1489223459.js → page-4fddc90b23feb215.js} +1 -1
  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-26102fd7350f171e.js +1 -0
  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-139ba5c367bbf60a.js +1 -0
  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 +30 -29
  64. package/apps/pwa/out/autopilot/index.html +1 -1
  65. package/apps/pwa/out/autopilot/index.txt +30 -29
  66. package/apps/pwa/out/board/index.html +1 -1
  67. package/apps/pwa/out/board/index.txt +30 -29
  68. package/apps/pwa/out/botlab/index.html +1 -1
  69. package/apps/pwa/out/botlab/index.txt +27 -26
  70. package/apps/pwa/out/bots/index.html +1 -1
  71. package/apps/pwa/out/bots/index.txt +27 -26
  72. package/apps/pwa/out/chat/index.html +1 -1
  73. package/apps/pwa/out/chat/index.txt +27 -26
  74. package/apps/pwa/out/consult/index.html +1 -1
  75. package/apps/pwa/out/consult/index.txt +30 -29
  76. package/apps/pwa/out/control/index.html +1 -1
  77. package/apps/pwa/out/control/index.txt +30 -29
  78. package/apps/pwa/out/design-check/index.html +1 -1
  79. package/apps/pwa/out/design-check/index.txt +30 -29
  80. package/apps/pwa/out/editor/index.html +1 -1
  81. package/apps/pwa/out/editor/index.txt +30 -29
  82. package/apps/pwa/out/exec/index.html +1 -1
  83. package/apps/pwa/out/exec/index.txt +30 -29
  84. package/apps/pwa/out/field/index.html +1 -1
  85. package/apps/pwa/out/field/index.txt +30 -29
  86. package/apps/pwa/out/healthz +1 -1
  87. package/apps/pwa/out/index.html +1 -1
  88. package/apps/pwa/out/index.txt +27 -26
  89. package/apps/pwa/out/intake/index.html +1 -1
  90. package/apps/pwa/out/intake/index.txt +30 -29
  91. package/apps/pwa/out/live/index.html +1 -1
  92. package/apps/pwa/out/live/index.txt +30 -29
  93. package/apps/pwa/out/market/index.html +1 -1
  94. package/apps/pwa/out/market/index.txt +27 -26
  95. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  96. package/apps/pwa/out/missions/placeholder/index.txt +27 -26
  97. package/apps/pwa/out/morning/index.html +1 -1
  98. package/apps/pwa/out/morning/index.txt +30 -29
  99. package/apps/pwa/out/observatory/index.html +1 -1
  100. package/apps/pwa/out/observatory/index.txt +30 -29
  101. package/apps/pwa/out/ops/checklist/index.html +1 -0
  102. package/apps/pwa/out/ops/checklist/index.txt +30 -0
  103. package/apps/pwa/out/ops/release/index.html +1 -0
  104. package/apps/pwa/out/ops/release/index.txt +30 -0
  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 +28 -27
  109. package/apps/pwa/out/scheduler/index.html +1 -1
  110. package/apps/pwa/out/scheduler/index.txt +30 -29
  111. package/apps/pwa/out/sessions/index.html +1 -1
  112. package/apps/pwa/out/sessions/index.txt +27 -26
  113. package/apps/pwa/out/settings/devices/index.html +1 -1
  114. package/apps/pwa/out/settings/devices/index.txt +30 -29
  115. package/apps/pwa/out/settings/index.html +1 -1
  116. package/apps/pwa/out/settings/index.txt +30 -29
  117. package/apps/pwa/out/setup/done/index.html +1 -1
  118. package/apps/pwa/out/setup/done/index.txt +30 -29
  119. package/apps/pwa/out/setup/index.html +1 -1
  120. package/apps/pwa/out/setup/index.txt +30 -29
  121. package/apps/pwa/out/share/index.html +1 -1
  122. package/apps/pwa/out/share/index.txt +30 -29
  123. package/apps/pwa/out/showroom/index.html +1 -1
  124. package/apps/pwa/out/showroom/index.txt +27 -26
  125. package/apps/pwa/out/tasks/index.html +1 -1
  126. package/apps/pwa/out/tasks/index.txt +30 -29
  127. package/apps/pwa/out/term/index.html +1 -1
  128. package/apps/pwa/out/term/index.txt +30 -29
  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 +30 -29
  133. package/apps/pwa/out/vault/index.html +1 -1
  134. package/apps/pwa/out/vault/index.txt +30 -29
  135. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  136. package/apps/pwa/out/workflows/chat-ui/index.txt +30 -29
  137. package/apps/pwa/out/workflows/index.html +1 -1
  138. package/apps/pwa/out/workflows/index.txt +30 -29
  139. package/apps/pwa/out/workspace/index.html +1 -1
  140. package/apps/pwa/out/workspace/index.txt +30 -29
  141. package/apps/pwa/out/worktrees/index.html +1 -1
  142. package/apps/pwa/out/worktrees/index.txt +30 -29
  143. package/graphs/field/field-feed.yaml +2 -0
  144. package/graphs/implement-loop.yaml +1 -1
  145. package/graphs/release/recipes.yaml +3 -0
  146. package/graphs/release/release-loop.yaml +3 -1
  147. package/graphs/test-diet/nightly-audit.yaml +18 -0
  148. package/graphs/test-diet/recipes.yaml +3 -0
  149. package/package.json +1 -1
  150. package/plugins/card-followup/graphs/run-step.ts +48 -13
  151. package/plugins/doc-draft/graphs/doc-draft.yaml +3 -3
  152. package/plugins/doc-draft/graphs/run-step.ts +14 -7
  153. package/scripts/ci-pwa-build-gate.ts +160 -0
  154. package/scripts/coord-channel-watch.sh +42 -8
  155. package/scripts/coord-digest.ts +199 -0
  156. package/scripts/coord-post.sh +60 -3
  157. package/scripts/install.ps1 +68 -9
  158. package/scripts/install.sh +39 -12
  159. package/scripts/lib/pwa-scenarios/scenarios.ts +20 -0
  160. package/scripts/lib/tui-regress-checks.ts +146 -0
  161. package/scripts/memory-lifecycle-cycle.ts +2 -2
  162. package/scripts/ops-now-probe.ts +327 -0
  163. package/scripts/publish-internal-dist.ts +11 -1
  164. package/scripts/release-loop/cutoff.ts +31 -2
  165. package/scripts/release-loop/export-check-node.ts +85 -0
  166. package/scripts/release-loop/gate-node.ts +18 -4
  167. package/scripts/release-loop/node-verdict.ts +8 -1
  168. package/scripts/release-loop/ops-upgrade-node.ts +72 -16
  169. package/scripts/release-loop/release-readiness.ts +9 -0
  170. package/scripts/release-loop/version-node.ts +58 -10
  171. package/scripts/repo-watch-cycle.ts +1 -1
  172. package/scripts/retro-cycle.ts +2 -2
  173. package/scripts/schedule-health-report.ts +1 -1
  174. package/scripts/storyboard/storyboard.ts +5 -161
  175. package/scripts/test-diet/lib.ts +31 -1
  176. package/scripts/test-diet/node.ts +134 -17
  177. package/scripts/test-timeout-audit.ts +114 -0
  178. package/scripts/tree-sync-alert.ts +1 -1
  179. package/scripts/tui-regress.ts +71 -0
  180. package/scripts/waitlist-daily-digest.ts +1 -1
  181. package/skills/explainer-video/SKILL.md +4 -0
  182. package/skills/explainer-video/engine/reel.mjs +18 -13
  183. package/skills/explainer-video/engine/reel.sh +24 -5
  184. package/skills/explainer-video/engine/stitch.mjs +145 -0
  185. package/skills/pitch-deck/SKILL.md +36 -0
  186. package/skills/pitch-deck/template.html +80 -0
  187. package/src/acp/codex-approval-adapter.ts +9 -3
  188. package/src/acp/core-turn-bridge.ts +117 -18
  189. package/src/acp/fact-question.ts +11 -0
  190. package/src/acp/server.ts +48 -7
  191. package/src/acp/tool-approval.ts +126 -0
  192. package/src/acp/turn-runner.ts +36 -0
  193. package/src/agent/monad-agent-turn.ts +8 -0
  194. package/src/ask-user-question/pending-questions.ts +25 -10
  195. package/src/ask-user-question/types.ts +9 -0
  196. package/src/auth/setup-link-tokens.fixture.ts +19 -0
  197. package/src/auth/setup-link-tokens.ts +164 -0
  198. package/src/autopilot/build/integrity-gate.ts +12 -0
  199. package/src/boot/daemon-prompt-turn.ts +3 -0
  200. package/src/boot/daemon-tools/index.ts +8 -0
  201. package/src/boot/daemon-tools/types.ts +2 -0
  202. package/src/boot/daemon-tools/web-search.ts +1 -0
  203. package/src/budget/unified-usage.ts +75 -0
  204. package/src/channel-bot-token.ts +37 -10
  205. package/src/chat/ctrl-c-in-input.ts +14 -0
  206. package/src/chat/index.ts +61 -9
  207. package/src/chat/pickers/state.ts +6 -2
  208. package/src/cli/agent-cli.ts +7 -0
  209. package/src/cli/decisions-cli.ts +25 -3
  210. package/src/cli/doctor-bundle-upload.ts +68 -0
  211. package/src/cli/doctor-cli.ts +38 -4
  212. package/src/cli/op-proxy-sync.ts +83 -0
  213. package/src/cli/ops-cli.ts +20 -0
  214. package/src/cli/pod-cli.ts +49 -0
  215. package/src/cli/pr-cli.ts +37 -4
  216. package/src/cli/pwa-stop.ts +163 -13
  217. package/src/cli/pwa-test.ts +3 -0
  218. package/src/cli/release-cli.ts +98 -15
  219. package/src/cli/self-update.ts +14 -0
  220. package/src/cli/storyboard-cli.ts +37 -0
  221. package/src/cli/test-state-dir-flag.ts +11 -4
  222. package/src/connectors/linear.ts +146 -0
  223. package/src/context-bus/coord-events.ts +115 -0
  224. package/src/dashboard/dashboard-boot-fixture.ts +23 -0
  225. package/src/dashboard/dashboard-status-lines.ts +45 -0
  226. package/src/dashboard/first-screen-band.ts +35 -0
  227. package/src/dashboard/index.ts +128 -34
  228. package/src/dashboard/provider-slash-runtime.ts +28 -11
  229. package/src/dashboard/slash-runtime/dashboard-handlers.ts +17 -0
  230. package/src/dashboard/slash-runtime/help-from-registry.ts +43 -28
  231. package/src/dashboard/turn-stream-formatter.ts +1 -1
  232. package/src/decisions/decision-cards.ts +50 -2
  233. package/src/decisions/decision-ledger.ts +75 -4
  234. package/src/decisions/decision-linear-projection.ts +157 -0
  235. package/src/decisions/web-push-decision-cards.ts +50 -0
  236. package/src/directive/interpretation-card.ts +36 -0
  237. package/src/discord.ts +2 -1
  238. package/src/domains/coo-admin-tool.ts +62 -0
  239. package/src/domains/core-tools.ts +11 -3
  240. package/src/domains/elanous-card.ts +29 -0
  241. package/src/domains/ops-facts-tool.ts +76 -0
  242. package/src/domains/outbound-alert.ts +41 -13
  243. package/src/domains/release-tool.ts +168 -0
  244. package/src/domains/telegram-kind-route.ts +19 -1
  245. package/src/exec-requests/planner.ts +98 -28
  246. package/src/exec-requests/runner.ts +107 -10
  247. package/src/exec-requests/store.ts +8 -2
  248. package/src/field/field-media.ts +10 -7
  249. package/src/field/field-reel.ts +14 -4
  250. package/src/git-fs/worktree.ts +45 -6
  251. package/src/graph-runner/graph-approval-card.ts +113 -0
  252. package/src/graph-runner/graph-cli.ts +129 -4
  253. package/src/graph-runner/graph-notify.ts +25 -3
  254. package/src/graph-runner/runner.ts +142 -16
  255. package/src/harness/browser-verify.ts +25 -2
  256. package/src/harness/harness-cli-command.ts +231 -35
  257. package/src/harness/harness-incidents-writer.fixture.ts +24 -0
  258. package/src/harness/harness-incidents.ts +129 -0
  259. package/src/harness/harness-stop.ts +53 -3
  260. package/src/hitl/clarification-policy.ts +3 -1
  261. package/src/index.ts +172 -41
  262. package/src/intake-plane/author-goals.ts +37 -4
  263. package/src/intake-plane/check.ts +3 -0
  264. package/src/intake-plane/discord-seat-work.ts +47 -2
  265. package/src/intake-plane/seat-doc-route.ts +170 -6
  266. package/src/intake-plane/telegram-seat-work.ts +44 -2
  267. package/src/intake-plane/telegram-work.ts +1 -0
  268. package/src/maturity/cli-maturity.ts +34 -0
  269. package/src/maturity/feature-maturity.ts +100 -1
  270. package/src/maturity/tui-slash-maturity.ts +31 -0
  271. package/src/memory.ts +32 -4
  272. package/src/nexus/api/exec-requests.ts +33 -3
  273. package/src/nexus/api/http-server.ts +73 -1
  274. package/src/nexus/api/meta-api.ts +38 -0
  275. package/src/nexus/api/operator.ts +61 -0
  276. package/src/nexus/api/ops-api.ts +148 -0
  277. package/src/nexus/api/ops-seats-sources.ts +95 -0
  278. package/src/nexus/api/ops-seats.ts +124 -0
  279. package/src/nexus/api/public-routes.ts +14 -1
  280. package/src/nexus/api/seat-requests.ts +88 -8
  281. package/src/nexus/api/sessions-store.ts +63 -0
  282. package/src/nexus/api/setup-claim.ts +21 -0
  283. package/src/nexus/api/setup-codex-login.ts +160 -0
  284. package/src/nexus/api/usage-api.ts +102 -0
  285. package/src/nexus/config/secrets/file-backend.ts +56 -18
  286. package/src/nexus/index.ts +45 -4
  287. package/src/onboarding/first-run-doctor.ts +54 -0
  288. package/src/onboarding/first-run.ts +89 -0
  289. package/src/onboarding/lan-address.ts +45 -0
  290. package/src/onboarding/web-first.ts +119 -24
  291. package/src/onboarding.ts +4 -0
  292. package/src/ops-now/ops-now-refresh.ts +100 -0
  293. package/src/project/project-store.ts +115 -0
  294. package/src/release-loop/checklist.ts +76 -61
  295. package/src/release-loop/feature-store.ts +460 -0
  296. package/src/release-loop/manifest.ts +59 -5
  297. package/src/release-loop/release-schedule.ts +53 -0
  298. package/src/repl/index.ts +7 -0
  299. package/src/seat-dispatch/ceo-commands.ts +98 -0
  300. package/src/seat-dispatch/ceo-intent.ts +38 -0
  301. package/src/seat-loop/seat-loop.ts +231 -0
  302. package/src/seat-loop/seat-report.ts +64 -0
  303. package/src/self-dev/decompose.ts +66 -11
  304. package/src/self-dev/run-store.ts +10 -1
  305. package/src/self-implement/execution-clarification.ts +115 -0
  306. package/src/self-implement/gate-cli.ts +6 -3
  307. package/src/self-implement/orchestrator.ts +37 -3
  308. package/src/self-implement/pipeline-shape.ts +1 -1
  309. package/src/self-implement/run-status-mapping.ts +5 -3
  310. package/src/session/chat.ts +2 -0
  311. package/src/session/index.ts +9 -0
  312. package/src/shell-env-bootstrap.ts +17 -0
  313. package/src/skills/tools/usage-slash.ts +8 -7
  314. package/src/steward/directive.ts +51 -2
  315. package/src/steward/launch.ts +250 -0
  316. package/src/steward/outcome-notify.ts +115 -0
  317. package/src/steward/steward-cards.ts +8 -2
  318. package/src/steward/triage-plan.ts +32 -4
  319. package/src/steward/triage.ts +93 -9
  320. package/src/steward/working-backwards.ts +64 -0
  321. package/src/storyboard/storyboard.ts +141 -0
  322. package/src/task-cards/card-store.ts +2 -2
  323. package/src/task-orchestrator/surfaces/pod-lease.ts +260 -0
  324. package/src/telegram-card-followup.ts +233 -0
  325. package/src/telegram-commands.ts +16 -0
  326. package/src/telegram-report.ts +15 -9
  327. package/src/telegram-run.ts +90 -13
  328. package/src/telegram.ts +29 -8
  329. package/src/thinking-line.ts +28 -20
  330. package/src/tool-runtime/types.ts +4 -0
  331. package/src/tui/chat/execution-badge.ts +1 -1
  332. package/src/tui-client/dashboard-session.ts +2 -0
  333. package/src/user-config.ts +118 -11
  334. package/src/ux-sim/turn-states.ts +1 -1
  335. package/src/version/packed-revision.json +1 -1
  336. package/src/web-push/notify-exec-request.ts +32 -0
  337. package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/4836-3084c58c1725a40b.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/7032-f4ef698518fba61c.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/8067-d976b441a104ab74.js +0 -1
  346. package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/8894-7317998f6cb97d8c.js +0 -1
  348. package/apps/pwa/out/_next/static/chunks/9454-a3e0953307216c58.js +0 -1
  349. package/apps/pwa/out/_next/static/chunks/9740-8f5ff96c522b5f55.js +0 -1
  350. package/apps/pwa/out/_next/static/chunks/app/approvals/page-9aa47b0bc6d570ff.js +0 -1
  351. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f4d2ecbbae6088d6.js +0 -1
  352. package/apps/pwa/out/_next/static/chunks/app/board/page-2053e49930c82748.js +0 -1
  353. package/apps/pwa/out/_next/static/chunks/app/bots/page-a70ba27c33003dd0.js +0 -1
  354. package/apps/pwa/out/_next/static/chunks/app/consult/page-18166f885d831d3e.js +0 -1
  355. package/apps/pwa/out/_next/static/chunks/app/control/page-1c9d6cfe68b121c2.js +0 -1
  356. package/apps/pwa/out/_next/static/chunks/app/editor/page-c108ff1fdb665c97.js +0 -1
  357. package/apps/pwa/out/_next/static/chunks/app/exec/page-147a328753d983e2.js +0 -1
  358. package/apps/pwa/out/_next/static/chunks/app/field/page-799395a9df7d95aa.js +0 -1
  359. package/apps/pwa/out/_next/static/chunks/app/intake/page-6a8158b71d1fe9d7.js +0 -1
  360. package/apps/pwa/out/_next/static/chunks/app/layout-9567560270857531.js +0 -1
  361. package/apps/pwa/out/_next/static/chunks/app/live/page-e7ed3d010153d818.js +0 -1
  362. package/apps/pwa/out/_next/static/chunks/app/morning/page-b672e02e4bec8deb.js +0 -1
  363. package/apps/pwa/out/_next/static/chunks/app/not-found-1ba4a5d857276334.js +0 -1
  364. package/apps/pwa/out/_next/static/chunks/app/observatory/page-9db2226f38c542fb.js +0 -1
  365. package/apps/pwa/out/_next/static/chunks/app/page-70ee65f5f7a151dc.js +0 -1
  366. package/apps/pwa/out/_next/static/chunks/app/reflection/page-0bb8e43203efb23b.js +0 -1
  367. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-092e1bcf27d751cc.js +0 -1
  368. package/apps/pwa/out/_next/static/chunks/app/sessions/page-eb52580c4b75b905.js +0 -1
  369. package/apps/pwa/out/_next/static/chunks/app/settings/page-e573437b0a1c728f.js +0 -1
  370. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-294300625e49e4d3.js +0 -1
  371. package/apps/pwa/out/_next/static/chunks/app/setup/page-085847b088675978.js +0 -1
  372. package/apps/pwa/out/_next/static/chunks/app/share/page-9fe9d38c36999eb4.js +0 -1
  373. package/apps/pwa/out/_next/static/chunks/app/tasks/page-6141f67f531d8b6a.js +0 -1
  374. package/apps/pwa/out/_next/static/chunks/app/term/page-295a02b1026b8255.js +0 -1
  375. package/apps/pwa/out/_next/static/chunks/app/trace/page-1eb9a0e1e054209d.js +0 -1
  376. package/apps/pwa/out/_next/static/chunks/app/vault/page-82efa177a30f3741.js +0 -1
  377. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d0be51cec4f62606.js +0 -1
  378. package/apps/pwa/out/_next/static/chunks/app/workflows/page-2e367075ffdbc062.js +0 -1
  379. package/apps/pwa/out/_next/static/chunks/app/workspace/page-647ed2b66c1d49f1.js +0 -1
  380. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ba058c56e38f2792.js +0 -1
  381. package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +0 -1
  382. package/apps/pwa/out/_next/static/css/5ef72a618a8994f3.css +0 -1
  383. package/apps/pwa/out/_next/static/lywc8m_HDzH9MKSDQhyEj/_buildManifest.js +0 -1
  384. package/src/dashboard/turn-typeahead-echo.ts +0 -23
  385. /package/apps/pwa/out/_next/static/{lywc8m_HDzH9MKSDQhyEj → 2DsiDfDCks44g1cPUMhiW}/_ssgManifest.js +0 -0
@@ -122,6 +122,152 @@ export async function fetchLinearIssues({ apiKey, teamKey, labelOrPrefix, since,
122
122
  return events.sort((a, b) => PRIORITY_RANK[a.priority ?? 'none'] - PRIORITY_RANK[b.priority ?? 'none']);
123
123
  }
124
124
 
125
+ export interface LinearProjectIssue {
126
+ identifier: string;
127
+ title: string;
128
+ url: string;
129
+ state: { name: string; type: string };
130
+ dueDate: string | null;
131
+ priority: number;
132
+ assignee: { name: string } | null;
133
+ updatedAt: string;
134
+ }
135
+
136
+ type LinearRequestOptions = { apiKey: string; fetch?: typeof fetch };
137
+
138
+ async function linearRequest<T>({ apiKey, fetch: fetchFn = fetch }: LinearRequestOptions, query: string, variables: Record<string, unknown>): Promise<T> {
139
+ let response: Response;
140
+ try {
141
+ response = await fetchFn('https://api.linear.app/graphql', {
142
+ method: 'POST', headers: { Authorization: apiKey, 'Content-Type': 'application/json' },
143
+ body: JSON.stringify({ query, variables }),
144
+ });
145
+ } catch (error) {
146
+ throw new Error(`Linear GraphQL request failed: ${error instanceof Error ? error.message : String(error)}`);
147
+ }
148
+ if (!response.ok) throw new Error(`Linear GraphQL HTTP ${response.status}`);
149
+ let body: { data?: T; errors?: Array<{ message?: string }> };
150
+ try { body = await response.json() as typeof body; }
151
+ catch { throw new Error('Linear GraphQL invalid JSON response'); }
152
+ if (body.errors?.length) throw new Error(`Linear GraphQL: ${body.errors.map(e => e.message ?? 'unknown error').join('; ')}`);
153
+ if (!body.data) throw new Error('Linear GraphQL missing data');
154
+ return body.data;
155
+ }
156
+
157
+ /** Shared unique name → project id lookup for COO reads and decision writes. */
158
+ export async function resolveLinearProjectId({ apiKey, project, fetch: fetchFn = fetch }: LinearRequestOptions & { project: string }): Promise<string> {
159
+ const cursors = new Set<string>();
160
+ let projectId = project;
161
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(project)) {
162
+ const matches: string[] = [];
163
+ let after: string | null = null;
164
+ do {
165
+ const data: { projects?: { nodes?: Array<{ id: string; name: string }>; pageInfo?: { hasNextPage: boolean; endCursor: string | null } } } = await linearRequest({ apiKey, fetch: fetchFn },
166
+ `query CooProjects($after: String) { projects(first: 100, after: $after) { nodes { id name } pageInfo { hasNextPage endCursor } } }`, { after });
167
+ if (!Array.isArray(data.projects?.nodes)) throw new Error('Linear GraphQL missing projects');
168
+ for (const candidate of data.projects.nodes) if (candidate.name === project) matches.push(candidate.id);
169
+ const page = data.projects.pageInfo;
170
+ if (page?.hasNextPage && (!page.endCursor || cursors.has(page.endCursor))) throw new Error('Linear GraphQL pagination cursor missing or repeated');
171
+ after = page?.hasNextPage ? page.endCursor : null;
172
+ if (after) cursors.add(after);
173
+ } while (after);
174
+ if (matches.length > 1) throw new Error('프로젝트 이름이 여럿과 맞습니다');
175
+ if (!matches.length) throw new Error('프로젝트 이름과 맞는 항목이 없습니다');
176
+ projectId = matches[0]!;
177
+ }
178
+ return projectId;
179
+ }
180
+
181
+ /** Read-only project lookup. A name is resolved uniquely before querying its issues. */
182
+ export async function fetchLinearProjectIssues({ apiKey, project, includeDone = false, fetch: fetchFn = fetch }: {
183
+ apiKey: string; project: string; includeDone?: boolean; fetch?: typeof fetch;
184
+ }): Promise<LinearProjectIssue[] & { truncated: boolean }> {
185
+ const projectId = await resolveLinearProjectId({ apiKey, project, fetch: fetchFn });
186
+ const issues: LinearProjectIssue[] = [];
187
+ const cursors = new Set<string>();
188
+ let after: string | null = null;
189
+ do {
190
+ const stateFilter = includeDone ? '' : ', state: { type: { nin: ["completed", "canceled"] } }';
191
+ const data: { issues?: { nodes?: LinearProjectIssue[]; pageInfo?: { hasNextPage: boolean; endCursor: string | null } } } = await linearRequest({ apiKey, fetch: fetchFn },
192
+ `query CooProjectIssues($projectId: ID!, $after: String, $first: Int!) {
193
+ issues(filter: { project: { id: { eq: $projectId } }${stateFilter} }, first: $first, after: $after) {
194
+ nodes { identifier title url state { name type } dueDate priority assignee { name } updatedAt }
195
+ pageInfo { hasNextPage endCursor }
196
+ }
197
+ }`, { projectId, after, first: Math.min(100, 250 - issues.length) });
198
+ if (!Array.isArray(data.issues?.nodes)) throw new Error('Linear GraphQL missing issues');
199
+ for (const node of data.issues.nodes) {
200
+ if (!includeDone && (node.state?.type === 'completed' || node.state?.type === 'canceled')) continue;
201
+ if (issues.length < 250) issues.push(node);
202
+ }
203
+ const page = data.issues.pageInfo;
204
+ if (page?.hasNextPage && (!page.endCursor || cursors.has(page.endCursor))) throw new Error('Linear GraphQL pagination cursor missing or repeated');
205
+ after = page?.hasNextPage ? page.endCursor : null;
206
+ if (after) cursors.add(after);
207
+ } while (after && issues.length < 250);
208
+ Object.defineProperty(issues, 'truncated', { value: after !== null, enumerable: false });
209
+ return issues as LinearProjectIssue[] & { truncated: boolean };
210
+ }
211
+
212
+ /** Find a previously created decision issue after a crash between Linear creation and the local rename. */
213
+ export async function findLinearDecisionIssue({ apiKey, projectId, decisionId, fetch: fetchFn = fetch }: LinearRequestOptions & { projectId: string; decisionId: string }): Promise<string | null> {
214
+ const cursors = new Set<string>();
215
+ let after: string | null = null;
216
+ let match: string | null = null;
217
+ do {
218
+ const data: { issues?: { nodes?: Array<{ id: string; description: string | null }>; pageInfo?: { hasNextPage: boolean; endCursor: string | null } } } = await linearRequest({ apiKey, fetch: fetchFn },
219
+ 'query DecisionIssue($projectId: ID!, $decisionId: String!, $after: String) { issues(filter: { project: { id: { eq: $projectId } }, description: { contains: $decisionId } }, first: 100, after: $after) { nodes { id description } pageInfo { hasNextPage endCursor } } }',
220
+ { projectId, decisionId, after });
221
+ if (!Array.isArray(data.issues?.nodes)) throw new Error('Linear GraphQL missing issues');
222
+ for (const node of data.issues.nodes) {
223
+ if (!node.description?.split('\n').includes(`결정 id: ${decisionId}`)) continue;
224
+ if (match) throw new Error('Linear decision issue duplicated');
225
+ match = node.id;
226
+ }
227
+ const page = data.issues.pageInfo;
228
+ if (page?.hasNextPage && (!page.endCursor || cursors.has(page.endCursor))) throw new Error('Linear GraphQL pagination cursor missing or repeated');
229
+ after = page?.hasNextPage ? page.endCursor : null;
230
+ if (after) cursors.add(after);
231
+ } while (after);
232
+ return match;
233
+ }
234
+
235
+ /** Create in the COO project; Linear requires a team from that project for issueCreate. */
236
+ export async function createLinearIssue({ apiKey, projectId, title, description, dueDate, fetch: fetchFn = fetch }: LinearRequestOptions & {
237
+ projectId: string; title: string; description: string; dueDate?: string;
238
+ }): Promise<string> {
239
+ const data = await linearRequest<{ project?: { teams?: { nodes?: Array<{ id: string }> } } }>({ apiKey, fetch: fetchFn },
240
+ 'query DecisionProjectTeam($id: String!) { project(id: $id) { teams { nodes { id } } } }', { id: projectId });
241
+ const teams = data.project?.teams?.nodes;
242
+ if (!teams?.[0]?.id) throw new Error('Linear project has no team');
243
+ const created = await linearRequest<{ issueCreate?: { success?: boolean; issue?: { id?: string } } }>({ apiKey, fetch: fetchFn },
244
+ 'mutation DecisionIssueCreate($input: IssueCreateInput!) { issueCreate(input: $input) { success issue { id } } }',
245
+ { input: { teamId: teams[0].id, projectId, title, description, ...(dueDate ? { dueDate } : {}) } });
246
+ const id = created.issueCreate?.issue?.id;
247
+ if (!created.issueCreate?.success || !id) throw new Error('Linear issueCreate did not return an issue id');
248
+ return id;
249
+ }
250
+
251
+ export async function commentLinearIssue({ apiKey, issueId, body, fetch: fetchFn = fetch }: LinearRequestOptions & { issueId: string; body: string }): Promise<void> {
252
+ const data = await linearRequest<{ commentCreate?: { success?: boolean; comment?: { id?: string } } }>({ apiKey, fetch: fetchFn },
253
+ 'mutation DecisionComment($input: CommentCreateInput!) { commentCreate(input: $input) { success comment { id } } }',
254
+ { input: { issueId, body } });
255
+ if (!data.commentCreate?.success || !data.commentCreate.comment?.id) throw new Error('Linear commentCreate failed');
256
+ }
257
+
258
+ export async function moveLinearIssueToStateType({ apiKey, issueId, type, fetch: fetchFn = fetch }: LinearRequestOptions & {
259
+ issueId: string; type: 'completed' | 'canceled';
260
+ }): Promise<void> {
261
+ const data = await linearRequest<{ issue?: { team?: { states?: { nodes?: Array<{ id: string; type: string }> } } } }>({ apiKey, fetch: fetchFn },
262
+ 'query DecisionIssueStates($id: String!) { issue(id: $id) { team { states { nodes { id type } } } } }', { id: issueId });
263
+ const state = data.issue?.team?.states?.nodes?.find(node => node.type === type);
264
+ if (!state?.id) throw new Error(`Linear team workflow state not found: ${type}`);
265
+ const updated = await linearRequest<{ issueUpdate?: { success?: boolean } }>({ apiKey, fetch: fetchFn },
266
+ 'mutation DecisionIssueClose($id: String!, $input: IssueUpdateInput!) { issueUpdate(id: $id, input: $input) { success } }',
267
+ { id: issueId, input: { stateId: state.id } });
268
+ if (!updated.issueUpdate?.success) throw new Error('Linear issueUpdate failed');
269
+ }
270
+
125
271
  export function toTaskRequest(event: ExternalTaskEvent): {
126
272
  title: string; description: string; priority: 'high' | 'medium' | 'low';
127
273
  external: { provider: 'linear'; ref: string; url?: string; team?: string };
@@ -0,0 +1,115 @@
1
+ import type { Database } from 'bun:sqlite';
2
+ import { readFileSync, existsSync } from 'node:fs';
3
+ import { resolve } from 'node:path';
4
+ import { debug, redactSecretText } from '../debug/log.js';
5
+ import { openSurfaceEventsDb, recordEvent, surfaceEventsDbPath } from '../domains/surface-events.js';
6
+
7
+ export interface CoordEventInput {
8
+ seat: string;
9
+ recipients: string[];
10
+ all: boolean;
11
+ kind: string | null;
12
+ slot: string | null;
13
+ deadline: string | null;
14
+ url: string | null;
15
+ headline: string;
16
+ at?: string;
17
+ }
18
+
19
+ export interface CoordEvent {
20
+ id: string;
21
+ at: string;
22
+ text: string;
23
+ summary: string;
24
+ kind: string;
25
+ refs: Pick<CoordEventInput, 'seat' | 'recipients' | 'all' | 'kind' | 'slot' | 'deadline' | 'url'>;
26
+ }
27
+
28
+ function seatIds(): Set<string> {
29
+ return new Set((JSON.parse(readFileSync(resolve(import.meta.dir, '../../scripts/coord-tracks.json'), 'utf8')) as
30
+ { tracks: Array<{ id: string }> }).tracks.map((track) => track.id));
31
+ }
32
+ const kinds = new Set(['요청', '결정', '사고', '보고', '정정']);
33
+
34
+ /** Mirror the sender's ordered envelope fields; only the first line enters this parser. */
35
+ export function parseCoordHeader(seat: string, header: string): Omit<CoordEventInput, 'url' | 'at'> {
36
+ const first = header.split(/\r?\n/, 1)[0] ?? '';
37
+ if (!first.startsWith(`**[${seat}]**`)) throw new Error('조율 글 신원 접두 불일치');
38
+ const headline = first.slice(`**[${seat}]**`.length)
39
+ .replace(/^\s*(?:📌안내\s*)?\d{4}-\d\d-\d\d\s+\d\d:\d\d\s+KST\s*/, '').trim();
40
+ const headerBody = first.slice(`**[${seat}]**`.length);
41
+ const arrow = headerBody.indexOf('→');
42
+ const envelope = arrow >= 0 && !headerBody.slice(0, arrow).includes(' · ') ? headerBody.slice(arrow + 1).trim() : '';
43
+ const fields = envelope ? envelope.split(' · ') : [];
44
+ const ids = seatIds();
45
+ const recipients: string[] = [];
46
+ let all = false;
47
+ let i = 0;
48
+ for (; i < fields.length; i++) {
49
+ const field = fields[i]!.trim().split(' — ')[0]!;
50
+ const tokens = field.replaceAll(',', ' ').split(/\s+/).filter(Boolean);
51
+ if (!tokens.length || !tokens.every((t) => t === '전원' || ids.has(t))) break;
52
+ for (const token of tokens) {
53
+ if (token === '전원') all = true;
54
+ else recipients.push(token);
55
+ }
56
+ if (fields[i]!.includes(' — ')) { i++; break; }
57
+ }
58
+ const candidate = fields[i]?.trim() ?? '';
59
+ const kind = kinds.has(candidate) ? candidate : null;
60
+ const slot = fields[i + 1]?.trim() || null;
61
+ const deadline = fields.slice(i + 1).map((f) => f.trim()).find((f) => /^기한\s+\S/.test(f)) ?? null;
62
+ return { seat, recipients, all, kind, slot, deadline, headline };
63
+ }
64
+
65
+ /** Atomic lookup+insert makes retries safe, including URL-less posts in the same minute. */
66
+ export function recordCoordEvent(input: CoordEventInput, deps: { db?: Database } = {}): string {
67
+ const hasUrl = !!input.url;
68
+ let db: Database | undefined;
69
+ try {
70
+ db = deps.db ?? openSurfaceEventsDb();
71
+ const at = input.at ?? new Date().toISOString();
72
+ const first = input.headline.split(/\r?\n/, 1)[0] ?? '';
73
+ const withoutPrefix = first.replace(/^\*\*\[[A-Z]+\]\*\*\s*/, '')
74
+ .replace(/^(?:📌안내\s*)?\d{4}-\d\d-\d\d\s+\d\d:\d\d\s+KST\s*/, '');
75
+ const headline = redactSecretText(withoutPrefix).trim().slice(0, 120);
76
+ const safe = (value: string) => redactSecretText(value.split(/\r?\n/, 1)[0] ?? '');
77
+ const refs = { seat: safe(input.seat), recipients: input.recipients.map(safe), all: input.all,
78
+ kind: input.kind === null ? null : safe(input.kind), slot: input.slot === null ? null : safe(input.slot),
79
+ deadline: input.deadline === null ? null : safe(input.deadline), url: input.url ? safe(input.url) : null };
80
+ const result = db.transaction(() => {
81
+ const prior = (refs.url
82
+ ? db!.prepare(`SELECT id FROM events WHERE surface='coord:channel' AND json_extract(refs,'$.url')=? LIMIT 1`).get(refs.url)
83
+ : db!.prepare(`SELECT id FROM events WHERE surface='coord:channel' AND json_extract(refs,'$.url') IS NULL
84
+ AND json_extract(refs,'$.seat')=? AND summary=? AND substr(ts,1,16)=? LIMIT 1`)
85
+ .get(refs.seat, headline, at.slice(0, 16))) as { id: string } | null;
86
+ if (prior) return { id: prior.id, dedup: true };
87
+ return { id: recordEvent(db!, { surface: 'coord:channel', direction: 'outbound', kind: refs.kind ?? 'unknown',
88
+ domain: 'elanous', category: 'coordination', text: headline, summary: headline,
89
+ refs: JSON.stringify(refs), ts: at }), dedup: false };
90
+ }).immediate();
91
+ try { debug.log('context.coord', result.dedup ? 'dedup' : 'recorded', { seat: refs.seat, kind: refs.kind, hasUrl }); }
92
+ catch { /* observation cannot turn a successful write into a failed send */ }
93
+ return result.id;
94
+ } catch (error) {
95
+ try { debug.log('context.coord', 'failed', { seat: redactSecretText(input.seat), kind: input.kind && redactSecretText(input.kind), hasUrl }); }
96
+ catch { /* preserve the original write error */ }
97
+ throw error;
98
+ } finally { if (!deps.db) db?.close(); }
99
+ }
100
+
101
+ /** Read-only chronological list; a missing store is an empty ledger, not a newly created one. */
102
+ export function listCoordEvents(opts: { since: string; seat?: string }, deps: { db?: Database } = {}): CoordEvent[] {
103
+ if (!deps.db && !existsSync(surfaceEventsDbPath())) return [];
104
+ const db = deps.db ?? openSurfaceEventsDb();
105
+ try {
106
+ const rows = db.prepare(`SELECT id, ts, text, summary, kind, refs FROM events
107
+ WHERE surface='coord:channel' AND direction='outbound' AND ts>=?
108
+ ${opts.seat ? "AND json_extract(refs,'$.seat')=?" : ''} ORDER BY ts ASC, rowid ASC`)
109
+ .all(...(opts.seat ? [opts.since, opts.seat] : [opts.since])) as Array<{
110
+ id: string; ts: string; text: string; summary: string; kind: string; refs: string;
111
+ }>;
112
+ return rows.map((r) => ({ id: r.id, at: r.ts, text: r.text, summary: r.summary, kind: r.kind,
113
+ refs: JSON.parse(r.refs) as CoordEvent['refs'] }));
114
+ } finally { if (!deps.db) db.close(); }
115
+ }
@@ -0,0 +1,23 @@
1
+ import { showDashboard } from './index.js';
2
+
3
+ const expected = process.env.DASHBOARD_BOOT_SUGGESTION;
4
+ if (!expected) throw new Error('DASHBOARD_BOOT_SUGGESTION is required');
5
+
6
+ const originalWrite = process.stdout.write.bind(process.stdout);
7
+ let screen = '';
8
+ process.stdout.write = ((chunk: string | Uint8Array, ...args: unknown[]) => {
9
+ screen += typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf8');
10
+ if (screen.includes(expected)) {
11
+ originalWrite(`FIRST_SCREEN_SUGGESTION=${expected}\n`);
12
+ process.exit(0);
13
+ }
14
+ return true;
15
+ }) as typeof process.stdout.write;
16
+
17
+ setTimeout(() => {
18
+ originalWrite(`FIRST_SCREEN_TIMEOUT=${screen.slice(-2000)}\n`);
19
+ process.exit(1);
20
+ }, 20_000).unref();
21
+
22
+ await showDashboard();
23
+ throw new Error('dashboard exited before suggestion appeared');
@@ -0,0 +1,45 @@
1
+ export interface DashboardStatusLinesInput {
2
+ readonly provider?: string | null;
3
+ readonly model?: string | null;
4
+ readonly reasoning?: string | null;
5
+ readonly daemonAddress?: string | null;
6
+ readonly daemonConnected: boolean;
7
+ readonly accountName?: string | null;
8
+ readonly sessionSurfaceLines: readonly string[];
9
+ readonly view: string;
10
+ readonly focus: string;
11
+ readonly cwd: string;
12
+ readonly preview: string;
13
+ readonly starterClosed: string;
14
+ readonly chatOnly: string;
15
+ readonly acp: string;
16
+ }
17
+
18
+ export function buildDashboardStatusLines(input: DashboardStatusLinesInput): string[] {
19
+ const known = (value?: string | null): string => value?.trim() || '모름';
20
+ // A remote URL can contain credentials or query parameters. Neither belongs in /status.
21
+ let address = '모름';
22
+ if (input.daemonAddress?.trim()) {
23
+ try {
24
+ const url = new URL(input.daemonAddress);
25
+ address = `${url.protocol}//${url.host}`;
26
+ } catch {
27
+ address = '모름';
28
+ }
29
+ }
30
+ return [
31
+ `모델: ${known(input.provider)}/${known(input.model)} (${known(input.reasoning)})`,
32
+ `연결: 데몬 ${address} ${input.daemonConnected ? '붙음' : '안 붙음'}`,
33
+ `계정: ${input.accountName?.trim() === 'default' ? '기본' : known(input.accountName)}`,
34
+ '',
35
+ '자세히',
36
+ ...input.sessionSurfaceLines.map(line => ` ${line}`),
37
+ ` view: ${input.view}`,
38
+ ` focus: ${input.focus}`,
39
+ ` cwd: ${input.cwd}`,
40
+ ` preview: ${input.preview}`,
41
+ ` starterClosed: ${input.starterClosed}`,
42
+ ` chatOnly: ${input.chatOnly}`,
43
+ ` acp: ${input.acp}`,
44
+ ];
45
+ }
@@ -0,0 +1,35 @@
1
+ export interface FirstScreenBandInput {
2
+ readonly width: number;
3
+ readonly daemon: boolean;
4
+ readonly pwa?: {
5
+ readonly tailnet?: string | null;
6
+ readonly lan?: string | null;
7
+ readonly loopback?: string | null;
8
+ };
9
+ }
10
+
11
+ /** A startup transcript band; never includes credentials or a partial address. */
12
+ export function buildFirstScreenBand(input: FirstScreenBandInput): string[] {
13
+ const width = Number.isFinite(input.width) ? Math.max(0, Math.floor(input.width)) : 0;
14
+ const fit = (text: string): string => text.slice(0, width);
15
+ const address = [input.pwa?.tailnet, input.pwa?.lan, input.pwa?.loopback]
16
+ .map((candidate) => {
17
+ if (!candidate) return null;
18
+ try {
19
+ const url = new URL(candidate);
20
+ if (!['http:', 'https:'].includes(url.protocol) || !url.hostname) return null;
21
+ return `${url.origin}${url.pathname === '/app/' ? '/app/' : ''}`;
22
+ } catch {
23
+ return null;
24
+ }
25
+ })
26
+ .find((candidate) => candidate !== null);
27
+ const fallback = input.pwa === undefined ? 'PWA: not checked' : 'PWA: unavailable';
28
+ const addressLine = address ? `PWA: ${address}` : fallback;
29
+
30
+ return [
31
+ fit(`elanous | daemon: ${input.daemon ? 'online' : 'offline'}`),
32
+ addressLine.length <= width ? addressLine : fit(address ? 'PWA: address available' : fallback),
33
+ fit('Type a message to begin | /help for commands'),
34
+ ];
35
+ }
@@ -17,6 +17,7 @@ import type { CommandRunner } from '../ad-pipeline/higgsfield-backend.js';
17
17
  import type { ShootRunOptions } from '../ad-pipeline/shoot-run.js';
18
18
  import type { QcThresholds } from '../ad-pipeline/qc.js';
19
19
  import { elanousStateRoot } from '../autopilot/state-paths.js';
20
+ import { suggestProjectForFolder } from '../project/project-store.js';
20
21
 
21
22
  const dashboardDefaultShootRunOptions: Required<Pick<ShootRunOptions, 'pollIntervalMs' | 'maxPollsPerJob' | 'submitStaggerMs'>> = {
22
23
  pollIntervalMs: 3_000,
@@ -425,7 +426,6 @@ import {
425
426
  renderTurnTypeaheadEcho,
426
427
  } from '../chat/turn-typeahead.js';
427
428
  import { dispatchStreamingTurnTypeaheadSubmission } from './input/streaming-turn-typeahead.js';
428
- import { wireDashboardTurnTypeaheadEcho } from './turn-typeahead-echo.js';
429
429
  import { composeVertical, type LayoutZone } from '../layout/composer.js';
430
430
  import type { CoreTurnDispatchTool } from '../core-turn/index.js';
431
431
  // streamGrok kept as back-compat shim in chat.ts — not imported here
@@ -881,6 +881,10 @@ import { getModelFamily, getModelTier } from '../models/prompts.js';
881
881
  import { buildPromptInjection, getPromptBankStore } from '../prompt-bank/index.js';
882
882
  import { getInputHistoryStore, inputHistoryDbPath, inputHistoryJsonPath } from '../input-history.js';
883
883
  import { inspectActiveProvider } from '../provider-summary.js';
884
+ import { activeCodexAccountView } from '../oauth/codex-account-store.js';
885
+ import { buildDashboardStatusLines } from './dashboard-status-lines.js';
886
+ import { buildFirstScreenBand } from './first-screen-band.js';
887
+ import { runNexusShow, type NexusShowResult } from '../cli/nexus-show.js';
884
888
  import {
885
889
  workingDirSegment, sessionCwdSegment, modelSegment, gitSegment,
886
890
  ctxBarSegment, costSegment, speedSegment, elapsedSegment, cacheSegment,
@@ -2100,6 +2104,35 @@ export function createDashboardLogSearchOpener(deps: {
2100
2104
  };
2101
2105
  }
2102
2106
 
2107
+ export function renderDashboardFirstScreenBand(input: {
2108
+ readonly history: ReadonlyArray<{ role: string }>;
2109
+ readonly lines: string[];
2110
+ readonly width: number;
2111
+ readonly draw: () => void;
2112
+ readonly observe: (data: { daemon: boolean; hasPwa: boolean; hasTailnet: boolean }) => void;
2113
+ readonly show?: () => Promise<Pick<NexusShowResult, 'status' | 'urls'>>;
2114
+ }): Promise<void> {
2115
+ if (input.history.some((message) => message.role !== 'system')) return Promise.resolve();
2116
+ const band = buildFirstScreenBand({ width: input.width, daemon: false });
2117
+ input.lines.unshift(...band);
2118
+ const show = input.show ?? (() => runNexusShow({ format: 'json', out: { log: () => {}, error: () => {} } }));
2119
+ const observe = (data: { daemon: boolean; hasPwa: boolean; hasTailnet: boolean }): void => {
2120
+ try { input.observe(data); } catch { /* observation cannot interrupt the transcript */ }
2121
+ };
2122
+ return Promise.resolve().then(show).then((result) => {
2123
+ const daemon = result.status !== 'absent';
2124
+ const pwa = result.urls?.pwa;
2125
+ const hasPwa = !!pwa;
2126
+ const hasTailnet = !!pwa?.tailnet;
2127
+ observe({ daemon, hasPwa, hasTailnet });
2128
+ if (input.lines[1] !== band[1]) return;
2129
+ input.lines[1] = buildFirstScreenBand({ width: input.width, daemon, pwa })[1]!;
2130
+ try { input.draw(); } catch { /* screen may have closed */ }
2131
+ }).catch(() => {
2132
+ observe({ daemon: false, hasPwa: false, hasTailnet: false });
2133
+ });
2134
+ }
2135
+
2103
2136
  export async function showDashboard(opts: ShowDashboardOptions = {}): Promise<DashboardAction> {
2104
2137
  const dashboardQuitConfirmationState = createQuitConfirmationState();
2105
2138
  const signalQuitConfirmationState = createQuitConfirmationState();
@@ -2623,6 +2656,41 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
2623
2656
  }
2624
2657
  chatScrollOffset = -1;
2625
2658
  };
2659
+ const liveTypeaheadEchoes: { at: number; lines: string[] }[] = [];
2660
+ const restoreLiveTypeaheadEchoes = (): void => {
2661
+ let after = 0;
2662
+ for (const echo of liveTypeaheadEchoes) {
2663
+ let found = -1;
2664
+ for (let i = after; i <= chatLines.length - echo.lines.length; i++) {
2665
+ if (echo.lines.every((line, offset) => chatLines[i + offset] === line)) {
2666
+ found = i;
2667
+ break;
2668
+ }
2669
+ }
2670
+ if (found < 0) {
2671
+ found = Math.max(after, Math.min(echo.at, chatLines.length));
2672
+ chatLines.splice(found, 0, ...echo.lines);
2673
+ }
2674
+ echo.at = found;
2675
+ after = found + echo.lines.length;
2676
+ }
2677
+ };
2678
+ const echoSubmittedUserText = (text: string, opts?: { live?: boolean }): void => {
2679
+ const echoStart = chatLines.length;
2680
+ chatLines.push('');
2681
+ if (opts?.live) chatLines.push(C.muted(' ↳ 이어서'));
2682
+ const echoWrapCols = Math.max(20, termSize().cols - 6);
2683
+ const echoRows = wrapAnsiByWidth(`❯ ${text}`, echoWrapCols);
2684
+ if (echoRows.length === 0) {
2685
+ chatLines.push(C.accent('❯ '));
2686
+ } else {
2687
+ echoRows.forEach((row, i) => {
2688
+ chatLines.push(C.accent(i === 0 ? row : ` ${row}`));
2689
+ });
2690
+ }
2691
+ if (opts?.live) liveTypeaheadEchoes.push({ at: echoStart, lines: chatLines.slice(echoStart) });
2692
+ chatScrollOffset = -1;
2693
+ };
2626
2694
  const pushDebugLine = (line: string): void => {
2627
2695
  debugLines.push(line);
2628
2696
  const dropCount = trimLogBuffer(debugLines, DEBUG_LOG_BUFFER_SOFT_LIMIT);
@@ -6330,19 +6398,34 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
6330
6398
  draw,
6331
6399
  });
6332
6400
 
6333
- const getDashboardStatusLines = () => [
6334
- 'Dashboard status',
6335
- ...buildSessionDashboardStatusLines(resolveSessionSurfaceStatus({ chatModeState })).map(
6336
- line => ` ${line}`,
6337
- ),
6338
- ` view: ${workingDir.view}`,
6339
- ` focus: ${workingDir.focus}`,
6340
- ` cwd: ${workingDir.cwd}`,
6341
- ` preview: ${dockedPreview.sourceMode}`,
6342
- ` starterClosed: ${closedStarterPanes().length ? closedStarterPanes().map((pane) => paneLabel(pane)).join(', ') : 'none'}`,
6343
- ` chatOnly: ${effectiveChatOnlyMode() ? 'on' : 'off'}${chatOnlyMode ? ' (manual)' : ''}`,
6344
- ` acp: ${dashboardAcpChat.isBusy() ? 'busy' : 'idle'}`,
6345
- ];
6401
+ let daemonAttached = false;
6402
+ const getDashboardStatusLines = () => {
6403
+ const cfg = getUserConfig();
6404
+ const active = inspectActiveProvider(cfg);
6405
+ const reasoningProvider = (['openai-codex', 'openai', 'anthropic', 'gemini', 'grok', 'local', 'openrouter'] as const)
6406
+ .find(provider => active.provider === provider || active.provider === `auto:${provider}`);
6407
+ return buildDashboardStatusLines({
6408
+ provider: active.provider,
6409
+ model: active.model,
6410
+ reasoning: reasoningProvider
6411
+ ? modelSupportsReasoning(reasoningProvider, active.model)
6412
+ ? reasoningLevelLabel(effectiveReasoningLevel(cfg.llm, reasoningProvider, active.model))
6413
+ : '미지원'
6414
+ : undefined,
6415
+ daemonAddress: opts.remote?.url,
6416
+ daemonConnected: daemonAttached,
6417
+ accountName: active.provider === 'openai-codex' || active.provider === 'auto:openai-codex'
6418
+ ? activeCodexAccountView().name : undefined,
6419
+ sessionSurfaceLines: buildSessionDashboardStatusLines(resolveSessionSurfaceStatus({ chatModeState })),
6420
+ view: String(workingDir.view),
6421
+ focus: workingDir.focus,
6422
+ cwd: workingDir.cwd,
6423
+ preview: dockedPreview.sourceMode,
6424
+ starterClosed: closedStarterPanes().length ? closedStarterPanes().map((pane) => paneLabel(pane)).join(', ') : 'none',
6425
+ chatOnly: `${effectiveChatOnlyMode() ? 'on' : 'off'}${chatOnlyMode ? ' (manual)' : ''}`,
6426
+ acp: dashboardAcpChat.isBusy() ? 'busy' : 'idle',
6427
+ });
6428
+ };
6346
6429
 
6347
6430
  bootDashboardSlashExecutor({
6348
6431
  initDashboardSlashExecutor,
@@ -6859,7 +6942,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
6859
6942
  turnTypeaheadRef.state = r.state;
6860
6943
  if (r.submissionDisposition) {
6861
6944
  debug.log('dashboard.turn-typeahead', 'submission-classified', {
6862
- text: r.immediateSubmission ?? turnTypeaheadRef.state.queuedSubmissions.at(-1),
6945
+ length: (r.immediateSubmission ?? turnTypeaheadRef.state.queuedSubmissions.at(-1))?.length ?? 0,
6863
6946
  disposition: r.submissionDisposition,
6864
6947
  });
6865
6948
  // ⭐⭐⭐ `B3`(2026-08-19 · 대표 지시 ②) — FIFO 로 «턴 종료 후» 나가던 것을,
@@ -6886,6 +6969,11 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
6886
6969
  // 배수된 만큼을 앞에서부터 떨어낸다(FIFO · 순서 보존).
6887
6970
  const remaining = turnTypeaheadRef.state.queuedSubmissions.slice(drained.length);
6888
6971
  turnTypeaheadRef.state = { ...turnTypeaheadRef.state, queuedSubmissions: remaining };
6972
+ for (const text of drained) {
6973
+ echoSubmittedUserText(text, { live: true });
6974
+ debug.log('dashboard.turn-typeahead', 'echoed', { path: 'live', length: text.length });
6975
+ }
6976
+ if (drained.length > 0) draw();
6889
6977
  debug.log('dashboard.turn-typeahead', 'drained-into-live-turn', {
6890
6978
  drained: drained.length, remaining: remaining.length,
6891
6979
  });
@@ -15397,6 +15485,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
15397
15485
  ...(opts.remote ? {
15398
15486
  remote: opts.remote,
15399
15487
  onRemoteDisconnect: () => {
15488
+ daemonAttached = false;
15400
15489
  pushChatLine(C.warning(' ⚠ 데몬 연결이 끊겼다 · 입력 중인 글은 그대로다 · 다시 붙으려면 TUI 를 다시 띄워라'));
15401
15490
  draw();
15402
15491
  },
@@ -15610,6 +15699,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
15610
15699
  },
15611
15700
  });
15612
15701
  const dashboardAcpSession: DashboardSession = dashboardAcpBootResult.session;
15702
+ daemonAttached = !!opts.remote && dashboardAcpBootResult.mode !== 'in-process';
15613
15703
  // ⭐ `B3` — 턴 중 발화를 «도는 턴»에 넣을 때 쓰는 키.
15614
15704
  try { acpSessionIdRef.current = dashboardAcpSession.currentSessionId ?? null; } catch { /* 없으면 넣지 않는다 */ }
15615
15705
  pushDebugLine(C.muted('[acp-boot] dashboard ACP session ready'));
@@ -15701,7 +15791,20 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
15701
15791
  chatLines.push(C.muted(` ${resume.turns} turns restored. Next turn continues this conversation.`));
15702
15792
  }
15703
15793
  }
15794
+ if (dashboardAcpBootResult.mode !== 'resumed' && !chat.history.some((message) => message.role !== 'system')) {
15795
+ try {
15796
+ const project = suggestProjectForFolder(process.cwd());
15797
+ if (project) chatLines.push(C.muted(` Project suggestion: ${project.name} (${project.id})`));
15798
+ } catch { /* suggestions must not interrupt the dashboard conversation */ }
15799
+ }
15704
15800
  chatScrollOffset = -1;
15801
+ void renderDashboardFirstScreenBand({
15802
+ history: chat.history,
15803
+ lines: chatLines,
15804
+ width: Math.max(0, termSize().cols - 4),
15805
+ draw: () => draw(),
15806
+ observe: (data) => { try { debug.log('dashboard.first-screen-band', 'resolved', data); } catch { /* observation is best-effort */ } },
15807
+ });
15705
15808
 
15706
15809
  while (true) {
15707
15810
  draw();
@@ -16763,8 +16866,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
16763
16866
  lastTurnFinalStatus = undefined;
16764
16867
  turnTypeaheadRef.state = drained.state;
16765
16868
  nextInitial = drained.nextInitial;
16766
- const turnTypeaheadEcho = wireDashboardTurnTypeaheadEcho(drained);
16767
- if (turnTypeaheadEcho) chatLines.push(C.muted(turnTypeaheadEcho));
16869
+ const queuedTurnEndText = drained.injectEnter ? drained.nextInitial : undefined;
16768
16870
  if (interrupted && queuedBeforeDrain > 0) {
16769
16871
  debug.log('dashboard.turn-typeahead', 'restored-after-interrupt', { count: queuedBeforeDrain });
16770
16872
  }
@@ -18598,25 +18700,11 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
18598
18700
  // pattern. Without this draw(), the echo sits in the chatLines
18599
18701
  // array but doesn't paint until after the skill router's
18600
18702
  // classifier LLM call returns (~1-2s of visual silence).
18601
- chatLines.push('');
18602
- // Wrap the echoed user message to the log width so long input
18603
- // soft-wraps onto continuation rows instead of being truncated
18604
- // with a `\u2026+N` marker by the log-pane renderer (which keeps a
18605
- // strict 1-line-per-row invariant and only truncates lines that
18606
- // weren't pre-wrapped). termCols-6 leaves room for the log
18607
- // pane's 2-col left margin plus the 2-col hanging indent on
18608
- // continuation rows; the \u276f marker stays on the first row only.
18609
- const echoWrapCols = Math.max(20, termSize().cols - 6);
18610
- const echoRows = wrapAnsiByWidth(`\u276f ${tok.text}`, echoWrapCols);
18611
- if (echoRows.length === 0) {
18612
- chatLines.push(C.accent('\u276f '));
18613
- } else {
18614
- echoRows.forEach((row, i) => {
18615
- chatLines.push(C.accent(i === 0 ? row : ` ${row}`));
18616
- });
18703
+ echoSubmittedUserText(tok.text);
18704
+ if (queuedTurnEndText !== undefined && input.text === queuedTurnEndText) {
18705
+ debug.log('dashboard.turn-typeahead', 'echoed', { path: 'turn-end', length: queuedTurnEndText.length });
18617
18706
  }
18618
18707
  renderAttachmentSummary(tok.added);
18619
- chatScrollOffset = -1;
18620
18708
  draw();
18621
18709
 
18622
18710
  // Start the animated thinking indicator BEFORE the router so
@@ -18974,6 +19062,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
18974
19062
  createTurnStreamRuntime: (initialAssistantStart) => {
18975
19063
  latestTurnUsage = undefined;
18976
19064
  createDashboardTurnStreamRuntimeEscBoundary(escAbortToolState);
19065
+ liveTypeaheadEchoes.length = 0;
18977
19066
  const toolRendering = {
18978
19067
  ...getUserConfig().chat.rendering.tool,
18979
19068
  expandHint: false,
@@ -19013,6 +19102,11 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
19013
19102
  });
19014
19103
  return {
19015
19104
  ...turnStreamRuntime,
19105
+ onText: (chunk, accumulated) => {
19106
+ turnStreamRuntime.onText(chunk, accumulated);
19107
+ restoreLiveTypeaheadEchoes();
19108
+ draw();
19109
+ },
19016
19110
  onToolCall: (call) => {
19017
19111
  const toolCall = call as DashboardTurnStreamCall;
19018
19112
  trackEscAbortToolCall(escAbortToolState, toolCall);