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
@@ -1,6 +1,6 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { readFileSync } from 'node:fs';
3
- import { join } from 'node:path';
2
+ import { readFileSync, realpathSync, statSync } from 'node:fs';
3
+ import { isAbsolute, join, relative, resolve } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import type { MissionOrigin } from '../autopilot/mission-origin.js';
6
6
  import { debug } from '../debug/log.js';
@@ -15,7 +15,7 @@ import { answerAsSeat, isNoGraphFailure } from './seat-answer.js';
15
15
  const POLL_MS = 5_000;
16
16
  const TIMEOUT_MS = 30 * 60_000;
17
17
  const DOC = /전략|한\s*장|원페이저|기획|보도자료|글|메모|초안|\b(?:one-pager|strategy|post|memo|draft)\b/i;
18
- const CODE = /구현|버그|고쳐|\b(?:fix|implement|PR)\b/i;
18
+ const CODE = /구현|버그|고쳐|\b(?:fix|implement)\b|\bPR\b(?!\s*#?\d+\b)/i;
19
19
  const RESEARCH = /전략|시장|경쟁|\bstrategy\b/i;
20
20
  /** EV12a — a question or a request for status / a report is answered, never turned into a code goal. */
21
21
  const ASK = /상황|현황|진행|준비|어때|어떻게|알려|보고|정리|요약|공유|\?|?|\b(?:status|update|report|summary|summari[sz]e|how|what)\b/i;
@@ -30,6 +30,8 @@ export interface SeatDocDeps {
30
30
  submitExec?: (text: string) => Promise<{ id: string }>;
31
31
  getExec?: (id: string) => Promise<ExecSnapshot | null>;
32
32
  getFile?: (path: string) => Promise<string>;
33
+ /** Resolve only references explicitly supplied in the request; null means their claims cannot be verified. */
34
+ readSource?: (ref: string) => Promise<string | null>;
33
35
  publishFile?: (content: string, key: string) => string | null;
34
36
  sendOutbound?: typeof sendOutbound;
35
37
  sleep?: (ms: number) => Promise<void>;
@@ -97,6 +99,160 @@ async function execFile(path: string): Promise<string> {
97
99
  return (await execResponse(path.slice('/v1/exec-requests'.length))).text();
98
100
  }
99
101
 
102
+ const SUPPLIED_REF = /https?:\/\/[^\s<>()[\]]+|\bPR\s*#?\d+\b|(?:\.?\.?\/|[\w.-]+\/)[\w./-]+\.(?:md|txt|csv|json|pdf)\b|\b[\w.-]+\.(?:md|txt|csv|json|pdf)\b/gi;
103
+ // Keep units and grammatical particles with the figure; never backtrack from 42% to 42.
104
+ const NUMBER = /(?<![\p{L}\p{N}_#.,])([+-]?\d+(?:[,.]\d+)*(?:(?:%|년|월|일|명|건|원|배)(?![\p{N}_%A-Za-z]|[.,]\d)|(?![\p{L}\p{N}_%]|[.,]\d)))(은|는|이|가|을|를|에|의|도|만|에서|으로|와|과)?/gu;
105
+
106
+ function suppliedReferences(text: string): string[] {
107
+ return [...new Set([...text.matchAll(SUPPLIED_REF)].map(([ref]) => ref!.replace(/[.,;]+$/, '')))];
108
+ }
109
+
110
+ const SOURCE_TIMEOUT_MS = 5_000;
111
+ const MAX_SOURCE_BYTES = 200_000;
112
+ // The default resolver only reads repository-local files and these two public HTTPS hosts.
113
+ const SOURCE_URL_HOSTS = new Set(['github.com', 'raw.githubusercontent.com']);
114
+
115
+ function allowedSourceUrl(value: string): boolean {
116
+ try {
117
+ const url = new URL(value);
118
+ return url.protocol === 'https:' && SOURCE_URL_HOSTS.has(url.hostname)
119
+ && !url.username && !url.password && !url.port;
120
+ } catch { return false; }
121
+ }
122
+
123
+ async function readPublicSource(ref: string): Promise<string | null> {
124
+ let url = ref;
125
+ const signal = AbortSignal.timeout(SOURCE_TIMEOUT_MS);
126
+ for (let redirects = 0; redirects < 3; redirects++) {
127
+ if (!allowedSourceUrl(url)) return null;
128
+ const response = await fetch(url, { signal, redirect: 'manual' });
129
+ if (response.status >= 300 && response.status < 400) {
130
+ const location = response.headers.get('location');
131
+ if (!location) return null;
132
+ url = new URL(location, url).href;
133
+ continue;
134
+ }
135
+ if (!response.ok || !/^(?:text\/|application\/(?:json|xml))/.test(response.headers.get('content-type') ?? '')) return null;
136
+ if (Number(response.headers.get('content-length') ?? 0) > MAX_SOURCE_BYTES) {
137
+ await response.body?.cancel();
138
+ return null;
139
+ }
140
+ if (!response.body) return null;
141
+ const reader = response.body.getReader();
142
+ const chunks: Uint8Array[] = [];
143
+ let size = 0;
144
+ try {
145
+ while (true) {
146
+ const { done, value } = await reader.read();
147
+ if (done) break;
148
+ size += value.byteLength;
149
+ if (size > MAX_SOURCE_BYTES) return null;
150
+ chunks.push(value);
151
+ }
152
+ const bytes = new Uint8Array(size);
153
+ let offset = 0;
154
+ for (const chunk of chunks) { bytes.set(chunk, offset); offset += chunk.byteLength; }
155
+ return new TextDecoder().decode(bytes);
156
+ } finally {
157
+ if (size > MAX_SOURCE_BYTES) await reader.cancel();
158
+ reader.releaseLock();
159
+ }
160
+ }
161
+ return null;
162
+ }
163
+
164
+ async function defaultReadSource(ref: string): Promise<string | null> {
165
+ try {
166
+ if (/^https?:\/\//i.test(ref)) return await readPublicSource(ref);
167
+ if (/^PR\s*#?\d+$/i.test(ref)) {
168
+ const number = ref.match(/\d+/)![0];
169
+ const child = Bun.spawn(['gh', 'pr', 'view', number, '--json', 'title,body'], { stdout: 'pipe', stderr: 'ignore' });
170
+ let timer: ReturnType<typeof setTimeout> | undefined;
171
+ const timeout = new Promise<null>(finish => {
172
+ timer = setTimeout(() => { child.kill(9); finish(null); }, SOURCE_TIMEOUT_MS);
173
+ });
174
+ try {
175
+ const result = await Promise.race([
176
+ Promise.all([new Response(child.stdout).text(), child.exited]).then(([out, code]) => ({ out, code })),
177
+ timeout,
178
+ ]);
179
+ if (!result || result.code !== 0 || result.out.length > MAX_SOURCE_BYTES) return null;
180
+ const pr = JSON.parse(result.out) as { title?: string; body?: string };
181
+ return `${pr.title ?? ''}\n${pr.body ?? ''}`;
182
+ } finally { clearTimeout(timer); }
183
+ }
184
+ const root = realpathSync(process.cwd());
185
+ const actual = realpathSync(resolve(root, ref));
186
+ const within = relative(root, actual);
187
+ if (isAbsolute(within) || within === '..' || within.startsWith('../')) return null;
188
+ const file = statSync(actual);
189
+ if (!file.isFile() || file.size > MAX_SOURCE_BYTES || /\.pdf$/i.test(actual)) return null;
190
+ return readFileSync(actual, 'utf8');
191
+ } catch { return null; }
192
+ }
193
+
194
+ function citationFor(ref: string): string {
195
+ return /^PR\s*#?(\d+)$/i.test(ref) ? `PR #${ref.match(/\d+/)![0]}` : ref;
196
+ }
197
+
198
+ function claimLabel(line: string, offset: number, length: number): string | null {
199
+ const before = line.slice(0, offset).split(/[,;:,;。.!?\n]/).at(-1) ?? '';
200
+ const after = (line.slice(offset + length).split(/[,;:,;。.!?\n]/)[0] ?? '').replace(/^\s*(?:\[출처: [^\]]+\]|«확인 필요»)/, '');
201
+ // Never borrow a label from another figure in the same clause.
202
+ if ([...before.matchAll(NUMBER), ...after.matchAll(NUMBER)].length) return null;
203
+ const left = before.trim().replace(/^(?:[#*>-]+\s*)+/, '');
204
+ const right = after.match(/^\s*([\p{L}]{2,}(?:\s+[\p{L}]{2,})*)/u)?.[1];
205
+ return /[\p{L}]{2,}/u.test(left) ? left : right ?? null;
206
+ }
207
+
208
+ const CLAIM_DIRECTION = /증가|상승|성장|확대|개선|감소|하락|축소|악화/g;
209
+ const CLAIM_FORECAST = /예상|전망|예측|추정|계획|예정|기대|목표|가능성/;
210
+ // Range and approximation words change what a figure claims («42%» ≠ «42% 미만»); both sides must carry the same set.
211
+ const CLAIM_QUALIFIER = /미만|이하|이상|초과|이내|약|최대|최소|가량|정도|내외|안팎|넘게|넘는|남짓|까지|부터|대략|거의|이상의|under|over|about|up to|at least|at most|less than|more than|nearly|almost/giu;
212
+
213
+ function claimDirection(segment: string): string | undefined {
214
+ const directions = [...segment.matchAll(CLAIM_DIRECTION)];
215
+ if (directions.length > 1) return undefined;
216
+ const direction = directions[0];
217
+ const after = direction ? segment.slice(direction.index + direction[0].length) : '';
218
+ const negated = direction && /^\s*(?:하지\s*(?:않|못)|않|못|안\s*(?:했|함)|없|아니)/u.test(after);
219
+ // Polarity outside the recognized predicate is ambiguous; do not use it to establish provenance.
220
+ if (/않|못|안\s|없|아니/u.test(segment) && !negated) return undefined;
221
+ const qualifiers = [...new Set([...segment.matchAll(CLAIM_QUALIFIER)].map(match => match[0].toLowerCase().replace(/^이상의$/, '이상')))].sort().join(',');
222
+ return `${direction?.[0] ?? ''}|${negated ? 'negated' : 'affirmed'}|${CLAIM_FORECAST.test(segment) ? 'forecast' : 'observed'}|${qualifiers}`;
223
+ }
224
+
225
+ function hasClaimPair(source: string, number: string, label: string, direction: string | undefined): boolean {
226
+ if (direction === undefined) return false;
227
+ return source.split(/\n|,(?=\s)|[;:,;。!?]|\.(?!\d)/).some(segment => {
228
+ const figures = [...segment.matchAll(NUMBER)];
229
+ if (figures.length !== 1 || figures[0]![1] !== number) return false;
230
+ return claimLabel(segment, figures[0]!.index, figures[0]![0].length) === label
231
+ && claimDirection(segment) === direction;
232
+ });
233
+ }
234
+
235
+ function citeDocumentNumbers(content: string, sources: Map<string, string | null>): string {
236
+ let fenced = false;
237
+ return content.split('\n').map(line => {
238
+ if (/^\s*```/.test(line)) { fenced = !fenced; return line; }
239
+ if (fenced || /^\s*(?:참고 자료|출처:)/.test(line)) return line;
240
+ const occupied = [...line.matchAll(/https?:\/\/\S+|\[출처: [^\]]+\]|(?:\.?\.?\/|[\w.-]+\/)[\w./-]+\.(?:md|txt|csv|json|pdf)|\b[\w.-]+\.(?:md|txt|csv|json|pdf)\b/g)]
241
+ .map(match => [match.index, match.index + match[0].length] as const);
242
+ const annotated = new RegExp(`${NUMBER.source}(?:\\s*(?:\\[출처: [^\\]]+\\]|«확인 필요»))?`, 'gu');
243
+ return line.replace(annotated, (claim, number: string, particle: string | undefined, offset: number) => {
244
+ if (occupied.some(([start, end]) => offset >= start && offset < end)) return claim;
245
+ const figure = `${number}${particle ?? ''}`;
246
+ const label = claimLabel(line, offset, claim.length);
247
+ const before = line.slice(0, offset).split(/[,;:,;。.!?]/).at(-1) ?? '';
248
+ const after = line.slice(offset + claim.length).split(/[,;:,;。.!?]/)[0] ?? '';
249
+ const direction = claimDirection(`${before} ${after}`);
250
+ const matches = label ? [...sources].filter(([, source]) => source && hasClaimPair(source, number, label, direction)) : [];
251
+ return matches.length === 1 ? `${figure} [출처: ${citationFor(matches[0]![0])}]` : `${figure} «확인 필요»`;
252
+ });
253
+ }).join('\n');
254
+ }
255
+
100
256
  function chatPwaUrl(): string | undefined {
101
257
  const endpoint = resolveDaemonEndpoint({ purpose: 'write' });
102
258
  return endpoint?.pwaUrl;
@@ -115,14 +271,15 @@ function accessiblePwaUrl(value: string | undefined): string | undefined {
115
271
  } catch { return undefined; }
116
272
  }
117
273
 
118
- async function fileLinks(id: string, result: ExecSnapshot, deps: SeatDocDeps): Promise<Map<string, string>> {
274
+ async function fileLinks(id: string, result: ExecSnapshot, deps: SeatDocDeps, sources: Map<string, string | null>): Promise<Map<string, string>> {
119
275
  const links = new Map<string, string>();
120
276
  for (const entry of result.results) {
121
277
  if (entry.kind !== 'report' && entry.kind !== 'text') continue;
122
278
  if (!entry.url.startsWith(`/v1/exec-requests/${encodeURIComponent(id)}/files/`)) continue;
123
279
  try {
124
280
  // The authenticated daemon route is the only source of bytes; a raw file path is never trusted.
125
- const content = await (deps.getFile ?? execFile)(entry.url);
281
+ const raw = await (deps.getFile ?? execFile)(entry.url);
282
+ const content = sources.size ? citeDocumentNumbers(raw, sources) : raw;
126
283
  const ext = entry.kind === 'report' ? 'md' : 'txt';
127
284
  const digest = createHash('sha256').update(content).digest('hex');
128
285
  const key = `seat-doc/${encodeURIComponent(id)}/${digest}.${ext}`;
@@ -158,6 +315,13 @@ export async function submitSeatDocRequest({ text, reportTo, deps = {} }: {
158
315
  const body = parseSeatAddress(text)?.body.trim() ?? text.trim();
159
316
  let withoutResearch = false;
160
317
  let requestText = text;
318
+ const refs = suppliedReferences(body);
319
+ const sources = new Map<string, string | null>();
320
+ for (const ref of refs) {
321
+ try { sources.set(ref, await (deps.readSource ?? defaultReadSource)(ref)); }
322
+ catch { sources.set(ref, null); }
323
+ }
324
+ if (refs.length) requestText += '\n\n문서 수치 근거 규칙: 제공된 파일·URL·PR 번호의 실제 내용을 확인하고, 근거가 확인된 각 수치 바로 뒤에 [출처: <파일|PR|명령>] 형식으로 실물 참조를 붙이세요. 근거를 확인할 수 없는 수치는 삭제하거나 바꾸지 말고 «확인 필요»로 표시하세요. 서로 다른 출처에 같은 숫자가 있다는 이유만으로 출처를 추정하지 마세요.';
161
325
  if (RESEARCH.test(body)) {
162
326
  try {
163
327
  const sources = (await (deps.research ?? research)(body)).slice(0, 5);
@@ -196,7 +360,7 @@ export async function submitSeatDocRequest({ text, reportTo, deps = {} }: {
196
360
  if (now() < deadline) await sleep(Math.min(POLL_MS, Math.max(0, deadline - now())));
197
361
  }
198
362
  log('settled', { id, status: result?.status ?? (lookupAvailable ? 'timeout' : 'lookup-unavailable'), lastConfirmedStatus });
199
- const links = result?.status === 'done' ? await fileLinks(id, result, deps) : new Map<string, string>();
363
+ const links = result?.status === 'done' ? await fileLinks(id, result, deps, sources) : new Map<string, string>();
200
364
  let reply = message(id, result, withoutResearch, lookupAvailable, accessiblePwaUrl((deps.pwaUrl ?? chatPwaUrl)()), links);
201
365
  if (isNoGraphFailure(result)) {
202
366
  // EV12d — a question with no fitting graph gets the seat's own answer, not «문서 요청 실패».
@@ -1,5 +1,11 @@
1
1
  import { debug } from '../debug/log.js';
2
+ import { telegramDecisionOwner } from '../decisions/telegram-decision-cards.js';
3
+ import { canonicalSeatId } from '../msg/msg-store.js';
4
+ import { dispatchCeoTask, type CeoCommandDeps } from '../seat-dispatch/ceo-commands.js';
5
+ import { ceoTaskDeps, classifyCeoIntent } from '../seat-dispatch/ceo-intent.js';
2
6
  import { parseSeatAddress, resolveSeat } from '../seat-address/seat-address.js';
7
+ import { getUserConfig, type UserConfig } from '../user-config.js';
8
+ import { answerAsSeat } from './seat-answer.js';
3
9
  import {
4
10
  submitIntakeWork,
5
11
  type SubmitIntakeWorkDeps,
@@ -8,15 +14,39 @@ import type { TelegramWorkMessage } from './telegram-work.js';
8
14
 
9
15
  export interface TelegramSeatWorkDeps extends SubmitIntakeWorkDeps {
10
16
  submit?: typeof submitIntakeWork;
17
+ config?: UserConfig;
18
+ answer?: typeof answerAsSeat;
19
+ dispatch?: typeof dispatchCeoTask;
20
+ commandDeps?: CeoCommandDeps;
11
21
  }
12
22
 
13
- /** Addressed Telegram work goes through graph intake, not the chat turn. */
23
+ /** Addressed work preserves graph intake except for one owner's private seat request. */
14
24
  export async function handleTelegramSeatWork(
15
25
  text: string,
16
26
  msg: TelegramWorkMessage,
17
27
  deps: TelegramSeatWorkDeps = {},
18
28
  ): Promise<string | null> {
19
- if (!text.startsWith('@')) return null;
29
+ const owner = (): { cfg: UserConfig; id: string } | null => {
30
+ if (msg.userId === undefined || String(msg.chatId) !== String(msg.userId)) return null;
31
+ const cfg = deps.config ?? getUserConfig();
32
+ const id = telegramDecisionOwner(cfg);
33
+ return id && id === String(msg.userId) ? { cfg, id } : null;
34
+ };
35
+ const sendTask = async (seat: string, body: string, cfg: UserConfig, id: string): Promise<string> => {
36
+ const result = await (deps.dispatch ?? dispatchCeoTask)(seat, body, deps.commandDeps ?? ceoTaskDeps(cfg, id), { via: 'telegram' });
37
+ debug.log('seat.dispatch', 'intent', { seat, intent: 'task', via: 'telegram', outcome: result.channel });
38
+ return result.reply;
39
+ };
40
+ if (!text.startsWith('@')) {
41
+ if (!text.trim() || /^\s*[/@@]/.test(text)) return null;
42
+ const auth = owner();
43
+ const configured = (auth?.cfg.raw?.seatDispatch as { defaultSeat?: unknown } | undefined)?.defaultSeat;
44
+ if (typeof configured !== 'string' || classifyCeoIntent(text) !== 'task') return null;
45
+ const seat = resolveSeat(configured);
46
+ if (!seat) return null;
47
+ const id = canonicalSeatId(seat.id);
48
+ return ['OP', 'TC', 'MK', 'UX'].includes(id) ? sendTask(id, text, auth!.cfg, auth!.id) : null;
49
+ }
20
50
  const address = parseSeatAddress(text);
21
51
  if (!address) return null;
22
52
 
@@ -34,6 +64,18 @@ export async function handleTelegramSeatWork(
34
64
  return '어떤 일을 맡길까요? 좌석 주소 뒤에 요청 내용을 적어 다시 보내 주세요.';
35
65
  }
36
66
 
67
+ if (seats.length === 1 && seats[0]?.seat) {
68
+ const auth = owner();
69
+ const seatId = canonicalSeatId(seats[0].seat.id);
70
+ if (auth && ['OP', 'TC', 'MK', 'UX'].includes(seatId)) {
71
+ const intent = classifyCeoIntent(body);
72
+ if (intent === 'task') return sendTask(seatId, body, auth.cfg, auth.id);
73
+ const answer = await (deps.answer ?? answerAsSeat)(seats[0].name, body);
74
+ debug.log('seat.dispatch', 'intent', { seat: seatId, intent, via: 'telegram', outcome: answer ? 'answered' : 'intake' });
75
+ if (answer) return answer.text;
76
+ }
77
+ }
78
+
37
79
  const reportTo = {
38
80
  channel: 'telegram' as const,
39
81
  chatId: msg.chatId,
@@ -13,6 +13,7 @@ import {
13
13
 
14
14
  export interface TelegramWorkMessage {
15
15
  chatId: number;
16
+ userId?: number;
16
17
  messageId: number;
17
18
  botId?: string;
18
19
  threadId?: number;
@@ -0,0 +1,34 @@
1
+ import type { Command } from 'commander';
2
+ import { program } from '../index.js';
3
+ import { FEATURE_MATURITY, type Maturity, type Role } from './feature-maturity.js';
4
+
5
+ /** Commander owns aliases; the table contains canonical command names only. */
6
+ export function cliRootMaturity(name: string, commands: readonly Command[] = program.commands): Maturity | undefined {
7
+ const command = commands.find((entry) => entry.name() === name || entry.aliases().includes(name));
8
+ const canonical = command?.name() ?? name;
9
+ return Object.prototype.hasOwnProperty.call(FEATURE_MATURITY.cliRoot, canonical)
10
+ ? FEATURE_MATURITY.cliRoot[canonical as keyof typeof FEATURE_MATURITY.cliRoot]
11
+ : undefined;
12
+ }
13
+
14
+ export function cliRootVisibleFor(name: string, role: Role, { showAll }: { showAll: boolean }, commands: readonly Command[] = program.commands): boolean {
15
+ const grade = cliRootMaturity(name, commands);
16
+ if (!grade || grade === 'system') return false;
17
+ if (showAll || role === 'owner') return true;
18
+ return grade === 'stable' || (role === 'contributor' && (grade === 'beta' || grade === 'tool'));
19
+ }
20
+
21
+ /** Only help is filtered: Commander still dispatches explicitly typed commands. */
22
+ export function filterCliRootHelp(commands: readonly Command[], role: Role, showAll: boolean): { shown: number; hidden: number } {
23
+ let shown = 0;
24
+ let hidden = 0;
25
+ for (const command of commands) {
26
+ const grade = cliRootMaturity(command.name(), commands);
27
+ if (!grade) throw new Error(`cliRoot 에 등급 없는 명령: ${command.name()}`);
28
+ const visible = cliRootVisibleFor(command.name(), role, { showAll }, commands);
29
+ (command as Command & { _hidden: boolean })._hidden = !visible;
30
+ if (visible) shown++;
31
+ else hidden++;
32
+ }
33
+ return { shown, hidden };
34
+ }
@@ -13,6 +13,7 @@ export const FEATURE_MATURITY = {
13
13
  '/setup/done': { pwa: 'stable' },
14
14
  '/consult': { pwa: 'stable' },
15
15
  '/chat': { pwa: 'stable' },
16
+ '/today': { pwa: 'stable' },
16
17
  '/term': { pwa: 'stable' },
17
18
  '/live': { pwa: 'stable' },
18
19
  '/market': { pwa: 'stable' },
@@ -38,6 +39,9 @@ export const FEATURE_MATURITY = {
38
39
  '/design-check': { pwa: 'tool' },
39
40
  '/control': { pwa: 'tool' },
40
41
  '/scheduler': { pwa: 'ops' },
42
+ '/ops/release': { pwa: 'ops' },
43
+ '/ops/seats': { pwa: 'ops' },
44
+ '/ops/checklist': { pwa: 'ops' },
41
45
  '/dashboard': { pwa: 'ops' },
42
46
  '/bots': { pwa: 'ops' },
43
47
  '/botlab': { pwa: 'ops' },
@@ -46,7 +50,101 @@ export const FEATURE_MATURITY = {
46
50
  '/workflows/chat-ui': { pwa: 'broken' },
47
51
  '/share': { pwa: 'system' },
48
52
  },
49
- } as const satisfies { pwaRoute: Record<string, SurfaceMaturity> };
53
+ // Canonical CLI command names only; aliases inherit their command's grade.
54
+ cliRoot: {
55
+ pty: 'beta', pr: 'beta', repo: 'beta', role: 'beta', machine: 'beta',
56
+ where: 'stable', shadow: 'beta', llm: 'beta', storage: 'beta', leader: 'beta',
57
+ pod: 'beta', browser: 'beta', questions: 'beta', usage: 'stable', live: 'beta',
58
+ research: 'beta', release: 'beta', seat: 'beta', coord: 'beta', 'model-watch': 'beta', doctor: 'stable',
59
+ control: 'beta', resources: 'beta', hooks: 'beta', setup: 'stable', start: 'stable',
60
+ grounding: 'beta', graph: 'beta', loop: 'stable', card: 'stable', 'launch-head': 'beta',
61
+ a2a: 'beta', plugin: 'stable', skills: 'beta', connect: 'beta', import: 'beta',
62
+ persona: 'beta', market: 'beta', directive: 'stable', connector: 'beta', storyboard: 'beta',
63
+ python: 'beta', 'self-update': 'stable', 'measure-fabric-arc-ab': 'beta', mcp: 'beta',
64
+ relay: 'beta', 'telegram-test': 'beta', telegram: 'beta', 'discord-test': 'beta',
65
+ sync: 'beta', status: 'beta', git: 'beta', gh: 'beta', repro: 'system',
66
+ theme: 'beta', history: 'beta', inspect: 'beta', autopilot: 'beta', memory: 'beta',
67
+ decide: 'beta', 'ax-screen': 'beta', 'decide-recipe': 'beta', signals: 'beta',
68
+ loops: 'beta', buzz: 'beta', harness: 'stable', self: 'beta', factcheck: 'beta',
69
+ provider: 'stable', 'provider:set': 'beta', 'provider:restore': 'beta',
70
+ 'provider:rotate': 'beta', 'provider:use': 'beta', 'status-bar': 'beta',
71
+ schedule: 'beta', intake: 'beta', logs: 'stable', ad: 'beta', docs: 'beta',
72
+ decisions: 'beta', directives: 'beta', fleet: 'beta', ops: 'stable', publish: 'beta',
73
+ env: 'beta', 'agent-mission': 'beta', dev: 'beta', keys: 'beta', finance: 'beta',
74
+ config: 'stable', tasks: 'stable', labels: 'beta', msg: 'beta', ask: 'stable',
75
+ onboarding: 'beta', login: 'stable', session: 'beta', chat: 'stable', repl: 'stable',
76
+ agent: 'stable', registry: 'beta', tier: 'stable', local: 'beta', scheduler: 'beta',
77
+ attach: 'beta', wf: 'beta', nexus: 'stable', phone: 'beta', token: 'beta',
78
+ acp: 'beta', voice: 'beta',
79
+ 'run-detached': 'system', dogfood: 'system', run: 'system',
80
+ },
81
+ // Canonical names only; aliases inherit their command's grade at lookup.
82
+ // Count: bun -e "import('./src/maturity/feature-maturity.ts').then(m=>console.log(Object.keys(m.FEATURE_MATURITY.tuiSlash).length))" (= SLASH_COMMANDS canonical names).
83
+ tuiSlash: {
84
+ help: 'stable',
85
+ resume: 'stable',
86
+ model: 'stable',
87
+ clear: 'stable',
88
+ status: 'stable',
89
+ remaining: 'stable',
90
+ setup: 'stable',
91
+ quit: 'stable',
92
+ 'run-skill': 'stable',
93
+ ad: 'beta',
94
+ design: 'beta',
95
+ provider: 'stable',
96
+ reasoning: 'stable',
97
+ local: 'tool',
98
+ session: 'stable',
99
+ fork: 'stable',
100
+ rewind: 'stable',
101
+ mission: 'beta',
102
+ 'resume-turn': 'beta',
103
+ context: 'stable',
104
+ paste: 'stable',
105
+ sync: 'tool',
106
+ plugin: 'tool',
107
+ widget: 'tool',
108
+ log: 'tool',
109
+ memory: 'beta',
110
+ export: 'stable',
111
+ delta: 'tool',
112
+ theme: 'stable',
113
+ debug: 'tool',
114
+ rebind: 'tool',
115
+ 'api-allow': 'tool',
116
+ prompt: 'beta',
117
+ history: 'stable',
118
+ research: 'beta',
119
+ harness: 'ops',
120
+ plan: 'beta',
121
+ chat: 'stable',
122
+ dashboard: 'stable',
123
+ telegram: 'ops',
124
+ tablet: 'ops',
125
+ surface: 'ops',
126
+ term: 'tool',
127
+ claude: 'tool',
128
+ codex: 'tool',
129
+ gemini: 'tool',
130
+ acp: 'beta',
131
+ conv: 'beta',
132
+ handoff: 'ops',
133
+ 'agent-room': 'ops',
134
+ showroom: 'ops',
135
+ reply: 'ops',
136
+ capture: 'ops',
137
+ inject: 'ops',
138
+ relay: 'ops',
139
+ lane: 'ops',
140
+ control: 'ops',
141
+ default: 'ops',
142
+ qc: 'ops',
143
+ 'voice-chat': 'beta',
144
+ 'auto-tts': 'beta',
145
+ directive: 'ops',
146
+ },
147
+ } as const satisfies { pwaRoute: Record<string, SurfaceMaturity>; cliRoot: Record<string, Maturity>; tuiSlash: Record<string, Maturity> };
50
148
 
51
149
  /** Unregistered routes and surfaces are not implicitly mature. Desktop shares the PWA implementation. */
52
150
  export function maturityOn(route: string, surface: Surface): Maturity | undefined {
@@ -63,3 +161,4 @@ export function visibleOn(route: string, surface: Surface, role: Role): boolean
63
161
  if (route === '/settings') return true;
64
162
  return maturity === 'stable' || (role === 'contributor' && (maturity === 'beta' || maturity === 'tool'));
65
163
  }
164
+
@@ -0,0 +1,31 @@
1
+ // MAT1b — TUI slash suggestions use the same maturity source as PWA routes.
2
+ // Kept out of feature-maturity.ts: the PWA bundles that file, and this one
3
+ // needs the TUI command list.
4
+ import { SLASH_COMMANDS } from '../chat/index.js';
5
+ import { getUserConfig } from '../user-config.js';
6
+ import { FEATURE_MATURITY, type Maturity, type Role } from './feature-maturity.js';
7
+
8
+ export function readTuiSlashAudience(raw: unknown = getUserConfig().raw?.tui): { role: Role; showBeta: boolean } {
9
+ const tui = raw && typeof raw === 'object' && !Array.isArray(raw) ? raw as Record<string, unknown> : {};
10
+ const role = tui.role === 'general' || tui.role === 'contributor' || tui.role === 'owner' ? tui.role : 'owner';
11
+ return { role, showBeta: tui.showBeta === true };
12
+ }
13
+
14
+ export function slashMaturity(name: string): Maturity | undefined {
15
+ const command = SLASH_COMMANDS.find((entry) => entry.name === name || entry.aliases?.includes(name));
16
+ if (!command) return undefined;
17
+ return FEATURE_MATURITY.tuiSlash[command.name as keyof typeof FEATURE_MATURITY.tuiSlash];
18
+ }
19
+
20
+ /** Visibility of a TUI slash suggestion, not permission to execute a typed command.
21
+ * general = stable (+ beta with showBeta) · contributor = stable·beta·tool · owner = all.
22
+ * Registered commands without a maturity grade remain visible to owner only in picker/help;
23
+ * unregistered names are never visible through this predicate. */
24
+ export function slashVisibleFor(name: string, role: Role, { showBeta }: { showBeta: boolean }): boolean {
25
+ const grade = slashMaturity(name);
26
+ if (!grade) return false;
27
+ if (role === 'owner') return true;
28
+ if (grade === 'stable') return true;
29
+ if (role === 'contributor') return grade === 'beta' || grade === 'tool';
30
+ return grade === 'beta' && showBeta;
31
+ }
package/src/memory.ts CHANGED
@@ -35,6 +35,7 @@ import { randomUUID, createHash } from 'node:crypto';
35
35
  import { debug } from './debug/log.js';
36
36
  import { estimateTokens } from './tokens.js';
37
37
  import { budgetModel } from './llm/model-defaults.js';
38
+ import { getAppliedGlobalTestRoot } from './cli/test-flag.js';
38
39
 
39
40
  /**
40
41
  * 임시 파일 → fsync → rename → 폴더 fsync. rename 만으로는 «원자적»이지만 «내구적»이 아니다.
@@ -86,14 +87,16 @@ export interface MemoryEntry {
86
87
  * Use for core / safety / reference-location facts that must always be
87
88
  * in context. Costs tokens every turn, so keep pinned bodies short. */
88
89
  pinned: boolean;
90
+ /** Optional pinned-memory freshness window; absent memories retain their original injection text. */
91
+ staleAfterMinutes?: number;
89
92
  }
90
93
 
91
94
  // ── Paths ────────────────────────────────────────────────────────────
92
95
 
93
96
  export function memoryRoot(): string {
94
- const base = process.env.XDG_DATA_HOME && process.env.XDG_DATA_HOME.trim()
97
+ const base = getAppliedGlobalTestRoot() ?? (process.env.XDG_DATA_HOME && process.env.XDG_DATA_HOME.trim()
95
98
  ? process.env.XDG_DATA_HOME
96
- : join(homedir(), '.local', 'share');
99
+ : join(homedir(), '.local', 'share'));
97
100
  return join(base, 'elanous', 'memory');
98
101
  }
99
102
 
@@ -124,6 +127,12 @@ function parseFrontmatter(raw: string): { fm: Record<string, string>; body: stri
124
127
  return { fm, body };
125
128
  }
126
129
 
130
+ function parseStaleAfterMinutes(value: string | number | undefined): number | undefined {
131
+ if (value === undefined || value === '') return undefined;
132
+ const n = Number(value);
133
+ return Number.isFinite(n) && n > 0 ? n : undefined;
134
+ }
135
+
127
136
  function renderFrontmatter(fm: Record<string, string>, body: string): string {
128
137
  const lines = ['---'];
129
138
  for (const [k, v] of Object.entries(fm)) lines.push(`${k}: ${v}`);
@@ -179,6 +188,8 @@ export interface SaveMemoryOpts {
179
188
  priority?: number;
180
189
  /** Always-inject (bypass keyword gate). Default false — or preserve on update. */
181
190
  pinned?: boolean;
191
+ /** Pinned injection warns after this many minutes; preserved on update. */
192
+ staleAfterMinutes?: number;
182
193
  /** Override the generated id (tests). */
183
194
  id?: string;
184
195
  }
@@ -195,16 +206,19 @@ export function saveMemory(opts: SaveMemoryOpts, root: string = memoryRoot()): M
195
206
  let createdAt = now;
196
207
  let existingPriority: number | undefined;
197
208
  let existingPinned: boolean | undefined;
209
+ let existingStaleAfterMinutes: number | undefined;
198
210
  if (existsSync(path)) {
199
211
  try {
200
212
  const parsed = parseFrontmatter(readFileSync(path, 'utf-8'));
201
213
  if (parsed.fm.created) createdAt = parsed.fm.created;
202
214
  if (parsed.fm.priority) existingPriority = parseInt(parsed.fm.priority, 10) || 0;
203
215
  if (parsed.fm.pinned) existingPinned = parsed.fm.pinned === 'true';
216
+ existingStaleAfterMinutes = parseStaleAfterMinutes(parsed.fm['stale-after-minutes']);
204
217
  } catch { /* keep new createdAt */ }
205
218
  }
206
219
  const priority = opts.priority ?? existingPriority ?? 0;
207
220
  const pinned = opts.pinned ?? existingPinned ?? false;
221
+ const staleAfterMinutes = parseStaleAfterMinutes(opts.staleAfterMinutes) ?? existingStaleAfterMinutes;
208
222
  const frontmatter: Record<string, string> = {
209
223
  name: opts.name,
210
224
  description: opts.description,
@@ -214,6 +228,7 @@ export function saveMemory(opts: SaveMemoryOpts, root: string = memoryRoot()): M
214
228
  };
215
229
  if (priority > 0) frontmatter.priority = String(Math.floor(priority));
216
230
  if (pinned) frontmatter.pinned = 'true';
231
+ if (staleAfterMinutes !== undefined) frontmatter['stale-after-minutes'] = String(staleAfterMinutes);
217
232
  const raw = renderFrontmatter(frontmatter, opts.body);
218
233
  writeFileDurable(path, raw);
219
234
 
@@ -232,6 +247,7 @@ export function saveMemory(opts: SaveMemoryOpts, root: string = memoryRoot()): M
232
247
  keywords: extractKeywords(`${opts.name} ${opts.description}`),
233
248
  priority,
234
249
  pinned,
250
+ staleAfterMinutes,
235
251
  };
236
252
  }
237
253
 
@@ -257,6 +273,7 @@ function parseMemoryFile(path: string): MemoryEntry | null {
257
273
  keywords: extractKeywords(`${fm.name} ${fm.description ?? ''}`),
258
274
  priority: fm.priority ? (parseInt(fm.priority, 10) || 0) : 0,
259
275
  pinned: fm.pinned === 'true',
276
+ staleAfterMinutes: parseStaleAfterMinutes(fm['stale-after-minutes']),
260
277
  };
261
278
  }
262
279
 
@@ -432,6 +449,17 @@ export function readIndex(root: string = memoryRoot()): string {
432
449
 
433
450
  // ── Injection heuristic ──────────────────────────────────────────────
434
451
 
452
+ function pinnedChunk(e: MemoryEntry): string {
453
+ const header = `### 📌 [${e.type}] ${e.name}\n\n`;
454
+ const body = (e.body || '').trim() || e.description;
455
+ const updated = Date.parse(e.updatedAt);
456
+ if (e.staleAfterMinutes === undefined || !Number.isFinite(updated)) return header + body;
457
+ const ageMinutes = (Date.now() - updated) / 60_000;
458
+ if (ageMinutes <= e.staleAfterMinutes) return header + body;
459
+ const hours = Math.floor(ageMinutes / 60);
460
+ return header + `⚠️ 이 기억은 ${hours}시간 전 것이다 — 답하기 전에 원장 도구 release_status(판 일정·칸) · decisions_pending(미결 결정) · ops_seats(자리 현황)로 다시 확인하고, 확인 전에는 «확인 중»이라고 말한다.\n${body}`;
461
+ }
462
+
435
463
  export interface InjectionResult {
436
464
  /** Markdown block to prepend to the system prompt (or '' when empty). */
437
465
  block: string;
@@ -490,7 +518,7 @@ export function buildMemoryInjection(
490
518
  const pinnedMems = listMemories({}, root).filter(m => m.pinned).sort((a, b) => b.priority - a.priority);
491
519
  const pinnedParts: string[] = [];
492
520
  for (const e of pinnedMems) {
493
- const chunk = `### 📌 [${e.type}] ${e.name}\n\n${(e.body || '').trim() || e.description}`;
521
+ const chunk = pinnedChunk(e);
494
522
  const tok = estimateTokens(chunk);
495
523
  if (usedTokens + tok > maxTokens) break;
496
524
  pinnedParts.push(chunk);
@@ -653,7 +681,7 @@ export async function buildMemoryInjectionLLM(
653
681
  const pinnedMems = listMemories({}, root).filter((m) => m.pinned).sort((a, b) => b.priority - a.priority);
654
682
  const pinnedParts: string[] = [];
655
683
  for (const e of pinnedMems) {
656
- const chunk = `### 📌 [${e.type}] ${e.name}\n\n${(e.body || '').trim() || e.description}`;
684
+ const chunk = pinnedChunk(e);
657
685
  const tok = estimateTokens(chunk);
658
686
  if (usedTokens + tok > maxTokens) break;
659
687
  pinnedParts.push(chunk); injected.push(e.id); usedTokens += tok;