elanous 0.2.9 → 0.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (361) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/2DsiDfDCks44g1cPUMhiW/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/24-e6d28380efab7faf.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/3615-740dd270764ffa24.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/3734-1015fe991eb9b7e7.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/5012-a7fe6f3c8b6fe45a.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/5783-ef40ebfc91d55230.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/62-ff227c3edb8b248b.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/7633-1b3709b0854c9412.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/7951-8b03dbb10b24cd06.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/8067-87ce253ec2f23697.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 8195-6fde3d7a39859206.js} +5 -5
  14. package/apps/pwa/out/_next/static/chunks/8894-b5fb36bab837cd29.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/9454-84d0d5d77446bd92.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/988-5f92e7ab7ccea3dd.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/9989-5582582f06429840.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/app/approvals/page-d2253d7e58ef1890.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-5441d4a9ab35e518.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/app/board/page-d9b12d680922d980.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-f972fc1c00a62e88.js → page-54b32bd27726588d.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/app/bots/page-e268d6d145827a22.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/app/chat/page-f8116559c7424da7.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/app/consult/page-1085399fd0f32112.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/control/page-1df686a14f65895a.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/app/design-check/page-54e1f966ab60a4dc.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/app/editor/{page-973996d584f008c1.js → page-7ed95f296f5ee8e9.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/exec/page-11edac668d77e5c3.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/field/page-59e2b9a22cd5a6d5.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/app/intake/page-2976df21ad74ab48.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/app/layout-34d211a67f16d510.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/live/page-8ff652d5d950639b.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/app/market/{page-8b0402a46a749a3d.js → page-b31610deda76337b.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-037f83e2a1edc8e2.js → page-6cb0e1bbefe5fb3d.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/morning/page-a286a152cf584576.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/not-found-d9237581e7003bcb.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/app/observatory/page-83274e761643fdd2.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-ceee6cfb1d4e6d68.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-9c9f8bb3e4a64091.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c17217603ea3426a.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/app/page-efb6177cbf1e8a51.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/app/reflection/page-d307523703bc4df3.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-462f4e4bab45ce7d.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/sessions/page-8990e96f33af3837.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-0df30e0c7e50148a.js → page-e168fa1030af0548.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/app/settings/page-1233e05a4068bc9c.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-13a1840b1fac337a.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/setup/page-64a29fc80b11ab55.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/share/page-68e787339a16b3dc.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/showroom/page-4fddc90b23feb215.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/tasks/page-886ba8260dc79c79.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/term/page-0f26507a1722e40b.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/today/page-109d4ccd42259c9d.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/trace/page-f5c2a4e510726774.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/vault/page-b2e0039416f42522.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-90670589b87abea8.js → page-26102fd7350f171e.js} +1 -1
  57. package/apps/pwa/out/_next/static/chunks/app/workflows/page-708948bc8a802794.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/workspace/page-5e5611620f78fb0b.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-35bedde6d885f08f.js → page-139ba5c367bbf60a.js} +1 -1
  60. package/apps/pwa/out/_next/static/chunks/main-app-202b4e81f33895ea.js +1 -0
  61. package/apps/pwa/out/_next/static/css/43feca5fd387dcbd.css +1 -0
  62. package/apps/pwa/out/approvals/index.html +1 -1
  63. package/apps/pwa/out/approvals/index.txt +14 -14
  64. package/apps/pwa/out/autopilot/index.html +1 -1
  65. package/apps/pwa/out/autopilot/index.txt +14 -14
  66. package/apps/pwa/out/board/index.html +1 -1
  67. package/apps/pwa/out/board/index.txt +14 -14
  68. package/apps/pwa/out/botlab/index.html +1 -1
  69. package/apps/pwa/out/botlab/index.txt +14 -14
  70. package/apps/pwa/out/bots/index.html +1 -1
  71. package/apps/pwa/out/bots/index.txt +14 -14
  72. package/apps/pwa/out/chat/index.html +1 -1
  73. package/apps/pwa/out/chat/index.txt +14 -14
  74. package/apps/pwa/out/consult/index.html +1 -1
  75. package/apps/pwa/out/consult/index.txt +14 -14
  76. package/apps/pwa/out/control/index.html +1 -1
  77. package/apps/pwa/out/control/index.txt +14 -14
  78. package/apps/pwa/out/design-check/index.html +1 -1
  79. package/apps/pwa/out/design-check/index.txt +14 -14
  80. package/apps/pwa/out/editor/index.html +1 -1
  81. package/apps/pwa/out/editor/index.txt +14 -14
  82. package/apps/pwa/out/exec/index.html +1 -1
  83. package/apps/pwa/out/exec/index.txt +14 -14
  84. package/apps/pwa/out/field/index.html +1 -1
  85. package/apps/pwa/out/field/index.txt +14 -14
  86. package/apps/pwa/out/healthz +1 -1
  87. package/apps/pwa/out/index.html +1 -1
  88. package/apps/pwa/out/index.txt +14 -14
  89. package/apps/pwa/out/intake/index.html +1 -1
  90. package/apps/pwa/out/intake/index.txt +14 -14
  91. package/apps/pwa/out/live/index.html +1 -1
  92. package/apps/pwa/out/live/index.txt +14 -14
  93. package/apps/pwa/out/market/index.html +1 -1
  94. package/apps/pwa/out/market/index.txt +14 -14
  95. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  96. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  97. package/apps/pwa/out/morning/index.html +1 -1
  98. package/apps/pwa/out/morning/index.txt +14 -14
  99. package/apps/pwa/out/observatory/index.html +1 -1
  100. package/apps/pwa/out/observatory/index.txt +14 -14
  101. package/apps/pwa/out/ops/checklist/index.html +1 -1
  102. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  103. package/apps/pwa/out/ops/release/index.html +1 -1
  104. package/apps/pwa/out/ops/release/index.txt +14 -14
  105. package/apps/pwa/out/ops/seats/index.html +1 -0
  106. package/apps/pwa/out/ops/seats/index.txt +30 -0
  107. package/apps/pwa/out/reflection/index.html +1 -1
  108. package/apps/pwa/out/reflection/index.txt +15 -15
  109. package/apps/pwa/out/scheduler/index.html +1 -1
  110. package/apps/pwa/out/scheduler/index.txt +14 -14
  111. package/apps/pwa/out/sessions/index.html +1 -1
  112. package/apps/pwa/out/sessions/index.txt +14 -14
  113. package/apps/pwa/out/settings/devices/index.html +1 -1
  114. package/apps/pwa/out/settings/devices/index.txt +14 -14
  115. package/apps/pwa/out/settings/index.html +1 -1
  116. package/apps/pwa/out/settings/index.txt +14 -14
  117. package/apps/pwa/out/setup/done/index.html +1 -1
  118. package/apps/pwa/out/setup/done/index.txt +14 -14
  119. package/apps/pwa/out/setup/index.html +1 -1
  120. package/apps/pwa/out/setup/index.txt +14 -14
  121. package/apps/pwa/out/share/index.html +1 -1
  122. package/apps/pwa/out/share/index.txt +14 -14
  123. package/apps/pwa/out/showroom/index.html +1 -1
  124. package/apps/pwa/out/showroom/index.txt +14 -14
  125. package/apps/pwa/out/tasks/index.html +1 -1
  126. package/apps/pwa/out/tasks/index.txt +14 -14
  127. package/apps/pwa/out/term/index.html +1 -1
  128. package/apps/pwa/out/term/index.txt +14 -14
  129. package/apps/pwa/out/today/index.html +1 -0
  130. package/apps/pwa/out/today/index.txt +33 -0
  131. package/apps/pwa/out/trace/index.html +1 -1
  132. package/apps/pwa/out/trace/index.txt +14 -14
  133. package/apps/pwa/out/vault/index.html +1 -1
  134. package/apps/pwa/out/vault/index.txt +14 -14
  135. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  136. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  137. package/apps/pwa/out/workflows/index.html +1 -1
  138. package/apps/pwa/out/workflows/index.txt +14 -14
  139. package/apps/pwa/out/workspace/index.html +1 -1
  140. package/apps/pwa/out/workspace/index.txt +14 -14
  141. package/apps/pwa/out/worktrees/index.html +1 -1
  142. package/apps/pwa/out/worktrees/index.txt +14 -14
  143. package/graphs/field/field-feed.yaml +2 -0
  144. package/graphs/implement-loop.yaml +1 -1
  145. package/package.json +1 -1
  146. package/plugins/card-followup/graphs/run-step.ts +48 -13
  147. package/plugins/doc-draft/graphs/doc-draft.yaml +3 -3
  148. package/plugins/doc-draft/graphs/run-step.ts +13 -6
  149. package/scripts/ci-pwa-build-gate.ts +160 -0
  150. package/scripts/coord-channel-watch.sh +42 -8
  151. package/scripts/coord-digest.ts +199 -0
  152. package/scripts/coord-post.sh +60 -3
  153. package/scripts/install.ps1 +68 -9
  154. package/scripts/install.sh +39 -12
  155. package/scripts/lib/pwa-scenarios/scenarios.ts +20 -0
  156. package/scripts/lib/tui-regress-checks.ts +146 -0
  157. package/scripts/memory-lifecycle-cycle.ts +2 -2
  158. package/scripts/ops-now-probe.ts +327 -0
  159. package/scripts/publish-internal-dist.ts +11 -1
  160. package/scripts/release-loop/cutoff.ts +19 -8
  161. package/scripts/release-loop/ops-upgrade-node.ts +72 -16
  162. package/scripts/release-loop/release-readiness.ts +9 -0
  163. package/scripts/release-loop/version-node.ts +58 -10
  164. package/scripts/repo-watch-cycle.ts +1 -1
  165. package/scripts/retro-cycle.ts +2 -2
  166. package/scripts/schedule-health-report.ts +1 -1
  167. package/scripts/storyboard/storyboard.ts +5 -161
  168. package/scripts/test-diet/lib.ts +30 -1
  169. package/scripts/test-diet/node.ts +82 -7
  170. package/scripts/test-timeout-audit.ts +114 -0
  171. package/scripts/tree-sync-alert.ts +1 -1
  172. package/scripts/tui-regress.ts +71 -0
  173. package/scripts/waitlist-daily-digest.ts +1 -1
  174. package/skills/explainer-video/SKILL.md +4 -0
  175. package/skills/explainer-video/engine/reel.mjs +18 -13
  176. package/skills/explainer-video/engine/reel.sh +24 -5
  177. package/skills/explainer-video/engine/stitch.mjs +145 -0
  178. package/skills/pitch-deck/SKILL.md +36 -0
  179. package/skills/pitch-deck/template.html +80 -0
  180. package/src/acp/codex-approval-adapter.ts +9 -3
  181. package/src/acp/core-turn-bridge.ts +117 -18
  182. package/src/acp/fact-question.ts +11 -0
  183. package/src/acp/server.ts +48 -7
  184. package/src/acp/tool-approval.ts +126 -0
  185. package/src/acp/turn-runner.ts +36 -0
  186. package/src/agent/monad-agent-turn.ts +8 -0
  187. package/src/ask-user-question/pending-questions.ts +25 -10
  188. package/src/ask-user-question/types.ts +9 -0
  189. package/src/auth/setup-link-tokens.ts +16 -5
  190. package/src/autopilot/build/integrity-gate.ts +12 -0
  191. package/src/boot/daemon-prompt-turn.ts +3 -0
  192. package/src/boot/daemon-tools/index.ts +8 -0
  193. package/src/boot/daemon-tools/types.ts +2 -0
  194. package/src/boot/daemon-tools/web-search.ts +1 -0
  195. package/src/chat/ctrl-c-in-input.ts +14 -0
  196. package/src/chat/index.ts +52 -0
  197. package/src/chat/pickers/state.ts +6 -2
  198. package/src/cli/agent-cli.ts +7 -0
  199. package/src/cli/decisions-cli.ts +25 -3
  200. package/src/cli/doctor-bundle-upload.ts +68 -0
  201. package/src/cli/doctor-cli.ts +38 -4
  202. package/src/cli/ops-cli.ts +20 -0
  203. package/src/cli/pod-cli.ts +49 -0
  204. package/src/cli/pr-cli.ts +31 -2
  205. package/src/cli/pwa-stop.ts +163 -13
  206. package/src/cli/pwa-test.ts +3 -0
  207. package/src/cli/release-cli.ts +63 -20
  208. package/src/cli/self-update.ts +14 -0
  209. package/src/cli/storyboard-cli.ts +37 -0
  210. package/src/cli/test-state-dir-flag.ts +11 -4
  211. package/src/connectors/linear.ts +146 -0
  212. package/src/context-bus/coord-events.ts +115 -0
  213. package/src/dashboard/dashboard-boot-fixture.ts +23 -0
  214. package/src/dashboard/index.ts +58 -21
  215. package/src/dashboard/slash-runtime/dashboard-handlers.ts +7 -0
  216. package/src/dashboard/slash-runtime/help-from-registry.ts +43 -28
  217. package/src/decisions/decision-cards.ts +49 -1
  218. package/src/decisions/decision-ledger.ts +75 -4
  219. package/src/decisions/decision-linear-projection.ts +157 -0
  220. package/src/decisions/web-push-decision-cards.ts +50 -0
  221. package/src/directive/interpretation-card.ts +36 -0
  222. package/src/discord.ts +2 -1
  223. package/src/domains/coo-admin-tool.ts +62 -0
  224. package/src/domains/core-tools.ts +11 -3
  225. package/src/domains/elanous-card.ts +29 -0
  226. package/src/domains/ops-facts-tool.ts +76 -0
  227. package/src/domains/outbound-alert.ts +41 -13
  228. package/src/domains/release-tool.ts +168 -0
  229. package/src/domains/telegram-kind-route.ts +19 -1
  230. package/src/exec-requests/planner.ts +98 -28
  231. package/src/exec-requests/runner.ts +107 -10
  232. package/src/exec-requests/store.ts +8 -2
  233. package/src/field/field-media.ts +10 -7
  234. package/src/field/field-reel.ts +14 -4
  235. package/src/git-fs/worktree.ts +45 -6
  236. package/src/graph-runner/graph-approval-card.ts +113 -0
  237. package/src/graph-runner/graph-cli.ts +129 -4
  238. package/src/graph-runner/graph-notify.ts +25 -3
  239. package/src/graph-runner/runner.ts +142 -16
  240. package/src/harness/browser-verify.ts +25 -2
  241. package/src/harness/harness-cli-command.ts +231 -35
  242. package/src/harness/harness-incidents-writer.fixture.ts +24 -0
  243. package/src/harness/harness-incidents.ts +129 -0
  244. package/src/harness/harness-stop.ts +53 -3
  245. package/src/hitl/clarification-policy.ts +3 -1
  246. package/src/index.ts +156 -37
  247. package/src/intake-plane/author-goals.ts +37 -4
  248. package/src/intake-plane/check.ts +3 -0
  249. package/src/intake-plane/discord-seat-work.ts +47 -2
  250. package/src/intake-plane/seat-doc-route.ts +170 -6
  251. package/src/intake-plane/telegram-seat-work.ts +44 -2
  252. package/src/intake-plane/telegram-work.ts +1 -0
  253. package/src/maturity/cli-maturity.ts +34 -0
  254. package/src/maturity/feature-maturity.ts +98 -1
  255. package/src/maturity/tui-slash-maturity.ts +31 -0
  256. package/src/memory.ts +32 -4
  257. package/src/nexus/api/exec-requests.ts +33 -3
  258. package/src/nexus/api/http-server.ts +53 -1
  259. package/src/nexus/api/meta-api.ts +11 -0
  260. package/src/nexus/api/ops-api.ts +10 -1
  261. package/src/nexus/api/ops-seats-sources.ts +37 -7
  262. package/src/nexus/api/ops-seats.ts +16 -6
  263. package/src/nexus/api/seat-requests.ts +88 -8
  264. package/src/nexus/api/sessions-store.ts +63 -0
  265. package/src/nexus/api/setup-codex-login.ts +160 -0
  266. package/src/nexus/api/usage-api.ts +102 -0
  267. package/src/nexus/index.ts +45 -4
  268. package/src/onboarding/first-run.ts +89 -0
  269. package/src/onboarding/lan-address.ts +45 -0
  270. package/src/onboarding/web-first.ts +119 -24
  271. package/src/onboarding.ts +4 -0
  272. package/src/ops-now/ops-now-refresh.ts +100 -0
  273. package/src/project/project-store.ts +115 -0
  274. package/src/release-loop/checklist.ts +78 -4
  275. package/src/release-loop/feature-store.ts +154 -21
  276. package/src/release-loop/release-schedule.ts +53 -0
  277. package/src/repl/index.ts +7 -0
  278. package/src/seat-dispatch/ceo-commands.ts +41 -8
  279. package/src/seat-dispatch/ceo-intent.ts +38 -0
  280. package/src/seat-loop/seat-loop.ts +231 -0
  281. package/src/seat-loop/seat-report.ts +64 -0
  282. package/src/self-dev/decompose.ts +66 -11
  283. package/src/self-dev/run-store.ts +10 -1
  284. package/src/self-implement/execution-clarification.ts +115 -0
  285. package/src/self-implement/gate-cli.ts +6 -3
  286. package/src/self-implement/orchestrator.ts +37 -3
  287. package/src/self-implement/pipeline-shape.ts +1 -1
  288. package/src/self-implement/run-status-mapping.ts +5 -3
  289. package/src/session/chat.ts +2 -0
  290. package/src/session/index.ts +9 -0
  291. package/src/shell-env-bootstrap.ts +17 -0
  292. package/src/steward/directive.ts +51 -2
  293. package/src/steward/launch.ts +250 -0
  294. package/src/steward/outcome-notify.ts +115 -0
  295. package/src/steward/steward-cards.ts +8 -2
  296. package/src/steward/triage-plan.ts +32 -4
  297. package/src/steward/triage.ts +93 -9
  298. package/src/steward/working-backwards.ts +64 -0
  299. package/src/storyboard/storyboard.ts +141 -0
  300. package/src/task-cards/card-store.ts +2 -2
  301. package/src/task-orchestrator/surfaces/pod-lease.ts +260 -0
  302. package/src/telegram-card-followup.ts +233 -0
  303. package/src/telegram-report.ts +15 -9
  304. package/src/telegram-run.ts +7 -0
  305. package/src/telegram.ts +29 -8
  306. package/src/tool-runtime/types.ts +4 -0
  307. package/src/tui-client/dashboard-session.ts +2 -0
  308. package/src/user-config.ts +118 -11
  309. package/src/version/packed-revision.json +1 -1
  310. package/src/web-push/notify-exec-request.ts +32 -0
  311. package/apps/pwa/out/_next/static/bFI5EEwg7T6aG88ibX4Zq/_buildManifest.js +0 -1
  312. package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
  313. package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
  314. package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
  315. package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +0 -1
  316. package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +0 -1
  317. package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
  318. package/apps/pwa/out/_next/static/chunks/7032-f4ef698518fba61c.js +0 -1
  319. package/apps/pwa/out/_next/static/chunks/8067-d976b441a104ab74.js +0 -1
  320. package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +0 -1
  321. package/apps/pwa/out/_next/static/chunks/8630-cdfc61e82c653ffc.js +0 -1
  322. package/apps/pwa/out/_next/static/chunks/8894-e263b332208e3957.js +0 -1
  323. package/apps/pwa/out/_next/static/chunks/9454-139040d37c79dbc9.js +0 -1
  324. package/apps/pwa/out/_next/static/chunks/9740-8f5ff96c522b5f55.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/app/approvals/page-fb7c2d458f32fcad.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-35e2713597dd24d1.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/board/page-918b656f064577ba.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/bots/page-5f30e00693527ebd.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/chat/page-7fbae173b3c103a7.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/consult/page-3b4edcce364062ea.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/control/page-c5794f484c251f44.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/design-check/page-1130d8eefcd87e76.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/exec/page-016e2bda04eecb1f.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/field/page-b4d6d9a3b56f2940.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/intake/page-ec49a466121233ba.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/app/layout-f72db356744ceade.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/app/live/page-a6fd9b50d1b85378.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/app/morning/page-adfbd2ba9598e59a.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/app/not-found-46fedff57d4bec67.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/app/observatory/page-0cf318e9dcee2bbf.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-899b327cb8b6845c.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-6c1e6ba743a29dfe.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/app/page-1a8e4807292f6b17.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/app/reflection/page-28deba6bcb834916.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-de68e2205794037d.js +0 -1
  346. package/apps/pwa/out/_next/static/chunks/app/sessions/page-c71c33a573230672.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/app/settings/page-4fc56790fe0d2682.js +0 -1
  348. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-59df2aebb38a7c1e.js +0 -1
  349. package/apps/pwa/out/_next/static/chunks/app/setup/page-4647be5dbcbd0795.js +0 -1
  350. package/apps/pwa/out/_next/static/chunks/app/share/page-e025892bda74b743.js +0 -1
  351. package/apps/pwa/out/_next/static/chunks/app/showroom/page-a0955de73b89f078.js +0 -1
  352. package/apps/pwa/out/_next/static/chunks/app/tasks/page-369846d2921af584.js +0 -1
  353. package/apps/pwa/out/_next/static/chunks/app/term/page-72cbc1c8cdd0f65d.js +0 -1
  354. package/apps/pwa/out/_next/static/chunks/app/trace/page-77b28bfb0996d5ea.js +0 -1
  355. package/apps/pwa/out/_next/static/chunks/app/vault/page-3220c4934f49c6c2.js +0 -1
  356. package/apps/pwa/out/_next/static/chunks/app/workflows/page-7df9243ed021a4cc.js +0 -1
  357. package/apps/pwa/out/_next/static/chunks/app/workspace/page-4498c3853f4f688d.js +0 -1
  358. package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +0 -1
  359. package/apps/pwa/out/_next/static/css/f92f63a5df0ba229.css +0 -1
  360. package/src/dashboard/turn-typeahead-echo.ts +0 -23
  361. /package/apps/pwa/out/_next/static/{bFI5EEwg7T6aG88ibX4Zq → 2DsiDfDCks44g1cPUMhiW}/_ssgManifest.js +0 -0
