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
@@ -3,16 +3,13 @@ import { join } from 'node:path';
3
3
 
4
4
  import { elanousStateRoot } from '../autopilot/state-paths.js';
5
5
  import { ASK_USER_QUESTION_DELIVERY_VALUES } from './types.js';
6
- import type { AskUserQuestionRequest, AskUserQuestionResult, HitlDelivery, QuestionOption } from './types.js';
6
+ import type { AskUserQuestionRequest, AskUserQuestionResult, HitlDelivery, Question, QuestionOption } from './types.js';
7
7
 
8
- interface PendingQuestion {
8
+ export interface PendingQuestion {
9
9
  id: string;
10
10
  sessionId?: string;
11
- questions: Array<{
12
- id: string;
13
- question: string;
14
- options: QuestionOption[];
15
- }>;
11
+ runId?: string;
12
+ questions: Array<Pick<Question, 'id' | 'question' | 'options' | 'impact' | 'recommendedIndex'>>;
16
13
  startedAt: string;
17
14
  expiresAt?: string;
18
15
  surface: 'tui' | 'file';
@@ -86,6 +83,7 @@ function isPendingQuestion(value: unknown): value is PendingQuestion {
86
83
  const pending = value as Record<string, unknown>;
87
84
  return typeof pending.id === 'string'
88
85
  && (pending.sessionId === undefined || typeof pending.sessionId === 'string')
86
+ && (pending.runId === undefined || typeof pending.runId === 'string')
89
87
  && Array.isArray(pending.questions)
90
88
  && pending.questions.every((question) => {
91
89
  if (question === null || typeof question !== 'object' || Array.isArray(question)) return false;
@@ -93,7 +91,10 @@ function isPendingQuestion(value: unknown): value is PendingQuestion {
93
91
  return typeof record.id === 'string'
94
92
  && typeof record.question === 'string'
95
93
  && Array.isArray(record.options)
96
- && record.options.every(isQuestionOption);
94
+ && record.options.every(isQuestionOption)
95
+ && (record.impact === undefined || record.impact === 'low' || record.impact === 'medium' || record.impact === 'high' || record.impact === 'critical')
96
+ && (record.recommendedIndex === undefined || (typeof record.recommendedIndex === 'number'
97
+ && Number.isInteger(record.recommendedIndex)));
97
98
  })
98
99
  && isIsoTimestamp(pending.startedAt)
99
100
  && (pending.surface === 'tui' || pending.surface === 'file')
@@ -111,7 +112,15 @@ function isPendingQuestion(value: unknown): value is PendingQuestion {
111
112
  function parsePendingQuestion(contents: string, file: string): PendingQuestion {
112
113
  const value: unknown = JSON.parse(contents);
113
114
  if (!isPendingQuestion(value)) throw new Error(`Invalid pending question record: ${file}`);
114
- return value;
115
+ return {
116
+ ...value,
117
+ questions: value.questions.map((question) => ({
118
+ ...question,
119
+ ...(question.recommendedIndex !== undefined
120
+ && (question.recommendedIndex < 0 || question.recommendedIndex >= question.options.length)
121
+ ? { recommendedIndex: undefined } : {}),
122
+ })),
123
+ };
115
124
  }
116
125
 
117
126
  export function isAskUserQuestionResult(value: unknown): value is AskUserQuestionResult {
@@ -176,7 +185,13 @@ export function createPendingQuestion(
176
185
  return {
177
186
  id,
178
187
  ...(sessionId === undefined ? {} : { sessionId }),
179
- questions: request.questions.map(({ id, question, options }) => ({ id, question, options })),
188
+ ...(request.runId === undefined ? {} : { runId: request.runId }),
189
+ questions: request.questions.map(({ id, question, options, impact, recommendedIndex }) => ({
190
+ id, question, options,
191
+ ...(impact === undefined ? {} : { impact }),
192
+ ...(recommendedIndex !== undefined && Number.isInteger(recommendedIndex) && recommendedIndex >= 0 && recommendedIndex < options.length
193
+ ? { recommendedIndex } : {}),
194
+ })),
180
195
  startedAt: (deps.now ?? (() => new Date()))().toISOString(),
181
196
  ...(presentation.expiresAt === undefined ? {} : { expiresAt: presentation.expiresAt }),
182
197
  surface: presentation.surface,
@@ -13,6 +13,7 @@
13
13
  // behaviour exactly.
14
14
 
15
15
  import type { HitlDelivery } from '../hitl/types.js';
16
+ import type { ClarificationImpact } from '../hitl/clarification-policy.js';
16
17
  export type { HitlDelivery };
17
18
 
18
19
  export interface QuestionOption {
@@ -37,6 +38,9 @@ export interface Question {
37
38
  question: string;
38
39
  /** 2–4 pre-defined choices. */
39
40
  options: QuestionOption[];
41
+ impact?: ClarificationImpact;
42
+ /** Zero-based position in options; an out-of-range value is ignored. */
43
+ recommendedIndex?: number;
40
44
  /** When true, user can pick more than one option; Space toggles,
41
45
  * Enter submits. Default false. */
42
46
  multiSelect?: boolean;
@@ -49,6 +53,7 @@ export interface Question {
49
53
 
50
54
  export interface AskUserQuestionRequest {
51
55
  questions: Question[]; // 1–3
56
+ runId?: string;
52
57
  /** AXON F4 — optional routing hint. When set, callers with HITL
53
58
  * resolvers can fan the prompt out to Telegram/Discord/etc;
54
59
  * omitted (or `'modal'` / `'terminal'`) keeps the existing TUI
@@ -165,6 +170,9 @@ export function parseQuestionRequest(
165
170
  header,
166
171
  question,
167
172
  options,
173
+ ...(item.impact === 'low' || item.impact === 'medium' || item.impact === 'high' || item.impact === 'critical' ? { impact: item.impact } : {}),
174
+ ...(typeof item.recommendedIndex === 'number' && Number.isInteger(item.recommendedIndex)
175
+ && item.recommendedIndex >= 0 && item.recommendedIndex < options.length ? { recommendedIndex: item.recommendedIndex } : {}),
168
176
  multiSelect: item.multiSelect === true,
169
177
  includeOther: item.includeOther !== false, // default true
170
178
  });
@@ -186,5 +194,6 @@ export function parseQuestionRequest(
186
194
 
187
195
  const req: AskUserQuestionRequest = { questions: parsed };
188
196
  if (delivery !== undefined) req.delivery = delivery;
197
+ if (typeof raw.runId === 'string') req.runId = raw.runId;
189
198
  return { ok: true, req };
190
199
  }
@@ -0,0 +1,19 @@
1
+ import { openSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { dlopen, FFIType } from 'bun:ffi';
4
+ import { claimSetupLinkToken } from './setup-link-tokens.js';
5
+
6
+ const [mode, dir, token, now] = process.argv.slice(2);
7
+ if (mode === 'claim') {
8
+ process.stdout.write(JSON.stringify(claimSetupLinkToken(token, { dir, now: Number(now) })));
9
+ } else if (mode === 'hold') {
10
+ const fd = openSync(join(dir, 'setup-link-tokens.json.lock'), 'r+');
11
+ const libc = dlopen(process.platform === 'darwin' ? '/usr/lib/libSystem.B.dylib' : 'libc.so.6', {
12
+ flock: { args: [FFIType.i32, FFIType.i32], returns: FFIType.i32 },
13
+ });
14
+ if (libc.symbols.flock(fd, 2) !== 0) throw new Error('failed to acquire lock');
15
+ process.stdout.write('locked\n');
16
+ await new Promise<void>(() => { setInterval(() => {}, 1000); });
17
+ } else {
18
+ throw new Error(`unknown fixture mode: ${mode}`);
19
+ }
@@ -0,0 +1,164 @@
1
+ import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
2
+ import { closeSync, constants, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { dlopen, FFIType } from 'bun:ffi';
5
+ import { getElanousConfigDir } from '../elanous-config-dir.js';
6
+
7
+ export const SETUP_LINK_TTL_MS = 10 * 60_000;
8
+ const SETUP_BEARER_TTL_MS = 24 * 60 * 60_000;
9
+ const LINK_PREFIX = 'els_';
10
+ const BEARER_PREFIX = 'elsb_';
11
+ const HASH_PATTERN = /^[a-f0-9]{64}$/;
12
+ const LINK_PATTERN = /^els_[A-Za-z0-9_-]{43}$/;
13
+ const BEARER_PATTERN = /^elsb_[A-Za-z0-9_-]{43}$/;
14
+
15
+ interface SetupLinkEntry {
16
+ hash: string;
17
+ expiresAt: string;
18
+ usedAt?: string;
19
+ bearerHash?: string;
20
+ bearerExpiresAt?: string;
21
+ }
22
+
23
+ type ClaimResult =
24
+ | { ok: true; bearer: string; expiresAt: string }
25
+ | { ok: false; reason: 'unknown' | 'used' | 'expired' };
26
+
27
+ function storePath(dir: string): string { return join(dir, 'setup-link-tokens.json'); }
28
+ function hashOf(token: string): string { return createHash('sha256').update(token).digest('hex'); }
29
+ function mint(prefix: string): string { return `${prefix}${randomBytes(32).toString('base64url')}`; }
30
+
31
+ function readStore(dir: string): SetupLinkEntry[] {
32
+ let raw: string;
33
+ try {
34
+ const path = storePath(dir);
35
+ const st = lstatSync(path);
36
+ if (!st.isFile() || (st.mode & 0o077) !== 0) throw new Error('setup link store must be a private regular file (0600)');
37
+ raw = readFileSync(path, 'utf8');
38
+ } catch (error) {
39
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') return [];
40
+ throw error;
41
+ }
42
+ const parsed: unknown = JSON.parse(raw);
43
+ if (!parsed || typeof parsed !== 'object' || !('tokens' in parsed) || !Array.isArray(parsed.tokens)) {
44
+ throw new Error('setup link store is malformed');
45
+ }
46
+ const entries: unknown[] = parsed.tokens;
47
+ if (!entries.every((entry) => {
48
+ if (!entry || typeof entry !== 'object') return false;
49
+ const t = entry as Partial<SetupLinkEntry>;
50
+ return typeof t.hash === 'string' && HASH_PATTERN.test(t.hash)
51
+ && typeof t.expiresAt === 'string' && Number.isFinite(Date.parse(t.expiresAt))
52
+ && (t.usedAt === undefined || (typeof t.usedAt === 'string' && Number.isFinite(Date.parse(t.usedAt))))
53
+ && (t.bearerHash === undefined || (typeof t.bearerHash === 'string' && HASH_PATTERN.test(t.bearerHash)))
54
+ && (t.bearerExpiresAt === undefined || (typeof t.bearerExpiresAt === 'string' && Number.isFinite(Date.parse(t.bearerExpiresAt))))
55
+ && ((t.usedAt === undefined && t.bearerHash === undefined && t.bearerExpiresAt === undefined)
56
+ || (t.usedAt !== undefined && t.bearerHash !== undefined && t.bearerExpiresAt !== undefined));
57
+ })) throw new Error('setup link store is malformed');
58
+ return entries as SetupLinkEntry[];
59
+ }
60
+
61
+ function writeStore(dir: string, tokens: SetupLinkEntry[]): void {
62
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
63
+ const path = storePath(dir);
64
+ const tmp = `${path}.${process.pid}.${randomBytes(4).toString('hex')}.tmp`;
65
+ try {
66
+ writeFileSync(tmp, JSON.stringify({ tokens }, null, 2), { flag: 'wx', mode: 0o600 });
67
+ renameSync(tmp, path);
68
+ } finally {
69
+ try { unlinkSync(tmp); } catch (error) {
70
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
71
+ }
72
+ }
73
+ }
74
+
75
+ // A persistent inode and an OS advisory lock avoid stale owner files after an abrupt process exit.
76
+ // Never unlink this file: a replacement inode would let two processes hold different locks.
77
+ // Loaded on first use, not at import: libc.so.6 does not exist on Windows and this module sits on the CLI start path (WIN2 10-02).
78
+ type Flock = (fd: number, op: number) => number;
79
+ let flockFn: Flock | null | undefined;
80
+ function osFlock(): Flock | null {
81
+ if (flockFn !== undefined) return flockFn;
82
+ if (process.platform === 'win32') return (flockFn = null);
83
+ const lib = dlopen(process.platform === 'darwin' ? '/usr/lib/libSystem.B.dylib' : 'libc.so.6', {
84
+ flock: { args: [FFIType.i32, FFIType.i32], returns: FFIType.i32 },
85
+ });
86
+ return (flockFn = lib.symbols.flock as Flock);
87
+ }
88
+ const LOCK_EX = 2;
89
+ const LOCK_NB = 4;
90
+ const LOCK_UN = 8;
91
+
92
+ function withLock<T>(dir: string, fn: () => T): T {
93
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
94
+ const flock = osFlock();
95
+ // Windows: no advisory flock and no POSIX modes — the store still writes by atomic rename.
96
+ if (!flock) return fn();
97
+ const lock = `${storePath(dir)}.lock`;
98
+ const fd = openSync(lock, constants.O_CREAT | constants.O_RDWR | constants.O_NOFOLLOW, 0o600);
99
+ try {
100
+ const stat = fstatSync(fd);
101
+ const current = lstatSync(lock);
102
+ if (!stat.isFile() || (stat.mode & 0o077) !== 0 || stat.ino !== current.ino || stat.dev !== current.dev) {
103
+ throw new Error('setup link lock must be a private regular file (0600)');
104
+ }
105
+ const until = Date.now() + 3000;
106
+ while (flock(fd, LOCK_EX | LOCK_NB) !== 0) {
107
+ if (Date.now() > until) throw new Error('setup link store is locked — try again');
108
+ Bun.sleepSync(20);
109
+ }
110
+ try {
111
+ const locked = lstatSync(lock);
112
+ if (stat.ino !== locked.ino || stat.dev !== locked.dev) throw new Error('setup link lock changed');
113
+ return fn();
114
+ } finally {
115
+ flock(fd, LOCK_UN);
116
+ }
117
+ } finally {
118
+ closeSync(fd);
119
+ }
120
+ }
121
+
122
+ /** The plaintext link token is returned once; only its SHA-256 digest is persisted. */
123
+ export function issueSetupLinkToken(opts: { dir?: string; now?: number } = {}): { token: string; expiresAt: string } {
124
+ const dir = opts.dir ?? getElanousConfigDir();
125
+ const now = opts.now ?? Date.now();
126
+ const token = mint(LINK_PREFIX);
127
+ const expiresAt = new Date(now + SETUP_LINK_TTL_MS).toISOString();
128
+ withLock(dir, () => writeStore(dir, [...readStore(dir), { hash: hashOf(token), expiresAt }]));
129
+ return { token, expiresAt };
130
+ }
131
+
132
+ /** Claim exactly once under the same lock used by issuance. Expired and used links retain their diagnostic status. */
133
+ export function claimSetupLinkToken(token: string, opts: { dir?: string; now?: number } = {}): ClaimResult {
134
+ if (typeof token !== 'string' || !LINK_PATTERN.test(token)) return { ok: false, reason: 'unknown' };
135
+ const dir = opts.dir ?? getElanousConfigDir();
136
+ const now = opts.now ?? Date.now();
137
+ return withLock(dir, () => {
138
+ const entries = readStore(dir);
139
+ const digest = Buffer.from(hashOf(token), 'hex');
140
+ const hit = entries.find((entry) => timingSafeEqual(digest, Buffer.from(entry.hash, 'hex')));
141
+ if (!hit) return { ok: false, reason: 'unknown' };
142
+ if (hit.usedAt) return { ok: false, reason: 'used' };
143
+ if (Date.parse(hit.expiresAt) <= now) return { ok: false, reason: 'expired' };
144
+ const bearer = mint(BEARER_PREFIX);
145
+ const expiresAt = new Date(now + SETUP_BEARER_TTL_MS).toISOString();
146
+ hit.usedAt = new Date(now).toISOString();
147
+ hit.bearerHash = hashOf(bearer);
148
+ hit.bearerExpiresAt = expiresAt;
149
+ writeStore(dir, entries);
150
+ return { ok: true, bearer, expiresAt };
151
+ });
152
+ }
153
+
154
+ /** A setup bearer is not an owner or temp-owner token; validate it only on setup-allowed routes. */
155
+ export function matchSetupBearer(bearer: string, opts: { dir?: string; now?: number } = {}): { expiresAt: string } | null {
156
+ if (typeof bearer !== 'string' || !BEARER_PATTERN.test(bearer)) return null;
157
+ let entries: SetupLinkEntry[];
158
+ try { entries = readStore(opts.dir ?? getElanousConfigDir()); } catch { return null; }
159
+ const digest = Buffer.from(hashOf(bearer), 'hex');
160
+ const now = opts.now ?? Date.now();
161
+ const hit = entries.find((entry) => entry.bearerHash && entry.bearerExpiresAt
162
+ && timingSafeEqual(digest, Buffer.from(entry.bearerHash, 'hex')) && Date.parse(entry.bearerExpiresAt) > now);
163
+ return hit?.bearerExpiresAt ? { expiresAt: hit.bearerExpiresAt } : null;
164
+ }
@@ -255,6 +255,7 @@ const STEP_CMDS: Record<GateStepName, { cmd: string; args: string[]; timeoutMs:
255
255
  'cli-smoke': { cmd: 'bun', args: ['bin/elanous.mjs', '--help'], timeoutMs: 60_000 },
256
256
  };
257
257
 
258
+ const DEPENDENCY_INSTALL_TIMEOUT_MS = 300_000;
258
259
  const MAX_LOGGED_TEST_FILTERS = 6;
259
260
 
260
261
  /** Platform runners own Kotlin/Swift execution; Bun must never receive their paths as test filters. */
@@ -289,6 +290,17 @@ export async function runIntegrityGate(
289
290
  const runCmd = opts.runCmd ?? createRunCmd();
290
291
  const results: GateStep[] = [];
291
292
  const logs: string[] = [];
293
+ // A Pod worktree has no node_modules unless the child happened to install them (the Pod clone has none to link), and Bun
294
+ // then auto-installs at import time — «Unexpected while resolving package 'commander'» failed cli-smoke as an
295
+ // «environment deficiency» three times on 10-02 (#22849 · #22919). Install from the lockfile before measuring instead.
296
+ if (steps.length > 0 && existsSync(join(cwd, 'bin', 'elanous.mjs')) && existsSync(join(cwd, 'bun.lock'))
297
+ && !existsSync(join(cwd, 'node_modules', 'commander', 'package.json'))) {
298
+ const install = await runCmd('bun', ['install', '--frozen-lockfile'], cwd, DEPENDENCY_INSTALL_TIMEOUT_MS);
299
+ const installed = install.code === 0 && !install.timedOut;
300
+ debug.log('gate.deps', 'install', { installed, code: install.code, timedOut: install.timedOut });
301
+ const tail = (install.stderr || install.stdout).split('\n').filter(Boolean).slice(-2).join(' | ').slice(0, 200);
302
+ logs.push(`[deps] ${installed ? 'INSTALLED' : 'FAIL'} bun install --frozen-lockfile — node_modules/commander was missing${installed ? '' : `: ${tail}`}`);
303
+ }
292
304
  let passed = true;
293
305
  let testPassCount: number | undefined;
294
306
  let sawPassCountRegression = false;
@@ -107,6 +107,8 @@ export async function runDaemonPromptTurn(opts: {
107
107
  surfaceResolutionReason?: SurfaceKindResolutionReason;
108
108
  /** Optional confirm channels for the triggering chat surface. */
109
109
  surfaceHitlChannels?: ConfirmChannel[];
110
+ /** Server-authenticated human owner for this request; absent for delegated or unverified callers. */
111
+ verifiedOwner?: { id: string };
110
112
  /** Optional structured-question channels for the triggering chat surface. */
111
113
  surfaceQuestionChannels?: QuestionChannel[];
112
114
  /** CWD for fs-bound tool dispatch (Read / Grep). Defaults to
@@ -226,6 +228,7 @@ export async function runDaemonPromptTurn(opts: {
226
228
  ...(request.source?.kind === 'daemon-api' && request.source.route === '/a2a'
227
229
  ? { requestOrigin: 'external-agent' as const } : {}),
228
230
  ...(request.userText ? { userText: request.userText } : {}),
231
+ ...(opts.verifiedOwner ? { verifiedOwner: opts.verifiedOwner } : {}),
229
232
  ...(opts.surface !== undefined || sourceResolution.surface !== 'unknown' ? { surface } : {}),
230
233
  ...(opts.surfaceHitlChannels ? { surfaceHitlChannels: opts.surfaceHitlChannels } : {}),
231
234
  ...(opts.surfaceQuestionChannels ? { surfaceQuestionChannels: opts.surfaceQuestionChannels } : {}),
@@ -16,6 +16,8 @@ import {
16
16
  dispatchBrowserRead,
17
17
  } from '../../tool-runtime/browser-runtime.js';
18
18
  import { askUserQuestionRuntime } from '../../tool-runtime/ask-user-question-runtime.js';
19
+ import { buildElanousHoldTool, buildPtyControlTool, dispatchElanousHold, dispatchPtyControl } from '../../tool-runtime/elanous-control-runtimes.js';
20
+ import { ptyAvailable } from '../../pty-shell/registry.js';
19
21
  import { dispatchToolByName, listToolRuntimes } from '../../tool-runtime/registry.js';
20
22
  import { ACP_SESSION_RUNTIMES } from '../../tool-runtime/acp-session-runtime.js';
21
23
  import { ClaudeSubscriptionNotAllowedError } from '../../policy/claude-subscription-guard.js';
@@ -340,6 +342,7 @@ export function toolSurface(kind: DaemonToolSurfaceKind, cfg?: import('../../use
340
342
  askUserQuestionRuntime.spec,
341
343
  ...ACP_SESSION_RUNTIMES.map((runtime) => runtime.spec),
342
344
  ...shared.specs, // L2 core + L3 finance(gated) — buildSharedAppTools 단일 출처(전 서피스 공용)
345
+ ...(ptyAvailable() ? [buildElanousHoldTool(), buildPtyControlTool()] : []),
343
346
  ];
344
347
 
345
348
  /** Shared dispatcher for chat-surface tools. Reused by 'chat' AND
@@ -392,11 +395,16 @@ export function toolSurface(kind: DaemonToolSurfaceKind, cfg?: import('../../use
392
395
  // L2 core + L3 finance(gated) — buildSharedAppTools 단일 dispatch · B5: 진행 한 줄(emitFeedback)·세션·호출 id 를 넘긴다.
393
396
  return shared.dispatch(name, args, {
394
397
  surface: 'tui',
398
+ ...(ctx.requestOrigin ? { requestOrigin: ctx.requestOrigin } : {}),
399
+ ...(ctx.verifiedOwner ? { verifiedOwner: ctx.verifiedOwner } : {}),
395
400
  ...(ctx.sessionId ? { sessionId: ctx.sessionId } : {}),
401
+ ...(ctx.surfaceHitlChannels ? { confirmChannels: ctx.surfaceHitlChannels } : {}),
396
402
  ...(ctx.toolCallId ? { toolCallId: ctx.toolCallId } : {}),
397
403
  ...(ctx.emitFeedback ? { emitFeedback: ctx.emitFeedback } : {}),
398
404
  });
399
405
  }
406
+ if (ptyAvailable() && name === 'ElanousHold') return dispatchElanousHold(args);
407
+ if (ptyAvailable() && name === 'PtyControl') return dispatchPtyControl(args);
400
408
  if (name === 'Bash') {
401
409
  // PLAN-ios-rich-dev-feedback-hydrate M3 (2026-05-13) — pass
402
410
  // sessionId + emitFeedback so dispatchBash emits tool.progress
@@ -61,6 +61,8 @@ export interface DaemonToolDispatchCtx {
61
61
  sessionId?: string;
62
62
  /** Trusted server-side classification of the current turn, never supplied as a tool argument. */
63
63
  requestOrigin?: RequestOrigin;
64
+ /** Owner principal verified at the server request boundary, not from input source metadata. */
65
+ verifiedOwner?: { id: string };
64
66
  /** Ingestion entry class. Omit for conservative external-verbatim fallback. */
65
67
  entry?: IngestionEntry;
66
68
  /** M5 PR 2 (PLAN-rich-dev-feedback-multi-surface · 2026-05-13) —
@@ -41,6 +41,7 @@ export function buildWebSearchTool(): LLMToolSpec {
41
41
  'Search the public web via the daemon\'s configured provider (Grok / ' +
42
42
  'Firecrawl / etc.). When no key is available, falls back to keyless DuckDuckGo. ' +
43
43
  'Returns a ranked list of URLs + titles + snippets. ' +
44
+ 'For fact questions, answer directly from the snippets and URLs; read the full body only when insufficient. ' +
44
45
  'Pair with a follow-up Read or external WebFetch when the model needs ' +
45
46
  'the full article body.',
46
47
  parameters: {
@@ -18,6 +18,7 @@ import { listCodexAccountsInStore } from '../oauth/codex-account-store.js';
18
18
  import { loadTokens } from '../oauth/store.js';
19
19
  import { envLiteral } from '../platform/env-literal.js';
20
20
  import { describeResetCreditExpiry } from './codex-reset-credit-state.js';
21
+ import { visibleWidth } from '../tui.js';
21
22
  import { listCodexResetCredits, type CodexResetCreditsResult } from './codex-reset-credits.js';
22
23
  import type {
23
24
  AccountUsageRow,
@@ -268,6 +269,80 @@ async function collectOpenRouterRows(deps: UnifiedUsageDeps): Promise<AccountUsa
268
269
  }
269
270
  }
270
271
 
272
+ export function formatUsageCompact(report: UnifiedUsageReport, options: { width: number; nowMs: number }): string[] {
273
+ return report.rows.map((row) => {
274
+ const credits = row.credits;
275
+ const subscription = row.subscription;
276
+ const remaining = subscription.status === 'available' && Number.isFinite(subscription.remainingPercent)
277
+ ? subscription.remainingPercent
278
+ : credits.status === 'ok' && credits.usedPercent !== null && Number.isFinite(credits.usedPercent)
279
+ ? Math.round((100 - credits.usedPercent) * 10) / 10
280
+ : null;
281
+ const period = subscription.status === 'available'
282
+ ? subscription.windowKind
283
+ : credits.status === 'ok' ? credits.periodType : null;
284
+ const reset = subscription.status === 'available' ? subscription.resetsAt
285
+ : credits.status === 'ok' && credits.periodEnd ? Date.parse(credits.periodEnd) : Number.NaN;
286
+ const periodLabel = period ? `${formatCompactPeriod(period)} ` : '';
287
+ const when = period === 'prepaid-usd' ? '' : !isValidEpochMs(reset)
288
+ ? ` · ${periodLabel}시각 모름`
289
+ : reset <= options.nowMs
290
+ ? ` · ${periodLabel}재조회 필요`
291
+ : ` · ${periodLabel}${formatCompactReset(reset, options.nowMs)}에 다시 참`;
292
+ const balance = credits.status === 'ok' && credits.balance !== null && Number.isFinite(credits.balance)
293
+ ? ` · 크레딧 ${new Intl.NumberFormat('en-US', { maximumFractionDigits: credits.balance >= 1_000 ? 0 : 1 }).format(credits.balance)}` : '';
294
+ const prefix = `${row.provider} `;
295
+ const name = row.accountName.replace(/[\r\n\t\x00-\x1f\x7f]/g, ' ');
296
+ const remainingLabel = `남은 ${remaining === null ? '잔량 모름' : `${remaining}%`}`;
297
+ const width = Number.isFinite(options.width) ? Math.max(0, Math.floor(options.width)) : 120;
298
+ const suffix = ` · ${remainingLabel}${when}${balance}`;
299
+ const available = width - visibleWidth(prefix) - visibleWidth(suffix);
300
+ if (available >= 1) {
301
+ const account = visibleWidth(name) <= available ? name
302
+ : available === 1 ? '…' : `${compactSlice(name, available - 1)}…`;
303
+ return `${prefix}${account}${suffix}`;
304
+ }
305
+ // The fixed labels can exceed very narrow widths even with no account name.
306
+ // Keep the measured value when possible rather than slicing its digits.
307
+ const essential = `${row.provider} · ${remainingLabel}`;
308
+ if (visibleWidth(essential) <= width) return essential;
309
+ if (visibleWidth(remainingLabel) <= width) return remainingLabel;
310
+ const value = remaining === null ? '잔량 모름' : `${remaining}%`;
311
+ if (visibleWidth(value) <= width) return value;
312
+ return width > 0 ? '…' : '';
313
+ });
314
+ }
315
+
316
+ function formatCompactPeriod(value: string): string {
317
+ const period = value.replace(/^USAGE_PERIOD_TYPE_/, '').toLowerCase();
318
+ switch (period) {
319
+ case 'weekly': return '주간';
320
+ case 'monthly': return '월간';
321
+ case 'daily': return '일간';
322
+ default: return period;
323
+ }
324
+ }
325
+
326
+ function formatCompactReset(reset: number, nowMs: number): string {
327
+ const remaining = reset - nowMs;
328
+ if (remaining < 86_400_000) {
329
+ if (remaining < 3_600_000) return `${Math.ceil(remaining / 60_000)}분 뒤`;
330
+ return `${Math.ceil(remaining / 3_600_000)}시간 뒤`;
331
+ }
332
+ const date = new Date(reset);
333
+ const weekdays = ['일', '월', '화', '수', '목', '금', '토'];
334
+ return `${date.getMonth() + 1}/${date.getDate()} (${weekdays[date.getDay()]}) ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
335
+ }
336
+
337
+ function compactSlice(text: string, width: number): string {
338
+ let result = '';
339
+ for (const { segment } of new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(text)) {
340
+ if (visibleWidth(result + segment) > width) break;
341
+ result += segment;
342
+ }
343
+ return result;
344
+ }
345
+
271
346
  export function formatUnifiedUsage(report: UnifiedUsageReport): string {
272
347
  const lines: string[] = [
273
348
  'usage — 계정마다 한 행 · 크레딧 축과 구독 축은 각각 칸',
@@ -1,35 +1,62 @@
1
1
  import { existsSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { debug } from './debug/log.js';
4
- import { getSecret, setSecretAsync } from './nexus/config/secrets/index.js';
5
- import { userConfigPath } from './nexus/config/paths.js';
4
+ import { getSecret, setSecretAsync, currentBackendId } from './nexus/config/secrets/index.js';
5
+ import { userConfigPath, secretsPath } from './nexus/config/paths.js';
6
6
  import { isSecretRef, makeSecretRef, secretIdFromRef } from './nexus/config/types.js';
7
7
  import { acquireLockAsync } from './storage/file-lock.js';
8
8
  import type { UserConfig } from './user-config.js';
9
9
 
10
10
  export type ChannelBotPlatform = 'telegram' | 'discord';
11
- export type ChannelBotTokenSource = 'tokenRef' | 'env' | 'plaintext';
11
+ export type ChannelBotTokenSource = 'tokenRef' | 'env' | 'botToken' | 'channels.main';
12
+ export type ChannelBotTokenResolution = { token: string; source: ChannelBotTokenSource; reason?: never }
13
+ | { token: ''; source?: never; reason: string };
12
14
 
13
15
  /** The tab reference is read from the same persistent config as the NEXUS switch. */
14
16
  export function resolveChannelBotToken(
15
17
  platform: ChannelBotPlatform,
16
18
  cfg: Pick<UserConfig, 'telegram' | 'discord'> & { tabs?: Record<string, { tokenRef?: unknown }> },
17
- ): { token: string; source: ChannelBotTokenSource } | undefined {
19
+ ): ChannelBotTokenResolution {
20
+ let reason: string | undefined;
18
21
  const ref = cfg.tabs?.[`${platform}:1`]?.tokenRef;
19
22
  if (isSecretRef(ref)) {
20
23
  const id = secretIdFromRef(ref);
21
- const token = id ? getSecret(id) : undefined;
22
- if (token) return { token, source: 'tokenRef' };
24
+ if (id) {
25
+ const backend = currentBackendId();
26
+ if (backend !== 'file') {
27
+ reason = `backend ${backend} not sync-readable`;
28
+ } else {
29
+ const token = getSecret(id);
30
+ if (token) {
31
+ debug.log('channel-bot.token', 'resolved', { kind: platform, source: 'tokenRef' });
32
+ return { token, source: 'tokenRef' };
33
+ }
34
+ reason = `ref-id-missing ${id} in ${secretsPath()}`;
35
+ }
36
+ }
23
37
  }
24
38
  const envName = platform === 'telegram' ? 'ELANOUS_TELEGRAM_BOT_TOKEN' : 'ELANOUS_DISCORD_BOT_TOKEN';
25
39
  const envToken = process.env[envName];
26
- if (envToken) return { token: envToken, source: 'env' };
40
+ if (envToken) {
41
+ debug.log('channel-bot.token', 'resolved', { kind: platform, source: 'env' });
42
+ return { token: envToken, source: 'env' };
43
+ }
27
44
  const token = cfg[platform]?.botToken;
28
45
  if (token) {
29
- debug.log('channel-bot.token', 'plaintext-fallback', { platform });
30
- return { token, source: 'plaintext' };
46
+ debug.log('channel-bot.token', 'resolved', { kind: platform, source: 'botToken' });
47
+ return { token, source: 'botToken' };
48
+ }
49
+ if (platform === 'telegram') {
50
+ const channels = cfg.telegram?.channels;
51
+ const channelToken = (channels?.find(c => c.name === 'main') ?? channels?.[0])?.botToken;
52
+ if (channelToken) {
53
+ debug.log('channel-bot.token', 'resolved', { kind: platform, source: 'channels.main' });
54
+ return { token: channelToken, source: 'channels.main' };
55
+ }
31
56
  }
32
- return undefined;
57
+ reason ??= 'no-source';
58
+ debug.log('channel-bot.token', 'unresolved', { kind: platform, reason });
59
+ return { token: '', reason };
33
60
  }
34
61
 
35
62
  /** Publish the secret before its reference, under the config lock. Other config keys survive unchanged. */
@@ -0,0 +1,14 @@
1
+ /** U1a — how long an empty-input Ctrl+C stays armed (and its hint stays on screen). */
2
+ export const CTRL_C_ARM_MS = 2_000;
3
+
4
+ export function resolveCtrlCInInput({ text, now, armedAt }: {
5
+ text: string;
6
+ now: number;
7
+ armedAt: number | null;
8
+ }): { action: 'clear-input' | 'arm-exit' | 'exit'; armedAt: number | null; hint: string | null } {
9
+ if (text.length > 0) return { action: 'clear-input', armedAt: null, hint: null };
10
+ if (armedAt !== null && now >= armedAt && now - armedAt <= CTRL_C_ARM_MS) {
11
+ return { action: 'exit', armedAt: null, hint: null };
12
+ }
13
+ return { action: 'arm-exit', armedAt: now, hint: '한 번 더 누르면 나갑니다 · /quit' };
14
+ }