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.
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/2DsiDfDCks44g1cPUMhiW/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/24-e6d28380efab7faf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3615-740dd270764ffa24.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3734-1015fe991eb9b7e7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5012-a7fe6f3c8b6fe45a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5783-ef40ebfc91d55230.js +1 -0
- package/apps/pwa/out/_next/static/chunks/62-ff227c3edb8b248b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7633-1b3709b0854c9412.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7951-8b03dbb10b24cd06.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8067-87ce253ec2f23697.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 8195-6fde3d7a39859206.js} +5 -5
- package/apps/pwa/out/_next/static/chunks/8894-b5fb36bab837cd29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9454-84d0d5d77446bd92.js +1 -0
- package/apps/pwa/out/_next/static/chunks/988-5f92e7ab7ccea3dd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9989-5582582f06429840.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-d2253d7e58ef1890.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-5441d4a9ab35e518.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-d9b12d680922d980.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-f972fc1c00a62e88.js → page-54b32bd27726588d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-e268d6d145827a22.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-f8116559c7424da7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-1085399fd0f32112.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-1df686a14f65895a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-54e1f966ab60a4dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-973996d584f008c1.js → page-7ed95f296f5ee8e9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-11edac668d77e5c3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-59e2b9a22cd5a6d5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-2976df21ad74ab48.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-34d211a67f16d510.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-8ff652d5d950639b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/{page-8b0402a46a749a3d.js → page-b31610deda76337b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-037f83e2a1edc8e2.js → page-6cb0e1bbefe5fb3d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-a286a152cf584576.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-d9237581e7003bcb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-83274e761643fdd2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-ceee6cfb1d4e6d68.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-9c9f8bb3e4a64091.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c17217603ea3426a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-efb6177cbf1e8a51.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-d307523703bc4df3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-462f4e4bab45ce7d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-8990e96f33af3837.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-0df30e0c7e50148a.js → page-e168fa1030af0548.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-1233e05a4068bc9c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-13a1840b1fac337a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-64a29fc80b11ab55.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-68e787339a16b3dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-4fddc90b23feb215.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-886ba8260dc79c79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-0f26507a1722e40b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-109d4ccd42259c9d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-f5c2a4e510726774.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-b2e0039416f42522.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-90670589b87abea8.js → page-26102fd7350f171e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-708948bc8a802794.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-5e5611620f78fb0b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-35bedde6d885f08f.js → page-139ba5c367bbf60a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/main-app-202b4e81f33895ea.js +1 -0
- package/apps/pwa/out/_next/static/css/43feca5fd387dcbd.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +14 -14
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +14 -14
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +14 -14
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +14 -14
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +14 -14
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +14 -14
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +14 -14
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +14 -14
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +14 -14
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +14 -14
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +14 -14
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +14 -14
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +14 -14
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +14 -14
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +14 -14
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +14 -14
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +14 -14
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +14 -14
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +14 -14
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +14 -14
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +14 -14
- package/apps/pwa/out/ops/seats/index.html +1 -0
- package/apps/pwa/out/ops/seats/index.txt +30 -0
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +15 -15
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +14 -14
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +14 -14
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +14 -14
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +14 -14
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +14 -14
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +14 -14
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +14 -14
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +14 -14
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +14 -14
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +14 -14
- package/apps/pwa/out/today/index.html +1 -0
- package/apps/pwa/out/today/index.txt +33 -0
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +14 -14
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +14 -14
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +14 -14
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +14 -14
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +14 -14
- package/graphs/field/field-feed.yaml +2 -0
- package/graphs/implement-loop.yaml +1 -1
- package/package.json +1 -1
- package/plugins/card-followup/graphs/run-step.ts +48 -13
- package/plugins/doc-draft/graphs/doc-draft.yaml +3 -3
- package/plugins/doc-draft/graphs/run-step.ts +13 -6
- package/scripts/ci-pwa-build-gate.ts +160 -0
- package/scripts/coord-channel-watch.sh +42 -8
- package/scripts/coord-digest.ts +199 -0
- package/scripts/coord-post.sh +60 -3
- package/scripts/install.ps1 +68 -9
- package/scripts/install.sh +39 -12
- package/scripts/lib/pwa-scenarios/scenarios.ts +20 -0
- package/scripts/lib/tui-regress-checks.ts +146 -0
- package/scripts/memory-lifecycle-cycle.ts +2 -2
- package/scripts/ops-now-probe.ts +327 -0
- package/scripts/publish-internal-dist.ts +11 -1
- package/scripts/release-loop/cutoff.ts +19 -8
- package/scripts/release-loop/ops-upgrade-node.ts +72 -16
- package/scripts/release-loop/release-readiness.ts +9 -0
- package/scripts/release-loop/version-node.ts +58 -10
- package/scripts/repo-watch-cycle.ts +1 -1
- package/scripts/retro-cycle.ts +2 -2
- package/scripts/schedule-health-report.ts +1 -1
- package/scripts/storyboard/storyboard.ts +5 -161
- package/scripts/test-diet/lib.ts +30 -1
- package/scripts/test-diet/node.ts +82 -7
- package/scripts/test-timeout-audit.ts +114 -0
- package/scripts/tree-sync-alert.ts +1 -1
- package/scripts/tui-regress.ts +71 -0
- package/scripts/waitlist-daily-digest.ts +1 -1
- package/skills/explainer-video/SKILL.md +4 -0
- package/skills/explainer-video/engine/reel.mjs +18 -13
- package/skills/explainer-video/engine/reel.sh +24 -5
- package/skills/explainer-video/engine/stitch.mjs +145 -0
- package/skills/pitch-deck/SKILL.md +36 -0
- package/skills/pitch-deck/template.html +80 -0
- package/src/acp/codex-approval-adapter.ts +9 -3
- package/src/acp/core-turn-bridge.ts +117 -18
- package/src/acp/fact-question.ts +11 -0
- package/src/acp/server.ts +48 -7
- package/src/acp/tool-approval.ts +126 -0
- package/src/acp/turn-runner.ts +36 -0
- package/src/agent/monad-agent-turn.ts +8 -0
- package/src/ask-user-question/pending-questions.ts +25 -10
- package/src/ask-user-question/types.ts +9 -0
- package/src/auth/setup-link-tokens.ts +16 -5
- package/src/autopilot/build/integrity-gate.ts +12 -0
- package/src/boot/daemon-prompt-turn.ts +3 -0
- package/src/boot/daemon-tools/index.ts +8 -0
- package/src/boot/daemon-tools/types.ts +2 -0
- package/src/boot/daemon-tools/web-search.ts +1 -0
- package/src/chat/ctrl-c-in-input.ts +14 -0
- package/src/chat/index.ts +52 -0
- package/src/chat/pickers/state.ts +6 -2
- package/src/cli/agent-cli.ts +7 -0
- package/src/cli/decisions-cli.ts +25 -3
- package/src/cli/doctor-bundle-upload.ts +68 -0
- package/src/cli/doctor-cli.ts +38 -4
- package/src/cli/ops-cli.ts +20 -0
- package/src/cli/pod-cli.ts +49 -0
- package/src/cli/pr-cli.ts +31 -2
- package/src/cli/pwa-stop.ts +163 -13
- package/src/cli/pwa-test.ts +3 -0
- package/src/cli/release-cli.ts +63 -20
- package/src/cli/self-update.ts +14 -0
- package/src/cli/storyboard-cli.ts +37 -0
- package/src/cli/test-state-dir-flag.ts +11 -4
- package/src/connectors/linear.ts +146 -0
- package/src/context-bus/coord-events.ts +115 -0
- package/src/dashboard/dashboard-boot-fixture.ts +23 -0
- package/src/dashboard/index.ts +58 -21
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +7 -0
- package/src/dashboard/slash-runtime/help-from-registry.ts +43 -28
- package/src/decisions/decision-cards.ts +49 -1
- package/src/decisions/decision-ledger.ts +75 -4
- package/src/decisions/decision-linear-projection.ts +157 -0
- package/src/decisions/web-push-decision-cards.ts +50 -0
- package/src/directive/interpretation-card.ts +36 -0
- package/src/discord.ts +2 -1
- package/src/domains/coo-admin-tool.ts +62 -0
- package/src/domains/core-tools.ts +11 -3
- package/src/domains/elanous-card.ts +29 -0
- package/src/domains/ops-facts-tool.ts +76 -0
- package/src/domains/outbound-alert.ts +41 -13
- package/src/domains/release-tool.ts +168 -0
- package/src/domains/telegram-kind-route.ts +19 -1
- package/src/exec-requests/planner.ts +98 -28
- package/src/exec-requests/runner.ts +107 -10
- package/src/exec-requests/store.ts +8 -2
- package/src/field/field-media.ts +10 -7
- package/src/field/field-reel.ts +14 -4
- package/src/git-fs/worktree.ts +45 -6
- package/src/graph-runner/graph-approval-card.ts +113 -0
- package/src/graph-runner/graph-cli.ts +129 -4
- package/src/graph-runner/graph-notify.ts +25 -3
- package/src/graph-runner/runner.ts +142 -16
- package/src/harness/browser-verify.ts +25 -2
- package/src/harness/harness-cli-command.ts +231 -35
- package/src/harness/harness-incidents-writer.fixture.ts +24 -0
- package/src/harness/harness-incidents.ts +129 -0
- package/src/harness/harness-stop.ts +53 -3
- package/src/hitl/clarification-policy.ts +3 -1
- package/src/index.ts +156 -37
- package/src/intake-plane/author-goals.ts +37 -4
- package/src/intake-plane/check.ts +3 -0
- package/src/intake-plane/discord-seat-work.ts +47 -2
- package/src/intake-plane/seat-doc-route.ts +170 -6
- package/src/intake-plane/telegram-seat-work.ts +44 -2
- package/src/intake-plane/telegram-work.ts +1 -0
- package/src/maturity/cli-maturity.ts +34 -0
- package/src/maturity/feature-maturity.ts +98 -1
- package/src/maturity/tui-slash-maturity.ts +31 -0
- package/src/memory.ts +32 -4
- package/src/nexus/api/exec-requests.ts +33 -3
- package/src/nexus/api/http-server.ts +53 -1
- package/src/nexus/api/meta-api.ts +11 -0
- package/src/nexus/api/ops-api.ts +10 -1
- package/src/nexus/api/ops-seats-sources.ts +37 -7
- package/src/nexus/api/ops-seats.ts +16 -6
- package/src/nexus/api/seat-requests.ts +88 -8
- package/src/nexus/api/sessions-store.ts +63 -0
- package/src/nexus/api/setup-codex-login.ts +160 -0
- package/src/nexus/api/usage-api.ts +102 -0
- package/src/nexus/index.ts +45 -4
- package/src/onboarding/first-run.ts +89 -0
- package/src/onboarding/lan-address.ts +45 -0
- package/src/onboarding/web-first.ts +119 -24
- package/src/onboarding.ts +4 -0
- package/src/ops-now/ops-now-refresh.ts +100 -0
- package/src/project/project-store.ts +115 -0
- package/src/release-loop/checklist.ts +78 -4
- package/src/release-loop/feature-store.ts +154 -21
- package/src/release-loop/release-schedule.ts +53 -0
- package/src/repl/index.ts +7 -0
- package/src/seat-dispatch/ceo-commands.ts +41 -8
- package/src/seat-dispatch/ceo-intent.ts +38 -0
- package/src/seat-loop/seat-loop.ts +231 -0
- package/src/seat-loop/seat-report.ts +64 -0
- package/src/self-dev/decompose.ts +66 -11
- package/src/self-dev/run-store.ts +10 -1
- package/src/self-implement/execution-clarification.ts +115 -0
- package/src/self-implement/gate-cli.ts +6 -3
- package/src/self-implement/orchestrator.ts +37 -3
- package/src/self-implement/pipeline-shape.ts +1 -1
- package/src/self-implement/run-status-mapping.ts +5 -3
- package/src/session/chat.ts +2 -0
- package/src/session/index.ts +9 -0
- package/src/shell-env-bootstrap.ts +17 -0
- package/src/steward/directive.ts +51 -2
- package/src/steward/launch.ts +250 -0
- package/src/steward/outcome-notify.ts +115 -0
- package/src/steward/steward-cards.ts +8 -2
- package/src/steward/triage-plan.ts +32 -4
- package/src/steward/triage.ts +93 -9
- package/src/steward/working-backwards.ts +64 -0
- package/src/storyboard/storyboard.ts +141 -0
- package/src/task-cards/card-store.ts +2 -2
- package/src/task-orchestrator/surfaces/pod-lease.ts +260 -0
- package/src/telegram-card-followup.ts +233 -0
- package/src/telegram-report.ts +15 -9
- package/src/telegram-run.ts +7 -0
- package/src/telegram.ts +29 -8
- package/src/tool-runtime/types.ts +4 -0
- package/src/tui-client/dashboard-session.ts +2 -0
- package/src/user-config.ts +118 -11
- package/src/version/packed-revision.json +1 -1
- package/src/web-push/notify-exec-request.ts +32 -0
- package/apps/pwa/out/_next/static/bFI5EEwg7T6aG88ibX4Zq/_buildManifest.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7032-f4ef698518fba61c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8067-d976b441a104ab74.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8630-cdfc61e82c653ffc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8894-e263b332208e3957.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9454-139040d37c79dbc9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9740-8f5ff96c522b5f55.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-fb7c2d458f32fcad.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-35e2713597dd24d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-918b656f064577ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-5f30e00693527ebd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-7fbae173b3c103a7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-3b4edcce364062ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-c5794f484c251f44.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-1130d8eefcd87e76.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-016e2bda04eecb1f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-b4d6d9a3b56f2940.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-ec49a466121233ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-f72db356744ceade.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-a6fd9b50d1b85378.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-adfbd2ba9598e59a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-46fedff57d4bec67.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-0cf318e9dcee2bbf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-899b327cb8b6845c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-6c1e6ba743a29dfe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-1a8e4807292f6b17.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-28deba6bcb834916.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-de68e2205794037d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-c71c33a573230672.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-4fc56790fe0d2682.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-59df2aebb38a7c1e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-4647be5dbcbd0795.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-e025892bda74b743.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-a0955de73b89f078.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-369846d2921af584.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-72cbc1c8cdd0f65d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-77b28bfb0996d5ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-3220c4934f49c6c2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-7df9243ed021a4cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-4498c3853f4f688d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +0 -1
- package/apps/pwa/out/_next/static/css/f92f63a5df0ba229.css +0 -1
- package/src/dashboard/turn-typeahead-echo.ts +0 -23
- /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
|
|
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
|
-
//
|
|
2211
|
-
//
|
|
2212
|
-
//
|
|
2213
|
-
useRegistry:
|
|
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
|
+
}
|
package/src/acp/turn-runner.ts
CHANGED
|
@@ -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
|
-
|
|
12
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
78
|
-
|
|
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 (
|
|
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
|
-
|
|
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: {
|