package/src/acp/server.ts CHANGED
@@ -101,6 +101,8 @@ import { readOriginSessionMeta } from './origin-session-meta.js';
101
101
  import type { CodexPlugin } from './codex-plugins.js';
102
102
  import { flattenMcpServersToCodexConfig } from './codex-approval-adapter.js';
103
103
  import { runAcpTurn } from './turn-runner.js';
104
+ import type { AcpPermissionApprovalRequest } from './client.js';
105
+ import { createAcpSessionApprovalPolicy, type AcpToolApprovalResult } from './tool-approval.js';
104
106
  import { projectPwaToolResult, type ToolResultMeta } from '../nexus/api/meta-api.js';
105
107
  import type { LlmBrand } from '../llm-vision-capability.js';
106
108
  import {
@@ -233,6 +235,9 @@ export interface AcpServerOptions {
233
235
  * receives the user message text and a push() for streaming back
234
236
  * assistant chunks. When omitted, the server echoes the prompt. */
235
237
  runTurn?: (ctx: AcpTurnContext) => Promise<void>;
238
+ /** PCH-2b — ask the client before write/shell tools run ('ask', default).
239
+ * 'off' keeps the host's own approvals (the in-process TUI dashboard). */
240
+ toolApproval?: 'ask' | 'off';
236
241
  /** ACP standalone entry: require session cwd or an explicit boot default. */
237
242
  requireSessionToolCwd?: boolean;
238
243
  bootToolCwd?: string;
@@ -850,6 +855,8 @@ export interface AcpTurnContext {
850
855
  toolArgs?: Record<string, unknown>;
851
856
  timeoutMs?: number;
852
857
  }) => Promise<AcpApprovalDecision>;
858
+ /** The session-scoped gate used by core-turn tool dispatchers. */
859
+ approveTool?: (req: Parameters<AcpTurnContext['requestApproval']>[0]) => Promise<AcpToolApprovalResult>;
853
860
  }
