elanous 0.2.9 → 0.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (361) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/2DsiDfDCks44g1cPUMhiW/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/24-e6d28380efab7faf.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/3615-740dd270764ffa24.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/3734-1015fe991eb9b7e7.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/5012-a7fe6f3c8b6fe45a.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/5783-ef40ebfc91d55230.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/62-ff227c3edb8b248b.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/7633-1b3709b0854c9412.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/7951-8b03dbb10b24cd06.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/8067-87ce253ec2f23697.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 8195-6fde3d7a39859206.js} +5 -5
  14. package/apps/pwa/out/_next/static/chunks/8894-b5fb36bab837cd29.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/9454-84d0d5d77446bd92.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/988-5f92e7ab7ccea3dd.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/9989-5582582f06429840.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/app/approvals/page-d2253d7e58ef1890.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-5441d4a9ab35e518.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/app/board/page-d9b12d680922d980.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-f972fc1c00a62e88.js → page-54b32bd27726588d.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/app/bots/page-e268d6d145827a22.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/app/chat/page-f8116559c7424da7.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/app/consult/page-1085399fd0f32112.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/control/page-1df686a14f65895a.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/app/design-check/page-54e1f966ab60a4dc.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/app/editor/{page-973996d584f008c1.js → page-7ed95f296f5ee8e9.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/exec/page-11edac668d77e5c3.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/field/page-59e2b9a22cd5a6d5.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/app/intake/page-2976df21ad74ab48.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/app/layout-34d211a67f16d510.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/live/page-8ff652d5d950639b.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/app/market/{page-8b0402a46a749a3d.js → page-b31610deda76337b.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-037f83e2a1edc8e2.js → page-6cb0e1bbefe5fb3d.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/morning/page-a286a152cf584576.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/not-found-d9237581e7003bcb.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/app/observatory/page-83274e761643fdd2.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-ceee6cfb1d4e6d68.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-9c9f8bb3e4a64091.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c17217603ea3426a.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/app/page-efb6177cbf1e8a51.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/app/reflection/page-d307523703bc4df3.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-462f4e4bab45ce7d.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/sessions/page-8990e96f33af3837.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-0df30e0c7e50148a.js → page-e168fa1030af0548.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/app/settings/page-1233e05a4068bc9c.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-13a1840b1fac337a.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/setup/page-64a29fc80b11ab55.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/share/page-68e787339a16b3dc.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/showroom/page-4fddc90b23feb215.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/tasks/page-886ba8260dc79c79.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/term/page-0f26507a1722e40b.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/today/page-109d4ccd42259c9d.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/trace/page-f5c2a4e510726774.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/vault/page-b2e0039416f42522.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-90670589b87abea8.js → page-26102fd7350f171e.js} +1 -1
  57. package/apps/pwa/out/_next/static/chunks/app/workflows/page-708948bc8a802794.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/workspace/page-5e5611620f78fb0b.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-35bedde6d885f08f.js → page-139ba5c367bbf60a.js} +1 -1
  60. package/apps/pwa/out/_next/static/chunks/main-app-202b4e81f33895ea.js +1 -0
  61. package/apps/pwa/out/_next/static/css/43feca5fd387dcbd.css +1 -0
  62. package/apps/pwa/out/approvals/index.html +1 -1
  63. package/apps/pwa/out/approvals/index.txt +14 -14
  64. package/apps/pwa/out/autopilot/index.html +1 -1
  65. package/apps/pwa/out/autopilot/index.txt +14 -14
  66. package/apps/pwa/out/board/index.html +1 -1
  67. package/apps/pwa/out/board/index.txt +14 -14
  68. package/apps/pwa/out/botlab/index.html +1 -1
  69. package/apps/pwa/out/botlab/index.txt +14 -14
  70. package/apps/pwa/out/bots/index.html +1 -1
  71. package/apps/pwa/out/bots/index.txt +14 -14
  72. package/apps/pwa/out/chat/index.html +1 -1
  73. package/apps/pwa/out/chat/index.txt +14 -14
  74. package/apps/pwa/out/consult/index.html +1 -1
  75. package/apps/pwa/out/consult/index.txt +14 -14
  76. package/apps/pwa/out/control/index.html +1 -1
  77. package/apps/pwa/out/control/index.txt +14 -14
  78. package/apps/pwa/out/design-check/index.html +1 -1
  79. package/apps/pwa/out/design-check/index.txt +14 -14
  80. package/apps/pwa/out/editor/index.html +1 -1
  81. package/apps/pwa/out/editor/index.txt +14 -14
  82. package/apps/pwa/out/exec/index.html +1 -1
  83. package/apps/pwa/out/exec/index.txt +14 -14
  84. package/apps/pwa/out/field/index.html +1 -1
  85. package/apps/pwa/out/field/index.txt +14 -14
  86. package/apps/pwa/out/healthz +1 -1
  87. package/apps/pwa/out/index.html +1 -1
  88. package/apps/pwa/out/index.txt +14 -14
  89. package/apps/pwa/out/intake/index.html +1 -1
  90. package/apps/pwa/out/intake/index.txt +14 -14
  91. package/apps/pwa/out/live/index.html +1 -1
  92. package/apps/pwa/out/live/index.txt +14 -14
  93. package/apps/pwa/out/market/index.html +1 -1
  94. package/apps/pwa/out/market/index.txt +14 -14
  95. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  96. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  97. package/apps/pwa/out/morning/index.html +1 -1
  98. package/apps/pwa/out/morning/index.txt +14 -14
  99. package/apps/pwa/out/observatory/index.html +1 -1
  100. package/apps/pwa/out/observatory/index.txt +14 -14
  101. package/apps/pwa/out/ops/checklist/index.html +1 -1
  102. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  103. package/apps/pwa/out/ops/release/index.html +1 -1
  104. package/apps/pwa/out/ops/release/index.txt +14 -14
  105. package/apps/pwa/out/ops/seats/index.html +1 -0
  106. package/apps/pwa/out/ops/seats/index.txt +30 -0
  107. package/apps/pwa/out/reflection/index.html +1 -1
  108. package/apps/pwa/out/reflection/index.txt +15 -15
  109. package/apps/pwa/out/scheduler/index.html +1 -1
  110. package/apps/pwa/out/scheduler/index.txt +14 -14
  111. package/apps/pwa/out/sessions/index.html +1 -1
  112. package/apps/pwa/out/sessions/index.txt +14 -14
  113. package/apps/pwa/out/settings/devices/index.html +1 -1
  114. package/apps/pwa/out/settings/devices/index.txt +14 -14
  115. package/apps/pwa/out/settings/index.html +1 -1
  116. package/apps/pwa/out/settings/index.txt +14 -14
  117. package/apps/pwa/out/setup/done/index.html +1 -1
  118. package/apps/pwa/out/setup/done/index.txt +14 -14
  119. package/apps/pwa/out/setup/index.html +1 -1
  120. package/apps/pwa/out/setup/index.txt +14 -14
  121. package/apps/pwa/out/share/index.html +1 -1
  122. package/apps/pwa/out/share/index.txt +14 -14
  123. package/apps/pwa/out/showroom/index.html +1 -1
  124. package/apps/pwa/out/showroom/index.txt +14 -14
  125. package/apps/pwa/out/tasks/index.html +1 -1
  126. package/apps/pwa/out/tasks/index.txt +14 -14
  127. package/apps/pwa/out/term/index.html +1 -1
  128. package/apps/pwa/out/term/index.txt +14 -14
  129. package/apps/pwa/out/today/index.html +1 -0
  130. package/apps/pwa/out/today/index.txt +33 -0
  131. package/apps/pwa/out/trace/index.html +1 -1
  132. package/apps/pwa/out/trace/index.txt +14 -14
  133. package/apps/pwa/out/vault/index.html +1 -1
  134. package/apps/pwa/out/vault/index.txt +14 -14
  135. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  136. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  137. package/apps/pwa/out/workflows/index.html +1 -1
  138. package/apps/pwa/out/workflows/index.txt +14 -14
  139. package/apps/pwa/out/workspace/index.html +1 -1
  140. package/apps/pwa/out/workspace/index.txt +14 -14
  141. package/apps/pwa/out/worktrees/index.html +1 -1
  142. package/apps/pwa/out/worktrees/index.txt +14 -14
  143. package/graphs/field/field-feed.yaml +2 -0
  144. package/graphs/implement-loop.yaml +1 -1
  145. package/package.json +1 -1
  146. package/plugins/card-followup/graphs/run-step.ts +48 -13
  147. package/plugins/doc-draft/graphs/doc-draft.yaml +3 -3
  148. package/plugins/doc-draft/graphs/run-step.ts +13 -6
  149. package/scripts/ci-pwa-build-gate.ts +160 -0
  150. package/scripts/coord-channel-watch.sh +42 -8
  151. package/scripts/coord-digest.ts +199 -0
  152. package/scripts/coord-post.sh +60 -3
  153. package/scripts/install.ps1 +68 -9
  154. package/scripts/install.sh +39 -12
  155. package/scripts/lib/pwa-scenarios/scenarios.ts +20 -0
  156. package/scripts/lib/tui-regress-checks.ts +146 -0
  157. package/scripts/memory-lifecycle-cycle.ts +2 -2
  158. package/scripts/ops-now-probe.ts +327 -0
  159. package/scripts/publish-internal-dist.ts +11 -1
  160. package/scripts/release-loop/cutoff.ts +19 -8
  161. package/scripts/release-loop/ops-upgrade-node.ts +72 -16
  162. package/scripts/release-loop/release-readiness.ts +9 -0
  163. package/scripts/release-loop/version-node.ts +58 -10
  164. package/scripts/repo-watch-cycle.ts +1 -1
  165. package/scripts/retro-cycle.ts +2 -2
  166. package/scripts/schedule-health-report.ts +1 -1
  167. package/scripts/storyboard/storyboard.ts +5 -161
  168. package/scripts/test-diet/lib.ts +30 -1
  169. package/scripts/test-diet/node.ts +82 -7
  170. package/scripts/test-timeout-audit.ts +114 -0
  171. package/scripts/tree-sync-alert.ts +1 -1
  172. package/scripts/tui-regress.ts +71 -0
  173. package/scripts/waitlist-daily-digest.ts +1 -1
  174. package/skills/explainer-video/SKILL.md +4 -0
  175. package/skills/explainer-video/engine/reel.mjs +18 -13
  176. package/skills/explainer-video/engine/reel.sh +24 -5
  177. package/skills/explainer-video/engine/stitch.mjs +145 -0
  178. package/skills/pitch-deck/SKILL.md +36 -0
  179. package/skills/pitch-deck/template.html +80 -0
  180. package/src/acp/codex-approval-adapter.ts +9 -3
  181. package/src/acp/core-turn-bridge.ts +117 -18
  182. package/src/acp/fact-question.ts +11 -0
  183. package/src/acp/server.ts +48 -7
  184. package/src/acp/tool-approval.ts +126 -0
  185. package/src/acp/turn-runner.ts +36 -0
  186. package/src/agent/monad-agent-turn.ts +8 -0
  187. package/src/ask-user-question/pending-questions.ts +25 -10
  188. package/src/ask-user-question/types.ts +9 -0
  189. package/src/auth/setup-link-tokens.ts +16 -5
  190. package/src/autopilot/build/integrity-gate.ts +12 -0
  191. package/src/boot/daemon-prompt-turn.ts +3 -0
  192. package/src/boot/daemon-tools/index.ts +8 -0
  193. package/src/boot/daemon-tools/types.ts +2 -0
  194. package/src/boot/daemon-tools/web-search.ts +1 -0
  195. package/src/chat/ctrl-c-in-input.ts +14 -0
  196. package/src/chat/index.ts +52 -0
  197. package/src/chat/pickers/state.ts +6 -2
  198. package/src/cli/agent-cli.ts +7 -0
  199. package/src/cli/decisions-cli.ts +25 -3
  200. package/src/cli/doctor-bundle-upload.ts +68 -0
  201. package/src/cli/doctor-cli.ts +38 -4
  202. package/src/cli/ops-cli.ts +20 -0
  203. package/src/cli/pod-cli.ts +49 -0
  204. package/src/cli/pr-cli.ts +31 -2
  205. package/src/cli/pwa-stop.ts +163 -13
  206. package/src/cli/pwa-test.ts +3 -0
  207. package/src/cli/release-cli.ts +63 -20
  208. package/src/cli/self-update.ts +14 -0
  209. package/src/cli/storyboard-cli.ts +37 -0
  210. package/src/cli/test-state-dir-flag.ts +11 -4
  211. package/src/connectors/linear.ts +146 -0
  212. package/src/context-bus/coord-events.ts +115 -0
  213. package/src/dashboard/dashboard-boot-fixture.ts +23 -0
  214. package/src/dashboard/index.ts +58 -21
  215. package/src/dashboard/slash-runtime/dashboard-handlers.ts +7 -0
  216. package/src/dashboard/slash-runtime/help-from-registry.ts +43 -28
  217. package/src/decisions/decision-cards.ts +49 -1
  218. package/src/decisions/decision-ledger.ts +75 -4
  219. package/src/decisions/decision-linear-projection.ts +157 -0
  220. package/src/decisions/web-push-decision-cards.ts +50 -0
  221. package/src/directive/interpretation-card.ts +36 -0
  222. package/src/discord.ts +2 -1
  223. package/src/domains/coo-admin-tool.ts +62 -0
  224. package/src/domains/core-tools.ts +11 -3
  225. package/src/domains/elanous-card.ts +29 -0
  226. package/src/domains/ops-facts-tool.ts +76 -0
  227. package/src/domains/outbound-alert.ts +41 -13
  228. package/src/domains/release-tool.ts +168 -0
  229. package/src/domains/telegram-kind-route.ts +19 -1
  230. package/src/exec-requests/planner.ts +98 -28
  231. package/src/exec-requests/runner.ts +107 -10
  232. package/src/exec-requests/store.ts +8 -2
  233. package/src/field/field-media.ts +10 -7
  234. package/src/field/field-reel.ts +14 -4
  235. package/src/git-fs/worktree.ts +45 -6
  236. package/src/graph-runner/graph-approval-card.ts +113 -0
  237. package/src/graph-runner/graph-cli.ts +129 -4
  238. package/src/graph-runner/graph-notify.ts +25 -3
  239. package/src/graph-runner/runner.ts +142 -16
  240. package/src/harness/browser-verify.ts +25 -2
  241. package/src/harness/harness-cli-command.ts +231 -35
  242. package/src/harness/harness-incidents-writer.fixture.ts +24 -0
  243. package/src/harness/harness-incidents.ts +129 -0
  244. package/src/harness/harness-stop.ts +53 -3
  245. package/src/hitl/clarification-policy.ts +3 -1
  246. package/src/index.ts +156 -37
  247. package/src/intake-plane/author-goals.ts +37 -4
  248. package/src/intake-plane/check.ts +3 -0
  249. package/src/intake-plane/discord-seat-work.ts +47 -2
  250. package/src/intake-plane/seat-doc-route.ts +170 -6
  251. package/src/intake-plane/telegram-seat-work.ts +44 -2
  252. package/src/intake-plane/telegram-work.ts +1 -0
  253. package/src/maturity/cli-maturity.ts +34 -0
  254. package/src/maturity/feature-maturity.ts +98 -1
  255. package/src/maturity/tui-slash-maturity.ts +31 -0
  256. package/src/memory.ts +32 -4
  257. package/src/nexus/api/exec-requests.ts +33 -3
  258. package/src/nexus/api/http-server.ts +53 -1
  259. package/src/nexus/api/meta-api.ts +11 -0
  260. package/src/nexus/api/ops-api.ts +10 -1
  261. package/src/nexus/api/ops-seats-sources.ts +37 -7
  262. package/src/nexus/api/ops-seats.ts +16 -6
  263. package/src/nexus/api/seat-requests.ts +88 -8
  264. package/src/nexus/api/sessions-store.ts +63 -0
  265. package/src/nexus/api/setup-codex-login.ts +160 -0
  266. package/src/nexus/api/usage-api.ts +102 -0
  267. package/src/nexus/index.ts +45 -4
  268. package/src/onboarding/first-run.ts +89 -0
  269. package/src/onboarding/lan-address.ts +45 -0
  270. package/src/onboarding/web-first.ts +119 -24
  271. package/src/onboarding.ts +4 -0
  272. package/src/ops-now/ops-now-refresh.ts +100 -0
  273. package/src/project/project-store.ts +115 -0
  274. package/src/release-loop/checklist.ts +78 -4
  275. package/src/release-loop/feature-store.ts +154 -21
  276. package/src/release-loop/release-schedule.ts +53 -0
  277. package/src/repl/index.ts +7 -0
  278. package/src/seat-dispatch/ceo-commands.ts +41 -8
  279. package/src/seat-dispatch/ceo-intent.ts +38 -0
  280. package/src/seat-loop/seat-loop.ts +231 -0
  281. package/src/seat-loop/seat-report.ts +64 -0
  282. package/src/self-dev/decompose.ts +66 -11
  283. package/src/self-dev/run-store.ts +10 -1
  284. package/src/self-implement/execution-clarification.ts +115 -0
  285. package/src/self-implement/gate-cli.ts +6 -3
  286. package/src/self-implement/orchestrator.ts +37 -3
  287. package/src/self-implement/pipeline-shape.ts +1 -1
  288. package/src/self-implement/run-status-mapping.ts +5 -3
  289. package/src/session/chat.ts +2 -0
  290. package/src/session/index.ts +9 -0
  291. package/src/shell-env-bootstrap.ts +17 -0
  292. package/src/steward/directive.ts +51 -2
  293. package/src/steward/launch.ts +250 -0
  294. package/src/steward/outcome-notify.ts +115 -0
  295. package/src/steward/steward-cards.ts +8 -2
  296. package/src/steward/triage-plan.ts +32 -4
  297. package/src/steward/triage.ts +93 -9
  298. package/src/steward/working-backwards.ts +64 -0
  299. package/src/storyboard/storyboard.ts +141 -0
  300. package/src/task-cards/card-store.ts +2 -2
  301. package/src/task-orchestrator/surfaces/pod-lease.ts +260 -0
  302. package/src/telegram-card-followup.ts +233 -0
  303. package/src/telegram-report.ts +15 -9
  304. package/src/telegram-run.ts +7 -0
  305. package/src/telegram.ts +29 -8
  306. package/src/tool-runtime/types.ts +4 -0
  307. package/src/tui-client/dashboard-session.ts +2 -0
  308. package/src/user-config.ts +118 -11
  309. package/src/version/packed-revision.json +1 -1
  310. package/src/web-push/notify-exec-request.ts +32 -0
  311. package/apps/pwa/out/_next/static/bFI5EEwg7T6aG88ibX4Zq/_buildManifest.js +0 -1
  312. package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
  313. package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
  314. package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
  315. package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +0 -1
  316. package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +0 -1
  317. package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
  318. package/apps/pwa/out/_next/static/chunks/7032-f4ef698518fba61c.js +0 -1
  319. package/apps/pwa/out/_next/static/chunks/8067-d976b441a104ab74.js +0 -1
  320. package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +0 -1
  321. package/apps/pwa/out/_next/static/chunks/8630-cdfc61e82c653ffc.js +0 -1
  322. package/apps/pwa/out/_next/static/chunks/8894-e263b332208e3957.js +0 -1
  323. package/apps/pwa/out/_next/static/chunks/9454-139040d37c79dbc9.js +0 -1
  324. package/apps/pwa/out/_next/static/chunks/9740-8f5ff96c522b5f55.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/app/approvals/page-fb7c2d458f32fcad.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-35e2713597dd24d1.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/board/page-918b656f064577ba.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/bots/page-5f30e00693527ebd.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/chat/page-7fbae173b3c103a7.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/consult/page-3b4edcce364062ea.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/control/page-c5794f484c251f44.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/design-check/page-1130d8eefcd87e76.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/exec/page-016e2bda04eecb1f.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/field/page-b4d6d9a3b56f2940.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/intake/page-ec49a466121233ba.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/app/layout-f72db356744ceade.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/app/live/page-a6fd9b50d1b85378.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/app/morning/page-adfbd2ba9598e59a.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/app/not-found-46fedff57d4bec67.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/app/observatory/page-0cf318e9dcee2bbf.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-899b327cb8b6845c.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-6c1e6ba743a29dfe.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/app/page-1a8e4807292f6b17.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/app/reflection/page-28deba6bcb834916.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-de68e2205794037d.js +0 -1
  346. package/apps/pwa/out/_next/static/chunks/app/sessions/page-c71c33a573230672.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/app/settings/page-4fc56790fe0d2682.js +0 -1
  348. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-59df2aebb38a7c1e.js +0 -1
  349. package/apps/pwa/out/_next/static/chunks/app/setup/page-4647be5dbcbd0795.js +0 -1
  350. package/apps/pwa/out/_next/static/chunks/app/share/page-e025892bda74b743.js +0 -1
  351. package/apps/pwa/out/_next/static/chunks/app/showroom/page-a0955de73b89f078.js +0 -1
  352. package/apps/pwa/out/_next/static/chunks/app/tasks/page-369846d2921af584.js +0 -1
  353. package/apps/pwa/out/_next/static/chunks/app/term/page-72cbc1c8cdd0f65d.js +0 -1
  354. package/apps/pwa/out/_next/static/chunks/app/trace/page-77b28bfb0996d5ea.js +0 -1
  355. package/apps/pwa/out/_next/static/chunks/app/vault/page-3220c4934f49c6c2.js +0 -1
  356. package/apps/pwa/out/_next/static/chunks/app/workflows/page-7df9243ed021a4cc.js +0 -1
  357. package/apps/pwa/out/_next/static/chunks/app/workspace/page-4498c3853f4f688d.js +0 -1
  358. package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +0 -1
  359. package/apps/pwa/out/_next/static/css/f92f63a5df0ba229.css +0 -1
  360. package/src/dashboard/turn-typeahead-echo.ts +0 -23
  361. /package/apps/pwa/out/_next/static/{bFI5EEwg7T6aG88ibX4Zq → 2DsiDfDCks44g1cPUMhiW}/_ssgManifest.js +0 -0