854
861
 
855
862
  /** F1 — Phase 3 · resolved decision from `AcpTurnContext.requestApproval`.
@@ -866,6 +873,14 @@ export type AcpApprovalDecision =
866
873
  * `_PERMISSION_DEFAULT_TIMEOUT_S`; overrideable per call. */
867
874
  export const DEFAULT_APPROVAL_TIMEOUT_MS = 60_000;
868
875
 
876
+ export {
877
+ acpToolDenialResult,
878
+ createAcpSessionApprovalPolicy,
879
+ requiresAcpToolApproval,
880
+ type AcpToolApprovalResult,
881
+ type AcpToolDenialReason,
882
+ } from './tool-approval.js';
883
+
869
884
  /** ACP streaming Phase E (PLAN-pwa-webterm-voice-control v1.2 · 2026-05-07) —
870
885
  * module-level handle to the broadcaster owned by the active runAcpServer
871
886
  * instance. The chat REST handler (`src/nexus/api/meta-api.ts handlePromptStreamPost`)
@@ -991,6 +1006,7 @@ interface AcpServerContext {
991
1006
  /** One skip diagnostic per session, cleared when its last peer leaves. */
992
1007
  plainEnvelopeLogged: Set<string>;
993
1008
  dualRole: DualRoleManager;
1009
+ approvalPolicy: ReturnType<typeof createAcpSessionApprovalPolicy>;
994
1010
  /** Mints the unique suffix of a new session id. Injected so tests can
995
1011
  * pin it deterministically — production wires `mintAcpSessionToken`. */
996
1012
  nextSessionToken: () => string;
@@ -1054,7 +1070,7 @@ function wireAcpConnection(
1054
1070
  stream: ReturnType<typeof ndJsonStream>,
1055
1071
  ctx: AcpServerContext,
1056
1072
  ): AgentSideConnection {
1057
- const { sessions, sessionPeers, awarePeers, plainEnvelopeLogged, dualRole, opts } = ctx;
1073
+ const { sessions, sessionPeers, awarePeers, plainEnvelopeLogged, dualRole, approvalPolicy, opts } = ctx;
1058
1074
  // 2026-05-13 (M2 of AskUserQuestion cross-surface) — bound connection
1059
1075
  // widened to expose `extMethod` + `extNotification` (SDK's elanous
1060
1076
  // extension escape hatch). pushAskRequest / pushAskCancel route through
@@ -1771,7 +1787,9 @@ function wireAcpConnection(
1771
1787
  );
1772
1788
  };
1773
1789
  const requestApproval: AcpTurnContext['requestApproval'] = async (approvalReq) => {
1774
- const timeoutMs = approvalReq.timeoutMs ?? DEFAULT_APPROVAL_TIMEOUT_MS;
1790
+ const timeoutMs = approvalReq.timeoutMs
1791
+ ?? (await import('../user-config.js')).getUserConfig().acp.toolApproval?.timeoutMs
1792
+ ?? DEFAULT_APPROVAL_TIMEOUT_MS;
1775
1793
  const optionBase = `${approvalReq.toolCallId}-`;
1776
1794
  const options = [
1777
1795
  { optionId: `${optionBase}allow-once`, name: 'Allow once', kind: 'allow_once' as const },
@@ -1787,6 +1805,8 @@ function wireAcpConnection(
1787
1805
  },
1788
1806
  options,
1789
1807
  });