@@ -1,5 +1,9 @@
1
- import { extname } from 'node:path';
1
+ import { realpathSync, statSync } from 'node:fs';
2
+ import { extname, isAbsolute, join, sep } from 'node:path';
2
3
  import { ExecRequestRunner } from '../../exec-requests/runner.js';
4
+ import type { ExecAttachment } from '../../exec-requests/store.js';
5
+ import { getElanousConfigDir } from '../../elanous-config-dir.js';
6
+ import { defaultAttachmentBaseDir } from '../../boot/attachment-store.js';
3
7
  import { jsonResponse } from './json-response.js';
4
8
 
5
9
  const mime: Record<string, string> = {
@@ -8,7 +12,21 @@ const mime: Record<string, string> = {
8
12
  '.md': 'text/markdown; charset=utf-8', '.txt': 'text/plain; charset=utf-8',
9
13
  };
10
14
 
11
- export async function handleExecRequests(req: Request, runner: ExecRequestRunner): Promise<Response> {
15
+ function resolveAttachment(path: string, roots: string[]): string | null {
16
+ if (!isAbsolute(path)) return null;
17
+ try {
18
+ const real = realpathSync(path);
19
+ if (!statSync(real).isFile()) return null;
20
+ return roots.some(root => {
21
+ try {
22
+ const base = realpathSync(root);
23
+ return real.startsWith(`${base}${sep}`);
24
+ } catch { return false; }
25
+ }) ? real : null;
26
+ } catch { return null; }
27
+ }
28
+
29
+ export async function handleExecRequests(req: Request, runner: ExecRequestRunner, uploadDir = defaultAttachmentBaseDir(), fieldDir = join(getElanousConfigDir(), 'field')): Promise<Response> {
12
30
  const path = new URL(req.url).pathname;
13
31
  if (path === '/v1/exec-requests') {
14
32
  if (req.method === 'POST') {
@@ -16,7 +34,19 @@ export async function handleExecRequests(req: Request, runner: ExecRequestRunner
16
34
  try { body = await req.json(); } catch { return jsonResponse({ error: 'bad_request' }, 400); }
17
35
  const text = body && typeof body === 'object' && !Array.isArray(body) ? (body as { text?: unknown }).text : undefined;
18
36
  if (typeof text !== 'string' || !text.trim()) return jsonResponse({ error: 'bad_request', reason: 'text required' }, 400);
19
- const item = runner.submit(text.trim());
37
+ const supplied = (body as { attachments?: unknown }).attachments;
38
+ if (supplied !== undefined && (!Array.isArray(supplied) || supplied.some(entry =>
39
+ !entry || typeof entry !== 'object' || Array.isArray(entry)
40
+ || typeof entry.name !== 'string' || !entry.name.trim() || typeof entry.path !== 'string'))) {
41
+ return jsonResponse({ error: 'bad_request', reason: 'attachments must be [{name,path}]' }, 400);
42
+ }
43
+ const roots = [uploadDir, fieldDir];
44
+ const resolved: Array<{ name: string; path: string | null }> | undefined = supplied?.map((entry: ExecAttachment) => ({
45
+ name: entry.name, path: resolveAttachment(entry.path, roots),
46
+ }));
47
+ if (resolved?.some(entry => !entry.path)) return jsonResponse({ error: 'attachment-outside' }, 400);
48
+ const attachments: ExecAttachment[] | undefined = resolved?.map(entry => ({ name: entry.name, path: entry.path! }));
49
+ const item = runner.submit(text.trim(), attachments);
20
50
  return jsonResponse({ id: item.id, status: 'planning' }, 202);
21
51
  }
22
52
  if (req.method === 'GET') return jsonResponse({ items: runner.store.list().map(item => {
@@ -19,6 +19,8 @@ import { setInProcessOutbound } from '../../domains/outbound-alert.js';
19
19
  import { getUserConfig } from '../../user-config.js';
20
20
  import type { TelegramEvent } from '../../workflow-runtime/triggers/telegram-source.js';
21
21
  import { spawnSync } from 'node:child_process';
22
+ import { isAbsolute } from 'node:path';
23
+ import { createProject, listProjects } from '../../project/project-store.js';
22
24
  import { compareTokenConstTime } from '../../acp/transport/auth.js';
23
25
  import { ensureAuthToken } from '../../auth/acp-token.js';
24
26
  import type { NexusState } from '../state/state.js';
@@ -134,6 +136,7 @@ import {
134
136
  type ReflectionRouteOpts,
135
137
  } from './reflection.js';
136
138
  import { handleSignalBoard } from './signal-board-api.js';
139
+ import { handleUsageGet } from './usage-api.js';
137
140
  import {
138
141
  handleDashboard,
139
142
  handleDashboardRefreshLive,
@@ -163,6 +166,7 @@ import { handleLlmModels } from './llm-models.js';
163
166
  import { handleLlmHostsConfig } from './llm-hosts-config.js';
164
167
  import { handleLlmRotationGet, handleLlmRotationNext } from './llm-rotation.js';
165
168
  import { handleSetupClaim } from './setup-claim.js';
169
+ import { handleSetupCodexLoginGet, handleSetupCodexLoginPost } from './setup-codex-login.js';
166
170
  import {
167
171
  handleLlmProvidersList,
168
172
  handleLlmProviderSet,
@@ -278,7 +282,7 @@ import { handleSchedulesActionPost } from './schedules-action.js';
278
282
  import { handleSchedulesList, handleScheduleDetail, handleScheduleRuns } from './schedules-read.js';
279
283
  import { handleIntakeRunsList } from './intake-runs.js';
280
284
  import { handleMissionsList, handleMissionDetail } from './missions.js';
281
- import { handleSessionsStoreList, handleSessionsStoreGet, handleSessionsStoreFork, handleSessionsStoreDelete } from './sessions-store.js';
285
+ import { handleSessionsStoreList, handleSessionsStoreGet, handleSessionsStoreFork, handleSessionsStoreDelete, handleSessionsStorePatch } from './sessions-store.js';
282
286
  import { handleLogsQuery, handleLogsStream, handleLogsLevelGet, handleLogsLevelPost, handleLogsFacets, handleLogsHistogram, handleLogsInstances } from './log-fabric.js';
283
287
  import { handleTrace, handleTraceEvidence } from './trace.js';
284
288
  import { handleDispatchRunsList } from './dispatch-runs.js';
@@ -465,6 +469,8 @@ export interface NexusHttpServerOpts {
465
469
  state: NexusState;
466
470
  registry: TabRegistry;
467
471
  eventBus: NexusEventBus;
472
+ /** Test seam for the read-only usage collector; production uses the cached handler. */
473
+ usageHandler?: () => Promise<Response>;
468
474
  /** Supervisor handle — required for the write API (PR ι). When omitted
469
475
  * (skipSupervisor mode), mutation routes return 503. */
470
476
  supervisor?: Supervisor;
@@ -795,6 +801,27 @@ export function startNexusHttpServer(opts: NexusHttpServerOpts): NexusHttpServer
795
801
  };
796
802
  }
797
803
 
804
+ export function handleProjectsGet(): Response {
805
+ return jsonResponse({ projects: listProjects() });
806
+ }
807
+
808
+ export async function handleProjectsPost(req: Request): Promise<Response> {
809
+ let body: unknown;
810
+ try { body = await req.json(); }
811
+ catch { return jsonResponse({ error: 'invalid_json' }, 400); }
812
+ if (!body || typeof body !== 'object' || Array.isArray(body)) {
813
+ return jsonResponse({ error: 'invalid_name' }, 400);
814
+ }
815
+ const { name, primaryFolder } = body as Record<string, unknown>;
816
+ if (typeof name !== 'string' || !name.trim() || name.length > 80) {
817
+ return jsonResponse({ error: 'invalid_name' }, 400);
818
+ }
819
+ if (primaryFolder !== undefined && (typeof primaryFolder !== 'string' || !isAbsolute(primaryFolder))) {
820
+ return jsonResponse({ error: 'invalid_primary_folder' }, 400);
821
+ }
822
+ return jsonResponse({ project: createProject({ name, ...(primaryFolder !== undefined ? { primaryFolder } : {}) }) }, 201);
823
+ }
824
+
798
825
  function isPortBusy(err: unknown): boolean {
799
826
  if (!err) return false;
800
827
  // Bun.serve errors expose `.code === 'EADDRINUSE'` (Node-compatible)
@@ -1285,11 +1312,22 @@ export async function routeRequest(
1285
1312
  return handleGraphsValidatePost(req, opts.metaApi);
1286
1313
  }
1287
1314
 
1315
+ if (pathname === '/v1/projects' && (method === 'GET' || method === 'POST')) {
1316
+ if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
1317
+ return method === 'GET' ? handleProjectsGet() : handleProjectsPost(req);
1318
+ }
1319
+
1288
1320
  if (pathname === '/v1/config/chat-fast-path' && (method === 'GET' || method === 'PUT')) {
1289
1321
  if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
1290
1322
  return handleChatFastPathConfig(req);
1291
1323
  }
1292
1324
 
1325
+ if (pathname === '/v1/usage') {
1326
+ if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
1327
+ if (method !== 'GET') return jsonResponse({ error: 'method-not-allowed' }, 405);
1328
+ return (opts.usageHandler ?? handleUsageGet)();
1329
+ }
1330
+
1293
1331
  // Mutation routes (PR ι) — POST/PATCH/DELETE on /v1/nexus/tabs[/:id[/action]].
1294
1332
  // Templates POST handled here too (PR κ).
1295
1333
  // Config / secrets PUT/POST/DELETE here too (PR μ).
@@ -1540,6 +1578,11 @@ export async function routeRequest(
1540
1578
  // PWA `/setup` wizard Phase 1 (2026-05-19) — LLM provider 첫 셋업.
1541
1579
  // POST 는 mutation 블록 안에서 등록 (memory `feedback_post_route_must_be_in_method_block`).
1542
1580
  // GET sibling 은 아래 GET-only 블록에 등록.
1581
+ if (pathname === '/v1/setup/codex-login' && (method === 'POST' || method === 'OPTIONS')) {
1582
+ if (method === 'OPTIONS') return corsPreflight('GET, POST, OPTIONS');
1583
+ if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
1584
+ return handleSetupCodexLoginPost(req);
1585
+ }
1543
1586
  if (pathname === '/v1/setup/llm-provider' && (method === 'POST' || method === 'OPTIONS')) {
1544
1587
  if (method === 'POST') { if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401); }
1545
1588
  return handleLlmProviderSet(req);
@@ -1904,6 +1947,11 @@ export async function routeRequest(
1904
1947
  const id = decodeURIComponent(pathname.slice('/v1/sessions/store/'.length, -'/fork'.length));
1905
1948
  return handleSessionsStoreFork(req, id, opts.metaApi);
1906
1949
  }
1950
+ if (pathname.startsWith('/v1/sessions/store/') && !pathname.endsWith('/fork') && method === 'PATCH') {
1951
+ if (!opts.metaApi) return jsonResponse({ error: 'unauthorized' }, 401);
1952
+ const id = decodeURIComponent(pathname.slice('/v1/sessions/store/'.length));
1953
+ return handleSessionsStorePatch(req, id, opts.metaApi);
1954
+ }
1907
1955
  // on-disk 세션 삭제(파괴적) — 라이브 세션 관리. /store/:id (fork/transcript 와 구분).
1908
1956
  if (pathname.startsWith('/v1/sessions/store/') && !pathname.endsWith('/fork') && method === 'DELETE') {
1909
1957
  if (!opts.metaApi) return jsonResponse({ error: 'meta-api-runtime-not-wired' }, 503);
@@ -2425,6 +2473,10 @@ export async function routeRequest(
2425
2473
  }
2426
2474
  // PWA `/setup` wizard Phase 1 (2026-05-19) — provider catalog 표시.
2427
2475
  // POST sibling (`/v1/setup/llm-provider`) 는 위 mutation 블록.
2476
+ if (pathname === '/v1/setup/codex-login') {
2477
+ if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
2478
+ return handleSetupCodexLoginGet();
2479
+ }
2428
2480
  if (pathname === '/v1/setup/llm-providers') {
2429
2481
  return handleLlmProvidersList(req);
2430
2482
  }
@@ -147,6 +147,10 @@ export interface MetaApiOpts {
147
147
  * compared constant-time against the `Authorization: Bearer …`
148
148
  * header. */
149
149
  bearerToken?: string;
150
+ /** Server-side human identity verification, independent of the shared bearer credential.
151
+ * Never derive this result from request body, claimed source, or bearer possession alone.
152
+ * Without a verifier, prompt tool mutations remain unavailable. */
153
+ resolveVerifiedOwner?: (req: Request) => { id: string } | undefined;
150
154
  /** Convenience for Tailscale-only / dogfood-loopback mode. */
151
155
  noAuth?: boolean;
152
156
  // ── PR k · runtime DI for the lifted endpoints ─────────────────
@@ -788,6 +792,11 @@ function notFound(payload: Record<string, unknown> = {}): Response {
788
792
 
789
793
  // ── /v1/prompt ────────────────────────────────────────────────────
790
794
 
795
+ export function promptVerifiedOwner(req: Request, opts: MetaApiOpts): { id: string } | undefined {
796
+ const owner = opts.resolveVerifiedOwner?.(req);
797
+ return typeof owner?.id === 'string' && owner.id.trim() ? { id: owner.id.trim() } : undefined;
798
+ }
799
+
791
800
  export async function handlePromptPost(
792
801
  req: Request,
793
802
  opts: MetaApiOpts,
@@ -831,6 +840,7 @@ export async function handlePromptPost(
831
840
  surface: surfaceResolution.surface,
832
841
  surfaceResolutionReason: surfaceResolution.reason,
833
842
  ...(parsed.value.userContent ? { promptBlocks: parsed.value.userContent } : {}),
843
+ ...((owner) => owner ? { verifiedOwner: owner } : {})(promptVerifiedOwner(req, opts)),
834
844
  ...(resolvePromptHitlChannels(typeof body.sessionId === 'string' ? body.sessionId : '', opts) ?? {}),
835
845
  ...(effectiveSurface ? { toolSurface: effectiveSurface } : {}),
836
846
  ...(opts.toolCwd ? { toolCwd: opts.toolCwd } : {}),
@@ -1188,6 +1198,7 @@ export async function handlePromptStreamPost(
1188
1198
  surface: surfaceResolution.surface,
1189
1199
  surfaceResolutionReason: surfaceResolution.reason,
1190
1200
  ...(parsed.value.userContent ? { promptBlocks: parsed.value.userContent } : {}),
1201
+ ...((owner) => owner ? { verifiedOwner: owner } : {})(promptVerifiedOwner(req, opts)),
1191
1202
  ...(resolvePromptHitlChannels(typeof body.sessionId === 'string' ? body.sessionId : '', opts) ?? {}),
1192
1203
  ...(effectiveSurface ? { toolSurface: effectiveSurface } : {}),
1193
1204
  ...(opts.toolCwd ? { toolCwd: opts.toolCwd } : {}),
@@ -3,6 +3,7 @@ import { join } from 'node:path';
3
3
  import { debug, redactSecretText } from '../../debug/log.js';
4
4
  import { effectiveInstanceRoot } from '../../instance/resolve.js';
5
5
  import { listChecklist } from '../../release-loop/checklist.js';
6
+ import { getSchedule } from '../../release-loop/release-schedule.js';
6
7
  import { jsonResponse } from './json-response.js';
7
8
  import { operatorSignal } from './operator.js';
8
9
  import { createSeatsCache, todayKst } from './ops-seats.js';
@@ -90,7 +91,15 @@ export function handleOpsApi(req: Request, metaApi: MetaApiOpts | undefined): Re
90
91
  const version = url.searchParams.get('version');
91
92
  if (pathname === '/v1/ops/checklist') {
92
93
  if (!version || !VERSION.test(version)) return jsonResponse({ error: 'invalid-version' }, 400);
93
- return served(jsonResponse(listChecklist(version)));
94
+ // OPS2 header «컷 · 착지 마감»: the version's row in the release schedule (REL9a), or null when none is set.
95
+ let schedule: { cutAt: string; landBy: string | null } | null = null;
96
+ try {
97
+ const row = getSchedule(version);
98
+ schedule = row ? { cutAt: row.cutAt, landBy: row.landBy ?? null } : null;
99
+ } catch (error) {
100
+ debug.log('ops.api', 'schedule-unreadable', { version, reason: error instanceof Error ? error.message.slice(0, 80) : 'unknown' }, { level: 'warn' });
101
+ }
102
+ return served(jsonResponse({ ...listChecklist(version), schedule }));
94
103
  }
95
104
  if (pathname === '/v1/ops/release/runs') {
96
105
  if (version !== null && !VERSION.test(version)) return jsonResponse({ error: 'invalid-version' }, 400);
@@ -1,9 +1,10 @@
1
1
  // Live sources for /v1/ops/seats: the coordination PR and merged PRs through gh (automation App identity, proxy variables
2
2
  // removed — same rule as decision replies), the release checklist, and the decision ledger. Each failure becomes null upstream.
3
3
  import { DecisionLedger } from '../../decisions/decision-ledger.js';
4
+ import { listCoordEvents, type CoordEvent } from '../../context-bus/coord-events.js';
4
5
  import { devVersion, listChecklist, type ChecklistItem } from '../../release-loop/checklist.js';
5
6
  import { getUserConfig } from '../../user-config.js';
6
- import { kstDayRange, type ChannelComment, type MergedPr, type SeatsSources } from './ops-seats.js';
7
+ import { SEATS, kstDayRange, type ChannelComment, type ChannelSource, type MergedPr, type SeatsSources } from './ops-seats.js';
7
8
 
8
9
  const GH_TIMEOUT_MS = 20_000;
9
10
 
@@ -29,19 +30,48 @@ function channelTarget(): { repo: string; pr: string } | null {
29
30
  return m ? { repo: m[1]!, pr: m[2]! } : null;
30
31
  }
31
32
 
33
+ export async function channelCommentsSince(sinceIso: string): Promise<Array<{ body: string; createdAt: string; url: string }> | null> {
34
+ const target = channelTarget();
35
+ if (!target) return null;
36
+ const lines = await ghLines(['api', '--paginate', `repos/${target.repo}/issues/${target.pr}/comments?since=${sinceIso}&per_page=100`,
37
+ '--jq', '.[] | {body: .body, createdAt: .created_at, url: .html_url} | tojson']);
38
+ return lines?.map((line) => JSON.parse(line) as { body: string; createdAt: string; url: string }) ?? null;
39
+ }
40
+
32
41
  function nextPatch(version: string): string {
33
42
  const [major, minor, patch] = version.split('.').map(Number);
34
43
  return `${major}.${minor}.${patch! + 1}`;
35
44
  }
36
45
 
37
- export function liveSeatsSources(): SeatsSources {
46
+ export function liveSeatsSources(deps: { listEvents?: typeof listCoordEvents; githubChannel?: typeof channelCommentsSince } = {}): SeatsSources {
38
47
  return {
39
48
  async channel(date) {
40
- const target = channelTarget();
41
- if (!target) return null;
42
- const lines = await ghLines(['api', '--paginate', `repos/${target.repo}/issues/${target.pr}/comments?since=${kstDayRange(date).start}&per_page=100`,
43
- '--jq', '.[] | {body: .body, createdAt: .created_at} | tojson']);
44
- return lines?.map((line) => JSON.parse(line) as ChannelComment) ?? null;
49
+ const since = kstDayRange(date).start;
50
+ let events: CoordEvent[] = [];
51
+ try { events = (deps.listEvents ?? listCoordEvents)({ since }); } catch { /* GitHub fallback */ }
52
+ const latest = new Map<string, CoordEvent>();
53
+ for (const event of events) {
54
+ if (!SEATS.some(({ seat }) => seat === event.refs.seat)) continue;
55
+ const prior = latest.get(event.refs.seat);
56
+ if (!prior || event.at > prior.at) latest.set(event.refs.seat, event);
57
+ }
58
+ const seatSources: Partial<Record<(typeof SEATS)[number]['seat'], ChannelSource>> = {};
59
+ if (latest.size) {
60
+ const comments: ChannelComment[] = [];
61
+ for (const { seat } of SEATS) {
62
+ const event = latest.get(seat);
63
+ if (!event) { seatSources[seat] = 'unknown'; continue; }
64
+ comments.push({ body: `**[${seat}]** ${event.text}`, createdAt: event.at, url: event.refs.url ?? '' });
65
+ seatSources[seat] = 'ledger';
66
+ }
67
+ return { source: 'ledger', comments, seatSources };
68
+ }
69
+ let github: ChannelComment[] | null;
70
+ try { github = await (deps.githubChannel ?? channelCommentsSince)(since); }
71
+ catch { github = null; }
72
+ if (github === null) return null;
73
+ for (const { seat } of SEATS) seatSources[seat] = 'github';
74
+ return { source: 'github', comments: github, seatSources };
45
75
  },
46
76
  async merged(date) {
47
77
  const target = channelTarget();
@@ -1,7 +1,7 @@
1
1
  // GET /v1/ops/seats?date=YYYY-MM-DD — one row per seat: latest channel line, PRs landed that day, red items, open decisions.
2
2
  // A source that could not be read is null, never 0 («못 읽음» ≠ «없음»). Contract: 내부 문서 `DESIGN-ops3-seat-board-and-public-demo-2026-10-02` §1.
3
3
  import { debug, redactSecretText } from '../../debug/log.js';
4
- import type { ChecklistItem } from '../../release-loop/checklist.js';
4
+ import { parseOwner, type ChecklistItem } from '../../release-loop/checklist.js';
5
5
  import { canonicalSeatId } from '../../msg/msg-store.js';
6
6
 
7
7
  export const SEATS = [
@@ -9,11 +9,17 @@ export const SEATS = [
9
9
  ] as const;
10
10
  export const SEATS_CACHE_MS = 60_000;
11
11
 
12
- export interface ChannelComment { body: string; createdAt: string }
12
+ export interface ChannelComment { body: string; createdAt: string; url?: string }
13
+ export type ChannelSource = 'ledger' | 'github' | 'unreadable' | 'unknown';
14
+ export type ChannelResult = ChannelComment[] | {
15
+ comments: ChannelComment[];
16
+ source: 'ledger' | 'github';
17
+ seatSources?: Partial<Record<(typeof SEATS)[number]['seat'], ChannelSource>>;
18
+ } | null;
13
19
  export interface MergedPr { number: number; title: string; body: string; mergedAt: string }
14
20
  export interface SeatsSources {
15
21
  /** Coordination-channel comments created on `date` (KST) or later; null when unreadable. */
16
- channel(date: string): Promise<ChannelComment[] | null>;
22
+ channel(date: string): Promise<ChannelResult>;
17
23
  /** PRs merged on `date` (KST); null when unreadable. */
18
24
  merged(date: string): Promise<MergedPr[] | null>;
19
25
  /** Checklist items of the version being worked on (counts, red) and of every version known (id → owner). */
@@ -24,6 +30,7 @@ export interface SeatsSources {
24
30
 
25
31
  export interface SeatRow {
26
32
  seat: string; role: string;
33
+ channelSource: ChannelSource;
27
34
  now: { text: string; at: string } | null;
28
35
  landed: Array<{ pr: number; title: string; at: string; checklistId: string | null }> | null;
29
36
  blocked: Array<{ id: string; title: string; status: 'red' }> | null;
@@ -33,7 +40,7 @@ export interface SeatRow {
33
40
 
34
41
  function seatOf(value: string | undefined): string | null {
35
42
  if (!value) return null;
36
- try { return canonicalSeatId(value); } catch { return null; }
43
+ try { return canonicalSeatId(value.includes('/') ? parseOwner(value).seat : value); } catch { return null; }
37
44
  }
38
45
 
39
46
  const clip = (text: string, max: number) => redactSecretText(text).slice(0, max);
@@ -49,9 +56,11 @@ function checklistIdOf(pr: MergedPr, ids: string[]): string | null {
49
56
  }
50
57
 
51
58
  export async function buildSeatsBoard(date: string, sources: SeatsSources): Promise<{ date: string; seats: SeatRow[] }> {
52
- const [channel, merged] = await Promise.all([
59
+ const [channelResult, merged] = await Promise.all([
53
60
  sources.channel(date).catch(() => null), sources.merged(date).catch(() => null),
54
61
  ]);
62
+ const channel = Array.isArray(channelResult) ? channelResult : channelResult?.comments ?? null;
63
+ const channelSource: ChannelSource = channelResult === null ? 'unreadable' : 'unknown';
55
64
  let checklist: ReturnType<SeatsSources['checklist']> = null;
56
65
  try { checklist = sources.checklist(); } catch { checklist = null; }
57
66
  let raisers: string[] | null = null;
@@ -72,7 +81,8 @@ export async function buildSeatsBoard(date: string, sources: SeatsSources): Prom
72
81
  const latest = channel?.filter((c) => c.body.trimStart().startsWith(`**[${seat}]**`)).sort((a, b) => b.createdAt.localeCompare(a.createdAt))[0];
73
82
  const mine = checklist?.current.filter((item) => seatOf(item.owner) === seat);
74
83
  return {
75
- seat, role,
84
+ seat, role, channelSource: channelResult && !Array.isArray(channelResult)
85
+ ? channelResult.seatSources?.[seat] ?? channelSource : channelSource,
76
86
  now: latest ? { text: clip(latest.body.trimStart().split('\n')[0]!, 120), at: latest.createdAt } : null,
77
87
  landed: merged === null || checklist === null ? null : (landedBySeat.get(seat) ?? []).sort((a, b) => a.at.localeCompare(b.at)),
78
88
  blocked: mine ? mine.filter((item) => item.status === 'red').map((item) => ({ id: item.id, title: clip(item.title, 80), status: 'red' as const })) : null,
@@ -1,7 +1,12 @@
1
1
  import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
2
2
  import { createHash, randomUUID } from 'node:crypto';
3
- import { dirname, join } from 'node:path';
3
+ import { basename, dirname, join } from 'node:path';
4
4
  import { debug } from '../../debug/log.js';
5
+ import { resolveAttachmentPath } from '../../boot/attachment-store.js';
6
+ import { runGh } from '../../decisions/decision-cards.js';
7
+ import { openMsgStore, canonicalSeatId } from '../../msg/msg-store.js';
8
+ import { dispatchCeoTask, type CeoCommandDeps } from '../../seat-dispatch/ceo-commands.js';
9
+ import { getUserConfig } from '../../user-config.js';
5
10
  import { effectiveInstanceRoot } from '../../instance/resolve.js';
6
11
  import { submitIntakeWork } from '../../intake-plane/submit-intake-work.js';
7
12
  import { parseSeatAddress, resolveSeat } from '../../seat-address/seat-address.js';
@@ -16,6 +21,9 @@ interface SeatRequestItem {
16
21
  text: string;
17
22
  queuedAt: string;
18
23
  status: 'queued';
24
+ attachments?: string[];
25
+ channel?: 'posted' | 'failed' | 'unknown';
26
+ channelError?: string;
19
27
  }
20
28
 
21
29
  interface PendingRequest {
@@ -25,6 +33,7 @@ interface PendingRequest {
25
33
  text: string;
26
34
  queuedAt: string;
27
35
  ref: string;
36
+ attachments?: string[];
28
37
  }
29
38
 
30
39
  interface RejectedRequest extends Omit<PendingRequest, 'status'> {
@@ -38,10 +47,17 @@ export interface SeatRequestsDeps {
38
47
  submit?: typeof submitIntakeWork;
39
48
  now?: () => string;
40
49
  append?: (path: string, entry: RecordEntry) => void;
50
+ resolveAttachment?: typeof resolveAttachmentPath;
51
+ dispatch?: typeof dispatchCeoTask;
52
+ ceoDeps?: () => CeoCommandDeps;
41
53
  }
42
54
 
43
55
  const seats = trackData.tracks.filter((entry): entry is typeof entry & { title: string } => 'title' in entry)
44
56
  .map(({ id, title }) => ({ id, title }));
57
+
58
+ function requestSeat(address: string) {
59
+ return resolveSeat(address) ?? trackData.tracks.find((entry) => entry.id.toLowerCase() === address.replace(/^@/, '').toLowerCase());
60
+ }
45
61
  const inFlight = new Map<string, { payload: string; task: Promise<Response> }>();
46
62
 
47
63
  function unknownSeat(): Response {
@@ -140,7 +156,28 @@ function persistOutcome(path: string, entry: RecordEntry, write: typeof append):
140
156
  }
141
157
 
142
158
  function receipt(item: SeatRequestItem): Response {
143
- return jsonResponse({ receiptId: item.receiptId, seat: item.seat, queuedAt: item.queuedAt }, 202);
159
+ return jsonResponse({ receiptId: item.receiptId, seat: item.seat, queuedAt: item.queuedAt,
160
+ // Only a CEO-seat dispatch knows whether the channel line went out; anything else is reported as unknown.
161
+ attachments: item.attachments?.length ?? 0, channel: item.channel ?? 'unknown',
162
+ ...(item.channelError ? { channelError: item.channelError } : {}) }, 202);
163
+ }
164
+
165
+ function defaultCeoDeps(): CeoCommandDeps {
166
+ const cfg = getUserConfig();
167
+ const replyTarget = (cfg.raw?.decisions as { replyGhPr?: unknown } | undefined)?.replyGhPr;
168
+ return { ownerId: null, replyTarget: typeof replyTarget === 'string' ? replyTarget : null, runGh,
169
+ append: (message) => { const store = openMsgStore(); try { return store.append(message); } finally { store.close(); } },
170
+ hasMessage: (seat, ref) => {
171
+ const store = openMsgStore();
172
+ try {
173
+ return store.db.query("SELECT 1 FROM msg_messages WHERE recipient = ? AND kind = 'ceo-task' AND body LIKE ? LIMIT 1")
174
+ .get(seat, `%\n요청: ${ref}`) !== null;
175
+ } finally { store.close(); }
176
+ } };
177
+ }
178
+
179
+ function canonicalSeatIdSafe(id: string): string {
180
+ try { return canonicalSeatId(id); } catch { return id; }
144
181
  }
145
182
 
146
183
  function unavailable(): Response {
@@ -153,7 +190,7 @@ export async function handleSeatRequests(req: Request, deps: SeatRequestsDeps =
153
190
  if (req.method === 'GET') {
154
191
  const params = new URL(req.url).searchParams;
155
192
  const requestedSeat = params.get('seat');
156
- const seat = requestedSeat ? resolveSeat(requestedSeat) : undefined;
193
+ const seat = requestedSeat ? requestSeat(requestedSeat) : undefined;
157
194
  if (requestedSeat && !seat) return unknownSeat();
158
195
  const rawLimit = params.get('limit');
159
196
  const limit = rawLimit === null ? 20 : Number(rawLimit);
@@ -183,18 +220,30 @@ export async function handleSeatRequests(req: Request, deps: SeatRequestsDeps =
183
220
  const firstAddress = parsed && input.text.startsWith(prefix)
184
221
  && (input.text.length === prefix.length || /[ \t\r\n]/.test(input.text[prefix.length]!)) ? parsed : null;
185
222
  const address = input.seat ?? (firstAddress?.seats.length === 1 ? firstAddress.seats[0] : undefined);
186
- const seat = address ? resolveSeat(address) : undefined;
223
+ const seat = address ? requestSeat(address) : undefined;
187
224
  if (!seat) return unknownSeat();
188
225
  const text = input.seat === undefined ? firstAddress!.body.trim() : input.text.trim();
189
226
  if (!text) {
190
227
  debug.log('seat-address.pwa', 'rejected', { reason: 'empty-text', seat: seat.id });
191
228
  return jsonResponse({ error: 'bad_request' }, 400);
192
229
  }
230
+ if (input.attachments !== undefined && (!Array.isArray(input.attachments) || input.attachments.length > 4
231
+ || input.attachments.some((attachment: unknown) => !attachment || typeof attachment !== 'object' || Array.isArray(attachment)
232
+ || typeof (attachment as { id?: unknown }).id !== 'string' || !(attachment as { id: string }).id.trim()))) {
233
+ debug.log('seat.dispatch', 'refused', { seat: seat.id, reason: 'invalid-attachments' });
234
+ return jsonResponse({ error: 'bad_request' }, 400);
235
+ }
236
+ const attachmentIds = (input.attachments as Array<{ id: string }> | undefined)?.map(({ id }) => id) ?? [];
237
+ // Intake (non-CEO seats) carries text only — refuse attachments there instead of accepting and dropping them.
238
+ if (attachmentIds.length && !['OP', 'TC', 'MK', 'UX'].includes(canonicalSeatIdSafe(seat.id))) {
239
+ debug.log('seat.dispatch', 'refused', { seat: seat.id, reason: 'attachments-unsupported' });
240
+ return jsonResponse({ error: 'attachments-unsupported', seat: seat.id }, 400);
241
+ }
193
242
  debug.log('seat-address.pwa', 'parsed', { seat: seat.id, textLength: text.length });
194
243
  const now = (deps.now ?? (() => new Date().toISOString()))();
195
244
  const header = req.headers.get('idempotency-key');
196
245
  if (header !== null && (!header.trim() || header.length > 200)) return jsonResponse({ error: 'bad_request' }, 400);
197
- const payload = `${seat.id}\0${text}`;
246
+ const payload = JSON.stringify([seat.id, text, attachmentIds]);
198
247
  const key = header === null ? createHash('sha256').update(randomUUID()).digest('hex')
199
248
  : createHash('sha256').update(`client\0${header}`).digest('hex');
200
249
  const lock = `${path}\0${key}`;
@@ -213,11 +262,23 @@ export async function handleSeatRequests(req: Request, deps: SeatRequestsDeps =
213
262
  try { existing = records(path).get(key); }
214
263
  catch { return unavailable(); }
215
264
  if (existing) {
216
- if (existing.seat !== seat.id || existing.text !== text) return jsonResponse({ error: 'idempotency-conflict' }, 409);
265
+ if (existing.seat !== seat.id || existing.text !== text
266
+ || JSON.stringify(existing.attachments ?? []) !== JSON.stringify(attachmentIds)) return jsonResponse({ error: 'idempotency-conflict' }, 409);
217
267
  if (existing.status === 'queued') return receipt(existing);
218
268
  }
269
+ const attachments: Array<{ name: string; path: string }> = [];
270
+ for (const id of attachmentIds) {
271
+ const resolved = (deps.resolveAttachment ?? resolveAttachmentPath)(id);
272
+ if (!resolved) {
273
+ debug.log('seat.dispatch', 'refused', { seat: seat.id, reason: 'unknown-attachment' });
274
+ return jsonResponse({ error: 'unknown-attachment', id }, 400);
275
+ }
276
+ const metadata = (input.attachments as Array<{ name?: unknown }>)[attachments.length];
277
+ attachments.push({ name: typeof metadata?.name === 'string' && metadata.name.trim() ? metadata.name : basename(resolved), path: resolved });
278
+ }
219
279
  const intent: PendingRequest = existing?.status === 'pending'
220
- ? existing : { key, seat: seat.id, text, queuedAt: now, status: 'pending', ref: `pwa:${randomUUID()}` };
280
+ ? existing : { key, seat: seat.id, text, queuedAt: now, status: 'pending', ref: `pwa:${randomUUID()}`,
281
+ ...(attachmentIds.length ? { attachments: attachmentIds } : {}) };
221
282
  if (existing?.status !== 'pending') {
222
283
  try {
223
284
  (deps.append ?? append)(path, intent);
@@ -227,6 +288,24 @@ export async function handleSeatRequests(req: Request, deps: SeatRequestsDeps =
227
288
  }
228
289
  } catch { return unavailable(); }
229
290
  }
291
+ const ceoSeat = canonicalSeatId(seat.id);
292
+ if (['OP', 'TC', 'MK', 'UX'].includes(ceoSeat)) {
293
+ let result: Awaited<ReturnType<typeof dispatchCeoTask>>;
294
+ try {
295
+ result = await (deps.dispatch ?? dispatchCeoTask)(ceoSeat, text, (deps.ceoDeps ?? defaultCeoDeps)(), { attachments, via: 'pwa', ref: intent.ref });
296
+ } catch {
297
+ debug.log('seat.dispatch', 'refused', { seat: seat.id, reason: 'dispatch-failed' });
298
+ return unavailable();
299
+ }
300
+ const item: RecordEntry & SeatRequestItem = {
301
+ key, receiptId: intent.ref, seat: seat.id, text, queuedAt: intent.queuedAt, status: 'queued',
302
+ ...(attachmentIds.length ? { attachments: attachmentIds } : {}), channel: result.channel,
303
+ ...(result.channelError ? { channelError: result.channelError } : {}),
304
+ };
305
+ try { persistOutcome(path, item, deps.append ?? append); }
306
+ catch { return unavailable(); }
307
+ return receipt(item);
308
+ }
230
309
  let result: Awaited<ReturnType<typeof submitIntakeWork>>;
231
310
  try {
232
311
  result = await (deps.submit ?? submitIntakeWork)({
@@ -241,7 +320,8 @@ export async function handleSeatRequests(req: Request, deps: SeatRequestsDeps =
241
320
  catch { return unavailable(); }
242
321
  return unavailable();
243
322
  }
244
- const item: RecordEntry & SeatRequestItem = { key, receiptId: result.acceptanceId, seat: seat.id, text, queuedAt: intent.queuedAt, status: 'queued' };
323
+ const item: RecordEntry & SeatRequestItem = { key, receiptId: result.acceptanceId, seat: seat.id, text, queuedAt: intent.queuedAt,
324
+ status: 'queued', ...(attachmentIds.length ? { attachments: attachmentIds } : {}) };
245
325
  try { persistOutcome(path, item, deps.append ?? append); }
246
326
  catch { return unavailable(); }
247
327
  debug.log('seat-address.pwa', 'enqueued', { seat: seat.id, receiptId: item.receiptId });