1808
+ // A late rejection after the timeout won the race must not go unhandled.
1809
+ permissionPromise.catch(() => undefined);
1790
1810
  let timer: ReturnType<typeof setTimeout> | null = null;
1791
1811
  const timeoutPromise = new Promise<'timeout'>((resolve) => {
1792
1812
  timer = setTimeout(() => resolve('timeout'), timeoutMs);
@@ -1972,6 +1992,7 @@ function wireAcpConnection(
1972
1992
  }
1973
1993
  }
1974
1994
 
1995
+ const rejectedThisTurn = new Set<string>();
1975
1996
  const turnCtx: AcpTurnContext = {
1976
1997
  sessionId: req.sessionId,
1977
1998
  cwd: s.cwd,
@@ -1987,6 +2008,10 @@ function wireAcpConnection(
1987
2008
  pushSessionUpdate,
1988
2009
  pushUsage,
1989
2010
  requestApproval,
2011
+ ...(opts.toolApproval !== 'off'
2012
+ ? { approveTool: (approvalReq: Parameters<AcpTurnContext['requestApproval']>[0]) =>
2013
+ approvalPolicy.resolve(req.sessionId, approvalReq, requestApproval, rejectedThisTurn) }
2014
+ : {}),
1990
2015
  };
1991
2016
 
1992
2017
  // RC — intercept hook runs first. Lets the host treat the
@@ -2020,6 +2045,20 @@ function wireAcpConnection(
2020
2045
  chatId: req.sessionId,
2021
2046
  cwd: s.cwd,
2022
2047
  codexArgs: s.codexArgs,
2048
+ ...(turnCtx.approveTool ? {
2049
+ // PCH-2b — codex asks this turn's ACP client instead of auto-approving.
2050
+ permissionApprover: async (approvalReq: AcpPermissionApprovalRequest) => {
2051
+ if (s.aborted) return false;
2052
+ const decision = await turnCtx.approveTool!({
2053
+ toolCallId: `codex-${randomBytes(6).toString('hex')}`,
2054
+ // «codex exec» · «codex network» · «codex mcp» — allow-always is per kind.
2055
+ toolName: /^codex [a-z]+/.exec(approvalReq.title)?.[0] ?? `codex ${approvalReq.kind ?? 'action'}`,
2056
+ ...(approvalReq.rawInput && typeof approvalReq.rawInput === 'object' && !Array.isArray(approvalReq.rawInput)
2057
+ ? { toolArgs: approvalReq.rawInput as Record<string, unknown> } : {}),
2058
+ });
2059
+ return decision.allowed && !s.aborted;
2060
+ },
2061
+ } : {}),
2023
2062
  streamer: { edit: (text) => { void push(text); } },
2024
2063
  });
2025
2064
  } else if (opts.runTurn) {
@@ -2169,8 +2208,8 @@ function wireAcpConnection(
2169
2208
  const adopted = matches.find((match) => match.pt.isAlive);
2170
2209
  const existing = sameSession?.isAlive ? sameSession : adopted?.pt;
2171
2210
  if (existing) {
2211
+ registerPreviewTerminalForWebTap(existing, p.sessionId, tid, handle);
2172
2212
  if (existing !== sameSession && adopted) {
2173
- registerPreviewTerminalForWebTap(existing, p.sessionId, tid, handle);
2174
2213
  debug.log('webterm.acp', 'spawn.adopt', {
2175
2214
  terminalId: tid, fromSessions: adopted.sessionIds, toSession: p.sessionId,
2176
2215
  });
@@ -2207,10 +2246,10 @@ function wireAcpConnection(
2207
2246
  // appear duplicated. Pinning TERM to xterm-256color matches
2208
2247
  // the actual rendering surface and makes the redraws idempotent.
2209
2248
  termName: 'xterm-256color',
2210
- // ★ P0b-2 (실행 substrate 통합·기본 off) — 이 진짜 PWA/iOS 라이브 셸을 공유 registry 버스로
2211
- // 흡수(정체성·크로스서피스 goto·3-스택 통합). 라이브 렌더(yazi/마우스/커서/alt-screen) 검증이
2212
- // 기기 왕복을 요하므로 per-run env 로만 켠다(대표가 별도 부팅해 수습). 미설정=기존 dup-fd(무회귀).
2213
- useRegistry: process.env.ELANOUS_PREVIEW_TERMINAL_REGISTRY === '1',
2249
+ // The shared PTY bus delivers output to PreviewTerminal's headless
2250
+ // renderer and ACP taps. The direct /dev/fd duplicate can observe
2251
+ // shell exit without ever reading its prompt (black web terminal).
2252
+ useRegistry: true,
2214
2253
  ...(typeof p.shell === 'string' && p.shell.length > 0 ? { shell: p.shell } : {}),
2215
2254
  onExit: () => {
2216
2255
  void import('../web-terminal/preview-tap-registry.js')
@@ -3343,6 +3382,7 @@ function wireAcpConnection(
3343
3382
  if (remaining === 0) {
3344
3383
  sessionPeers.delete(sid);
3345
3384
  plainEnvelopeLogged.delete(sid);
3385
+ approvalPolicy.clearSession(sid);
3346
3386
  }
3347
3387
  if (debug.enabled) {
3348
3388
  debug.log('acp.peer.unregister', sid, { remainingPeers: remaining });
@@ -3487,6 +3527,7 @@ export async function runAcpServer(opts: AcpServerOptions = {}): Promise<void> {
3487
3527
  awarePeers,
3488
3528
  plainEnvelopeLogged,
3489
3529
  dualRole,
3530
+ approvalPolicy: createAcpSessionApprovalPolicy(),
3490
3531
  nextSessionToken: mintAcpSessionToken,
3491
3532
  agentName: opts.agentName ?? 'elanous-agent',
3492
3533
  agentVersion: opts.agentVersion ?? readPkgVersion(),
@@ -0,0 +1,126 @@
1
+ // PCH-2b (0.2.10 · security D-20261002-08) — ACP turns ask the client before
2
+ // write/shell tools run; «allow always» is remembered per session and tool.
3
+ // Goal: 내부 문서 `ASK-pwa-chat-turns-run-write-and-shell-tools-without-approval-2026-10-02`
4
+ import { debug } from '../debug/log.js';
5
+ import type { AcpApprovalDecision, AcpTurnContext } from './server.js';
6
+
7
+ /** PCH-2b — tools that write files or run shell commands. ACP turns (PWA·iOS·
8
+ * remote TUI) ask the client before these run; everything else runs as before.
9
+ * One list, here, because the daemon catalog has no read-only/mutating flag. */
10
+ const ACP_APPROVAL_TOOLS = new Set([
11
+ 'write', 'edit', 'multiedit', 'notebookedit', 'applypatch', 'apply_patch', 'patch',
12
+ 'bash', 'shell', 'runshell', 'exec_command',
13
+ 'ptyshellstart', 'ptyshellsend', 'webterminalinput',
14
+ 'delegate_code_agent',
15
+ ]);
16
+ export function requiresAcpToolApproval(toolName: string): boolean {
17
+ const name = toolName.startsWith('functions.') ? toolName.slice('functions.'.length) : toolName;
18
+ // Codex app-server only sends a permission request when it wants to act.
19
+ return ACP_APPROVAL_TOOLS.has(name.toLowerCase()) || name.startsWith('codex ');
20
+ }
21
+
22
+ export type AcpToolDenialReason = 'rejected' | 'cancelled' | 'timeout' | 'error';
23
+ export type AcpToolApprovalResult =
24
+ | { allowed: true }
25
+ | { allowed: false; reason: AcpToolDenialReason };
26
+
27
+ /** The tool result the LLM sees when a protected tool was not run. */
28
+ export function acpToolDenialResult(toolName: string, reason: AcpToolDenialReason): { error: string } {
29
+ if (reason === 'rejected') {
30
+ return { error: `${toolName}: 사람이 이 호출을 거절했다. 같은 턴에서 같은 도구를 다시 요청하지 말고, 이유를 묻거나 다른 길을 제안하라 — 실행하지 않았다 (tool not run: approval ${reason})` };
31
+ }
32
+ if (reason === 'timeout') {
33
+ return { error: `${toolName}: 사람의 답이 없었다(시한 초과) — 실행하지 않았다. 사용자에게 한 줄로 알려라 (tool not run: approval ${reason})` };
34
+ }
35
+ const why = reason === 'cancelled' ? '승인 요청이 취소됐다' : '승인을 받지 못했다';
36
+ return { error: `${toolName}: ${why} — 실행하지 않았다 (tool not run: approval ${reason})` };
37
+ }
38
+
39
+ /** One policy per ACP server: grants are scoped to the session AND exact tool
40
+ * name, not to a turn, connection, argument set, or other session. */
41
+ export function createAcpSessionApprovalPolicy(): {
42
+ resolve: (
43
+ sessionId: string,
44
+ request: Parameters<AcpTurnContext['requestApproval']>[0],
45
+ requestApproval: AcpTurnContext['requestApproval'],
46
+ rejectedThisTurn?: Set<string>,
47
+ ) => Promise<AcpToolApprovalResult>;
48
+ clearSession: (sessionId: string) => void;
49
+ } {
50
+ const alwaysAllowed = new Map<string, Set<string>>();
51
+ const sessionGenerations = new Map<string, number>();
52
+ const observe = (event: 'requested' | 'decided' | 'timeout' | 'remembered', sessionId: string, tool: string, outcome?: string) => {
53
+ // Tool arguments are deliberately not logged.
54
+ debug.log('acp.approval', event, { sessionId, tool, ...(outcome !== undefined ? { outcome } : {}) });
55
+ };
56
+ // Same-turn calls to one tool are asked one at a time, so a concurrent second call sees the first's denial
57
+ // instead of opening a second sheet (round 1 should-fix).
58
+ const turnInflight = new WeakMap<Set<string>, Map<string, Promise<void>>>();
59
+ const decide = async (
60
+ sessionId: string,
61
+ request: Parameters<AcpTurnContext['requestApproval']>[0],
62
+ requestApproval: AcpTurnContext['requestApproval'],
63
+ rejectedThisTurn?: Set<string>,
64
+ ): Promise<AcpToolApprovalResult> => {
65
+ if (rejectedThisTurn?.has(request.toolName)) {
66
+ debug.log('acp.approval', 'repeat-denied', { sessionId, tool: request.toolName });
67
+ return { allowed: false, reason: 'rejected' };
68
+ }
69
+ if (alwaysAllowed.get(sessionId)?.has(request.toolName)) {
70
+ observe('remembered', sessionId, request.toolName, 'allow-always');
71
+ return { allowed: true };
72
+ }
73
+ const generation = sessionGenerations.get(sessionId) ?? 0;
74
+ observe('requested', sessionId, request.toolName);
75
+ let decision: AcpApprovalDecision;
76
+ try {
77
+ decision = await requestApproval(request);
78
+ } catch {
79
+ observe('decided', sessionId, request.toolName, 'error');
80
+ return { allowed: false, reason: 'error' };
81
+ }
82
+ observe(decision === 'timeout' ? 'timeout' : 'decided', sessionId, request.toolName, decision);
83
+ if (generation !== (sessionGenerations.get(sessionId) ?? 0)) return { allowed: false, reason: 'cancelled' };
84
+ if (decision === 'allow-always') {
85
+ let tools = alwaysAllowed.get(sessionId);
86
+ if (!tools) {
87
+ tools = new Set();
88
+ alwaysAllowed.set(sessionId, tools);
89
+ }
90
+ tools.add(request.toolName);
91
+ }
92
+ if (decision === 'allow-once' || decision === 'allow-always') return { allowed: true };
93
+ if (decision === 'deny-once' || decision === 'deny-always') rejectedThisTurn?.add(request.toolName);
94
+ return {
95
+ allowed: false,
96
+ reason: decision === 'timeout' ? 'timeout' : decision === 'cancelled' ? 'cancelled' : 'rejected',
97
+ };
98
+ };
99
+ return {
100
+ async resolve(sessionId, request, requestApproval, rejectedThisTurn) {
101
+ if (!requiresAcpToolApproval(request.toolName)) return { allowed: true };
102
+ if (!rejectedThisTurn) return decide(sessionId, request, requestApproval);
103
+ let inflight = turnInflight.get(rejectedThisTurn);
104
+ if (!inflight) {
105
+ inflight = new Map();
106
+ turnInflight.set(rejectedThisTurn, inflight);
107
+ }
108
+ const prior = inflight.get(request.toolName);
109
+ let release!: () => void;
110
+ const mine = new Promise<void>((done) => { release = done; });
111
+ const chained = (prior ?? Promise.resolve()).then(() => mine);
112
+ inflight.set(request.toolName, chained);
113
+ try {
114
+ if (prior) await prior;
115
+ return await decide(sessionId, request, requestApproval, rejectedThisTurn);
116
+ } finally {
117
+ release();
118
+ if (inflight.get(request.toolName) === chained) inflight.delete(request.toolName);
119
+ }
120
+ },
121
+ clearSession(sessionId) {
122
+ alwaysAllowed.delete(sessionId);
123
+ sessionGenerations.set(sessionId, (sessionGenerations.get(sessionId) ?? 0) + 1);
124
+ },
125
+ };
126
+ }
@@ -40,6 +40,11 @@ import { unsafeBrandSessionUri } from '../mss/uri/brand.js';
40
40
  import { debug } from '../debug/log.js';
41
41
  import type { AgentStatusStore } from '../agent-status/store.js';
42
42
  import type { AcpPermissionApprover, AcpQuestionApprover } from './client.js';
43
+ import {
44
+ CODEX_APPROVAL_OPT_ONCE,
45
+ CODEX_APPROVAL_OPT_REJECT,
46
+ CODEX_APPROVAL_QUESTION_ID,
47
+ } from './codex-approval-adapter.js';
43
48
  import type { ConfirmChannel } from '../hitl/confirm.js';
44
49
  import type { QuestionChannel } from '../hitl/question.js';
45
50
  import {
@@ -69,6 +74,7 @@ import { getUserConfig } from '../user-config.js';
69
74
  // ACP `cancelled`), matching the pre-existing default-deny posture for
70
75
  // unattended turns — so nothing hangs when a surface opts out.
71
76
  const turnHitlChannels = new Map<string, ConfirmChannel[]>();
77
+ const turnPermissionApprovers = new Map<string, AcpPermissionApprover>();
72
78
  /** Parallel registry for multi-option question channels (same keying
73
79
  * as `turnHitlChannels`). Present ⇒ the question approver fans out
74
80
  * real N-option prompts; absent ⇒ it falls back to yes/no collapse
@@ -81,6 +87,8 @@ const turnHitlQuestionChannels = new Map<string, QuestionChannel[]>();
81
87
  const HITL_TURN_TIMEOUT_MS = 120_000;
82
88
 
83
89
  const surfacePermissionApprover: AcpPermissionApprover = async (req) => {
90
+ const turnApprover = turnPermissionApprovers.get(req.sessionId);
91
+ if (turnApprover) return turnApprover(req);
84
92
  // Autonomous by default: edit/command PERMISSIONS auto-approve. Asking the
85
93
  // human to approve every write defeats delegation (the interactive-editor
86
94
  // paradigm ACP inherited doesn't fit autonomous telegram delegation).
@@ -102,7 +110,31 @@ const surfacePermissionApprover: AcpPermissionApprover = async (req) => {
102
110
  return answer;
103
111
  };
104
112
 
113
+ /** PCH-2b — answer codex's 3-way approval question with a yes/no permission
114
+ * approver. Returns null when the request is not that question. */
115
+ export async function answerCodexApprovalQuestion(
116
+ req: Parameters<AcpQuestionApprover>[0],
117
+ approver: AcpPermissionApprover,
118
+ ): Promise<Awaited<ReturnType<AcpQuestionApprover>> | null> {
119
+ const q = req.questions[0];
120
+ if (req.questions.length !== 1 || q?.id !== CODEX_APPROVAL_QUESTION_ID) return null;
121
+ const approved = await approver({
122
+ backendId: req.backendId,
123
+ sessionId: req.sessionId,
124
+ title: q.question,
125
+ options: [],
126
+ });
127
+ return { answers: { [q.id]: approved ? CODEX_APPROVAL_OPT_ONCE : CODEX_APPROVAL_OPT_REJECT } };
128
+ }
129
+
105
130
  const surfaceQuestionApprover: AcpQuestionApprover = async (req) => {
131
+ // PCH-2b — an ACP server turn answers codex's approval question through its
132
+ // own client (yes/no), not the messenger HITL channels.
133
+ const turnApprover = turnPermissionApprovers.get(req.sessionId);
134
+ if (turnApprover) {
135
+ const answered = await answerCodexApprovalQuestion(req, turnApprover);
136
+ if (answered) return answered;
137
+ }
106
138
  const channels = turnHitlChannels.get(req.sessionId);
107
139
  if (!channels || channels.length === 0) return { answers: {}, cancelled: true };
108
140
  // When the surface provided real multi-option QuestionChannels, fan
@@ -306,6 +338,8 @@ export interface RunAcpTurnOpts {
306
338
  cwd?: string;
307
339
  /** Session-scoped Codex app-server configuration arguments. */
308
340
  codexArgs?: readonly string[];
341
+ /** ACP server turn's permission request, routed through its client rather than the messenger HITL channel. */
342
+ permissionApprover?: AcpPermissionApprover;
309
343
  /** Soft focus budget (tool-turns) for a TARGETED slash invocation
310
344
  * (`/cc`·/cdx·/gem). When set, a tight-budget directive is prepended
311
345
  * to the prompt so the sub-agent stays focused (minimize re-reads /
@@ -663,6 +697,7 @@ export async function runAcpTurn(opts: RunAcpTurnOpts): Promise<RunAcpTurnResult
663
697
  // session id (== the `req.sessionId` seen in requestPermission).
664
698
  const hasHitl = !!opts.hitlConfirmChannels && opts.hitlConfirmChannels.length > 0;
665
699
  if (hasHitl) turnHitlChannels.set(sessionId, opts.hitlConfirmChannels!);
700
+ if (opts.permissionApprover) turnPermissionApprovers.set(sessionId, opts.permissionApprover);
666
701
  const hasHitlQuestions = !!opts.hitlQuestionChannels && opts.hitlQuestionChannels.length > 0;
667
702
  if (hasHitlQuestions) turnHitlQuestionChannels.set(sessionId, opts.hitlQuestionChannels!);
668
703
 
@@ -784,6 +819,7 @@ export async function runAcpTurn(opts: RunAcpTurnOpts): Promise<RunAcpTurnResult
784
819
  } finally {
785
820
  inFlightTurns.delete(key);
786
821
  if (hasHitl) turnHitlChannels.delete(sessionId);
822
+ if (opts.permissionApprover) turnPermissionApprovers.delete(sessionId);
787
823
  if (hasHitlQuestions) turnHitlQuestionChannels.delete(sessionId);
788
824
  }
789
825
  }
@@ -26,6 +26,7 @@ import { buildAutonomousToolSpecs, dispatchAutonomousTool, isAutonomousTool, del
26
26
  import { isToolSearchCall, routeToolSearch } from '../skills/tools/tool-search-route.js';
27
27
  import { financeEnabled, financeAgentSystemPrompt, marketClock } from '../domains/finance.js';
28
28
  import { buildFinanceTools } from '../domains/finance-tools.js';
29
+ import { dispatchReleaseChange } from '../domains/release-tool.js';
29
30
  import { surfaceEventsDbPath, openSurfaceEventsDb, recentSentDigest, recordInboundTurn } from '../domains/surface-events.js';
30
31
  import { elanousSelfAccessPrompt, elanousSelfAmbientParts } from './self-ambient.js';
31
32
  import { localRefGroundingAmbient } from './ref-grounding.js';
@@ -188,6 +189,13 @@ export function makeElanousAgentRunTurn(cfg: UserConfig, surface: SessionSource,
188
189
  },
189
190
  });
190
191
  }
192
+ if (name === 'release_change') {
193
+ return dispatchReleaseChange(args, {
194
+ surface: 'chat', sessionId: opts.sessionId,
195
+ ...(opts.verifiedOwner ? { verifiedOwner: opts.verifiedOwner } : {}),
196
+ ...(surfaceChannels ? { confirmChannels: surfaceChannels } : {}),
197
+ });
198
+ }
191
199
  // Thread the turn's abort signal so a long in-flight Bash is killed by
192
200
  // `/cancel` (dispatchBash SIGTERMs its child on abort).
193
201
  return baseDispatch(name, args, opts.signal);
@@ -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
  }
@@ -74,15 +74,26 @@ function writeStore(dir: string, tokens: SetupLinkEntry[]): void {
74
74
 
75
75
  // A persistent inode and an OS advisory lock avoid stale owner files after an abrupt process exit.
76
76
  // Never unlink this file: a replacement inode would let two processes hold different locks.
77
- const libc = dlopen(process.platform === 'darwin' ? '/usr/lib/libSystem.B.dylib' : 'libc.so.6', {
78
- flock: { args: [FFIType.i32, FFIType.i32], returns: FFIType.i32 },
79
- });
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
+ }
80
88
  const LOCK_EX = 2;
81
89
  const LOCK_NB = 4;
82
90
  const LOCK_UN = 8;
83
91
 
84
92
  function withLock<T>(dir: string, fn: () => T): T {
85
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();
86
97
  const lock = `${storePath(dir)}.lock`;
87
98
  const fd = openSync(lock, constants.O_CREAT | constants.O_RDWR | constants.O_NOFOLLOW, 0o600);
88
99
  try {
@@ -92,7 +103,7 @@ function withLock<T>(dir: string, fn: () => T): T {
92
103
  throw new Error('setup link lock must be a private regular file (0600)');
93
104
  }
94
105
  const until = Date.now() + 3000;
95
- while (libc.symbols.flock(fd, LOCK_EX | LOCK_NB) !== 0) {
106
+ while (flock(fd, LOCK_EX | LOCK_NB) !== 0) {
96
107
  if (Date.now() > until) throw new Error('setup link store is locked — try again');
97
108
  Bun.sleepSync(20);
98
109
  }
@@ -101,7 +112,7 @@ function withLock<T>(dir: string, fn: () => T): T {
101
112
  if (stat.ino !== locked.ino || stat.dev !== locked.dev) throw new Error('setup link lock changed');
102
113
  return fn();
103
114
  } finally {
104
- libc.symbols.flock(fd, LOCK_UN);
115
+ flock(fd, LOCK_UN);
105
116
  }
106
117
  } finally {
107
118
  closeSync(fd);
@@ -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: {