elanous 0.2.8 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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-d4b6df905f185055.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-186fc6952f027acc.js → page-f8116559c7424da7.js} +1 -1
- 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-cbbe03c0da7b081a.js → page-54e1f966ab60a4dc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-7ed95f296f5ee8e9.js +1 -0
- 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-c0c0d19d51342d3e.js → page-b31610deda76337b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-2e002ad8faa43291.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-7464c1987dd76e6d.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-d8ab4d1489223459.js → page-4fddc90b23feb215.js} +1 -1
- 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-26102fd7350f171e.js +1 -0
- 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-139ba5c367bbf60a.js +1 -0
- 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 +30 -29
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +30 -29
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +30 -29
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +27 -26
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +27 -26
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +27 -26
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +30 -29
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +30 -29
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +30 -29
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +30 -29
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +30 -29
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +30 -29
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +27 -26
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +30 -29
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +30 -29
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +27 -26
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +27 -26
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +30 -29
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +30 -29
- package/apps/pwa/out/ops/checklist/index.html +1 -0
- package/apps/pwa/out/ops/checklist/index.txt +30 -0
- package/apps/pwa/out/ops/release/index.html +1 -0
- package/apps/pwa/out/ops/release/index.txt +30 -0
- 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 +28 -27
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +30 -29
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +27 -26
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +30 -29
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +30 -29
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +30 -29
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +30 -29
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +30 -29
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +27 -26
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +30 -29
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +30 -29
- 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 +30 -29
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +30 -29
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +30 -29
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +30 -29
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +30 -29
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +30 -29
- package/graphs/field/field-feed.yaml +2 -0
- package/graphs/implement-loop.yaml +1 -1
- package/graphs/release/recipes.yaml +3 -0
- package/graphs/release/release-loop.yaml +3 -1
- package/graphs/test-diet/nightly-audit.yaml +18 -0
- package/graphs/test-diet/recipes.yaml +3 -0
- 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 +14 -7
- 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 +31 -2
- package/scripts/release-loop/export-check-node.ts +85 -0
- package/scripts/release-loop/gate-node.ts +18 -4
- package/scripts/release-loop/node-verdict.ts +8 -1
- 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 +31 -1
- package/scripts/test-diet/node.ts +134 -17
- 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.fixture.ts +19 -0
- package/src/auth/setup-link-tokens.ts +164 -0
- 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/budget/unified-usage.ts +75 -0
- package/src/channel-bot-token.ts +37 -10
- package/src/chat/ctrl-c-in-input.ts +14 -0
- package/src/chat/index.ts +61 -9
- 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/op-proxy-sync.ts +83 -0
- package/src/cli/ops-cli.ts +20 -0
- package/src/cli/pod-cli.ts +49 -0
- package/src/cli/pr-cli.ts +37 -4
- package/src/cli/pwa-stop.ts +163 -13
- package/src/cli/pwa-test.ts +3 -0
- package/src/cli/release-cli.ts +98 -15
- 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/dashboard-status-lines.ts +45 -0
- package/src/dashboard/first-screen-band.ts +35 -0
- package/src/dashboard/index.ts +128 -34
- package/src/dashboard/provider-slash-runtime.ts +28 -11
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +17 -0
- package/src/dashboard/slash-runtime/help-from-registry.ts +43 -28
- package/src/dashboard/turn-stream-formatter.ts +1 -1
- package/src/decisions/decision-cards.ts +50 -2
- 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 +172 -41
- 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 +100 -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 +73 -1
- package/src/nexus/api/meta-api.ts +38 -0
- package/src/nexus/api/operator.ts +61 -0
- package/src/nexus/api/ops-api.ts +148 -0
- package/src/nexus/api/ops-seats-sources.ts +95 -0
- package/src/nexus/api/ops-seats.ts +124 -0
- package/src/nexus/api/public-routes.ts +14 -1
- package/src/nexus/api/seat-requests.ts +88 -8
- package/src/nexus/api/sessions-store.ts +63 -0
- package/src/nexus/api/setup-claim.ts +21 -0
- package/src/nexus/api/setup-codex-login.ts +160 -0
- package/src/nexus/api/usage-api.ts +102 -0
- package/src/nexus/config/secrets/file-backend.ts +56 -18
- package/src/nexus/index.ts +45 -4
- package/src/onboarding/first-run-doctor.ts +54 -0
- 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 +76 -61
- package/src/release-loop/feature-store.ts +460 -0
- package/src/release-loop/manifest.ts +59 -5
- package/src/release-loop/release-schedule.ts +53 -0
- package/src/repl/index.ts +7 -0
- package/src/seat-dispatch/ceo-commands.ts +98 -0
- 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/skills/tools/usage-slash.ts +8 -7
- 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-commands.ts +16 -0
- package/src/telegram-report.ts +15 -9
- package/src/telegram-run.ts +90 -13
- package/src/telegram.ts +29 -8
- package/src/thinking-line.ts +28 -20
- package/src/tool-runtime/types.ts +4 -0
- package/src/tui/chat/execution-badge.ts +1 -1
- package/src/tui-client/dashboard-session.ts +2 -0
- package/src/user-config.ts +118 -11
- package/src/ux-sim/turn-states.ts +1 -1
- package/src/version/packed-revision.json +1 -1
- package/src/web-push/notify-exec-request.ts +32 -0
- 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/4836-3084c58c1725a40b.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/8894-7317998f6cb97d8c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9454-a3e0953307216c58.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-9aa47b0bc6d570ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f4d2ecbbae6088d6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-2053e49930c82748.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-a70ba27c33003dd0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-18166f885d831d3e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-1c9d6cfe68b121c2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-c108ff1fdb665c97.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-147a328753d983e2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-799395a9df7d95aa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-6a8158b71d1fe9d7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-9567560270857531.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-e7ed3d010153d818.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-b672e02e4bec8deb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-1ba4a5d857276334.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-9db2226f38c542fb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-70ee65f5f7a151dc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-0bb8e43203efb23b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-092e1bcf27d751cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-eb52580c4b75b905.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-e573437b0a1c728f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-294300625e49e4d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-085847b088675978.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-9fe9d38c36999eb4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-6141f67f531d8b6a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-295a02b1026b8255.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-1eb9a0e1e054209d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-82efa177a30f3741.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d0be51cec4f62606.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-2e367075ffdbc062.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-647ed2b66c1d49f1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ba058c56e38f2792.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +0 -1
- package/apps/pwa/out/_next/static/css/5ef72a618a8994f3.css +0 -1
- package/apps/pwa/out/_next/static/lywc8m_HDzH9MKSDQhyEj/_buildManifest.js +0 -1
- package/src/dashboard/turn-typeahead-echo.ts +0 -23
- /package/apps/pwa/out/_next/static/{lywc8m_HDzH9MKSDQhyEj → 2DsiDfDCks44g1cPUMhiW}/_ssgManifest.js +0 -0
|
@@ -3,16 +3,13 @@ import { join } from 'node:path';
|
|
|
3
3
|
|
|
4
4
|
import { elanousStateRoot } from '../autopilot/state-paths.js';
|
|
5
5
|
import { ASK_USER_QUESTION_DELIVERY_VALUES } from './types.js';
|
|
6
|
-
import type { AskUserQuestionRequest, AskUserQuestionResult, HitlDelivery, QuestionOption } from './types.js';
|
|
6
|
+
import type { AskUserQuestionRequest, AskUserQuestionResult, HitlDelivery, Question, QuestionOption } from './types.js';
|
|
7
7
|
|
|
8
|
-
interface PendingQuestion {
|
|
8
|
+
export interface PendingQuestion {
|
|
9
9
|
id: string;
|
|
10
10
|
sessionId?: string;
|
|
11
|
-
|
|
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
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { openSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { dlopen, FFIType } from 'bun:ffi';
|
|
4
|
+
import { claimSetupLinkToken } from './setup-link-tokens.js';
|
|
5
|
+
|
|
6
|
+
const [mode, dir, token, now] = process.argv.slice(2);
|
|
7
|
+
if (mode === 'claim') {
|
|
8
|
+
process.stdout.write(JSON.stringify(claimSetupLinkToken(token, { dir, now: Number(now) })));
|
|
9
|
+
} else if (mode === 'hold') {
|
|
10
|
+
const fd = openSync(join(dir, 'setup-link-tokens.json.lock'), 'r+');
|
|
11
|
+
const libc = dlopen(process.platform === 'darwin' ? '/usr/lib/libSystem.B.dylib' : 'libc.so.6', {
|
|
12
|
+
flock: { args: [FFIType.i32, FFIType.i32], returns: FFIType.i32 },
|
|
13
|
+
});
|
|
14
|
+
if (libc.symbols.flock(fd, 2) !== 0) throw new Error('failed to acquire lock');
|
|
15
|
+
process.stdout.write('locked\n');
|
|
16
|
+
await new Promise<void>(() => { setInterval(() => {}, 1000); });
|
|
17
|
+
} else {
|
|
18
|
+
throw new Error(`unknown fixture mode: ${mode}`);
|
|
19
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { closeSync, constants, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { dlopen, FFIType } from 'bun:ffi';
|
|
5
|
+
import { getElanousConfigDir } from '../elanous-config-dir.js';
|
|
6
|
+
|
|
7
|
+
export const SETUP_LINK_TTL_MS = 10 * 60_000;
|
|
8
|
+
const SETUP_BEARER_TTL_MS = 24 * 60 * 60_000;
|
|
9
|
+
const LINK_PREFIX = 'els_';
|
|
10
|
+
const BEARER_PREFIX = 'elsb_';
|
|
11
|
+
const HASH_PATTERN = /^[a-f0-9]{64}$/;
|
|
12
|
+
const LINK_PATTERN = /^els_[A-Za-z0-9_-]{43}$/;
|
|
13
|
+
const BEARER_PATTERN = /^elsb_[A-Za-z0-9_-]{43}$/;
|
|
14
|
+
|
|
15
|
+
interface SetupLinkEntry {
|
|
16
|
+
hash: string;
|
|
17
|
+
expiresAt: string;
|
|
18
|
+
usedAt?: string;
|
|
19
|
+
bearerHash?: string;
|
|
20
|
+
bearerExpiresAt?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type ClaimResult =
|
|
24
|
+
| { ok: true; bearer: string; expiresAt: string }
|
|
25
|
+
| { ok: false; reason: 'unknown' | 'used' | 'expired' };
|
|
26
|
+
|
|
27
|
+
function storePath(dir: string): string { return join(dir, 'setup-link-tokens.json'); }
|
|
28
|
+
function hashOf(token: string): string { return createHash('sha256').update(token).digest('hex'); }
|
|
29
|
+
function mint(prefix: string): string { return `${prefix}${randomBytes(32).toString('base64url')}`; }
|
|
30
|
+
|
|
31
|
+
function readStore(dir: string): SetupLinkEntry[] {
|
|
32
|
+
let raw: string;
|
|
33
|
+
try {
|
|
34
|
+
const path = storePath(dir);
|
|
35
|
+
const st = lstatSync(path);
|
|
36
|
+
if (!st.isFile() || (st.mode & 0o077) !== 0) throw new Error('setup link store must be a private regular file (0600)');
|
|
37
|
+
raw = readFileSync(path, 'utf8');
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return [];
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
const parsed: unknown = JSON.parse(raw);
|
|
43
|
+
if (!parsed || typeof parsed !== 'object' || !('tokens' in parsed) || !Array.isArray(parsed.tokens)) {
|
|
44
|
+
throw new Error('setup link store is malformed');
|
|
45
|
+
}
|
|
46
|
+
const entries: unknown[] = parsed.tokens;
|
|
47
|
+
if (!entries.every((entry) => {
|
|
48
|
+
if (!entry || typeof entry !== 'object') return false;
|
|
49
|
+
const t = entry as Partial<SetupLinkEntry>;
|
|
50
|
+
return typeof t.hash === 'string' && HASH_PATTERN.test(t.hash)
|
|
51
|
+
&& typeof t.expiresAt === 'string' && Number.isFinite(Date.parse(t.expiresAt))
|
|
52
|
+
&& (t.usedAt === undefined || (typeof t.usedAt === 'string' && Number.isFinite(Date.parse(t.usedAt))))
|
|
53
|
+
&& (t.bearerHash === undefined || (typeof t.bearerHash === 'string' && HASH_PATTERN.test(t.bearerHash)))
|
|
54
|
+
&& (t.bearerExpiresAt === undefined || (typeof t.bearerExpiresAt === 'string' && Number.isFinite(Date.parse(t.bearerExpiresAt))))
|
|
55
|
+
&& ((t.usedAt === undefined && t.bearerHash === undefined && t.bearerExpiresAt === undefined)
|
|
56
|
+
|| (t.usedAt !== undefined && t.bearerHash !== undefined && t.bearerExpiresAt !== undefined));
|
|
57
|
+
})) throw new Error('setup link store is malformed');
|
|
58
|
+
return entries as SetupLinkEntry[];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function writeStore(dir: string, tokens: SetupLinkEntry[]): void {
|
|
62
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
63
|
+
const path = storePath(dir);
|
|
64
|
+
const tmp = `${path}.${process.pid}.${randomBytes(4).toString('hex')}.tmp`;
|
|
65
|
+
try {
|
|
66
|
+
writeFileSync(tmp, JSON.stringify({ tokens }, null, 2), { flag: 'wx', mode: 0o600 });
|
|
67
|
+
renameSync(tmp, path);
|
|
68
|
+
} finally {
|
|
69
|
+
try { unlinkSync(tmp); } catch (error) {
|
|
70
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// A persistent inode and an OS advisory lock avoid stale owner files after an abrupt process exit.
|
|
76
|
+
// Never unlink this file: a replacement inode would let two processes hold different locks.
|
|
77
|
+
// Loaded on first use, not at import: libc.so.6 does not exist on Windows and this module sits on the CLI start path (WIN2 10-02).
|
|
78
|
+
type Flock = (fd: number, op: number) => number;
|
|
79
|
+
let flockFn: Flock | null | undefined;
|
|
80
|
+
function osFlock(): Flock | null {
|
|
81
|
+
if (flockFn !== undefined) return flockFn;
|
|
82
|
+
if (process.platform === 'win32') return (flockFn = null);
|
|
83
|
+
const lib = dlopen(process.platform === 'darwin' ? '/usr/lib/libSystem.B.dylib' : 'libc.so.6', {
|
|
84
|
+
flock: { args: [FFIType.i32, FFIType.i32], returns: FFIType.i32 },
|
|
85
|
+
});
|
|
86
|
+
return (flockFn = lib.symbols.flock as Flock);
|
|
87
|
+
}
|
|
88
|
+
const LOCK_EX = 2;
|
|
89
|
+
const LOCK_NB = 4;
|
|
90
|
+
const LOCK_UN = 8;
|
|
91
|
+
|
|
92
|
+
function withLock<T>(dir: string, fn: () => T): T {
|
|
93
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
94
|
+
const flock = osFlock();
|
|
95
|
+
// Windows: no advisory flock and no POSIX modes — the store still writes by atomic rename.
|
|
96
|
+
if (!flock) return fn();
|
|
97
|
+
const lock = `${storePath(dir)}.lock`;
|
|
98
|
+
const fd = openSync(lock, constants.O_CREAT | constants.O_RDWR | constants.O_NOFOLLOW, 0o600);
|
|
99
|
+
try {
|
|
100
|
+
const stat = fstatSync(fd);
|
|
101
|
+
const current = lstatSync(lock);
|
|
102
|
+
if (!stat.isFile() || (stat.mode & 0o077) !== 0 || stat.ino !== current.ino || stat.dev !== current.dev) {
|
|
103
|
+
throw new Error('setup link lock must be a private regular file (0600)');
|
|
104
|
+
}
|
|
105
|
+
const until = Date.now() + 3000;
|
|
106
|
+
while (flock(fd, LOCK_EX | LOCK_NB) !== 0) {
|
|
107
|
+
if (Date.now() > until) throw new Error('setup link store is locked — try again');
|
|
108
|
+
Bun.sleepSync(20);
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
const locked = lstatSync(lock);
|
|
112
|
+
if (stat.ino !== locked.ino || stat.dev !== locked.dev) throw new Error('setup link lock changed');
|
|
113
|
+
return fn();
|
|
114
|
+
} finally {
|
|
115
|
+
flock(fd, LOCK_UN);
|
|
116
|
+
}
|
|
117
|
+
} finally {
|
|
118
|
+
closeSync(fd);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** The plaintext link token is returned once; only its SHA-256 digest is persisted. */
|
|
123
|
+
export function issueSetupLinkToken(opts: { dir?: string; now?: number } = {}): { token: string; expiresAt: string } {
|
|
124
|
+
const dir = opts.dir ?? getElanousConfigDir();
|
|
125
|
+
const now = opts.now ?? Date.now();
|
|
126
|
+
const token = mint(LINK_PREFIX);
|
|
127
|
+
const expiresAt = new Date(now + SETUP_LINK_TTL_MS).toISOString();
|
|
128
|
+
withLock(dir, () => writeStore(dir, [...readStore(dir), { hash: hashOf(token), expiresAt }]));
|
|
129
|
+
return { token, expiresAt };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Claim exactly once under the same lock used by issuance. Expired and used links retain their diagnostic status. */
|
|
133
|
+
export function claimSetupLinkToken(token: string, opts: { dir?: string; now?: number } = {}): ClaimResult {
|
|
134
|
+
if (typeof token !== 'string' || !LINK_PATTERN.test(token)) return { ok: false, reason: 'unknown' };
|
|
135
|
+
const dir = opts.dir ?? getElanousConfigDir();
|
|
136
|
+
const now = opts.now ?? Date.now();
|
|
137
|
+
return withLock(dir, () => {
|
|
138
|
+
const entries = readStore(dir);
|
|
139
|
+
const digest = Buffer.from(hashOf(token), 'hex');
|
|
140
|
+
const hit = entries.find((entry) => timingSafeEqual(digest, Buffer.from(entry.hash, 'hex')));
|
|
141
|
+
if (!hit) return { ok: false, reason: 'unknown' };
|
|
142
|
+
if (hit.usedAt) return { ok: false, reason: 'used' };
|
|
143
|
+
if (Date.parse(hit.expiresAt) <= now) return { ok: false, reason: 'expired' };
|
|
144
|
+
const bearer = mint(BEARER_PREFIX);
|
|
145
|
+
const expiresAt = new Date(now + SETUP_BEARER_TTL_MS).toISOString();
|
|
146
|
+
hit.usedAt = new Date(now).toISOString();
|
|
147
|
+
hit.bearerHash = hashOf(bearer);
|
|
148
|
+
hit.bearerExpiresAt = expiresAt;
|
|
149
|
+
writeStore(dir, entries);
|
|
150
|
+
return { ok: true, bearer, expiresAt };
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** A setup bearer is not an owner or temp-owner token; validate it only on setup-allowed routes. */
|
|
155
|
+
export function matchSetupBearer(bearer: string, opts: { dir?: string; now?: number } = {}): { expiresAt: string } | null {
|
|
156
|
+
if (typeof bearer !== 'string' || !BEARER_PATTERN.test(bearer)) return null;
|
|
157
|
+
let entries: SetupLinkEntry[];
|
|
158
|
+
try { entries = readStore(opts.dir ?? getElanousConfigDir()); } catch { return null; }
|
|
159
|
+
const digest = Buffer.from(hashOf(bearer), 'hex');
|
|
160
|
+
const now = opts.now ?? Date.now();
|
|
161
|
+
const hit = entries.find((entry) => entry.bearerHash && entry.bearerExpiresAt
|
|
162
|
+
&& timingSafeEqual(digest, Buffer.from(entry.bearerHash, 'hex')) && Date.parse(entry.bearerExpiresAt) > now);
|
|
163
|
+
return hit?.bearerExpiresAt ? { expiresAt: hit.bearerExpiresAt } : null;
|
|
164
|
+
}
|
|
@@ -255,6 +255,7 @@ const STEP_CMDS: Record<GateStepName, { cmd: string; args: string[]; timeoutMs:
|
|
|
255
255
|
'cli-smoke': { cmd: 'bun', args: ['bin/elanous.mjs', '--help'], timeoutMs: 60_000 },
|
|
256
256
|
};
|
|
257
257
|
|
|
258
|
+
const DEPENDENCY_INSTALL_TIMEOUT_MS = 300_000;
|
|
258
259
|
const MAX_LOGGED_TEST_FILTERS = 6;
|
|
259
260
|
|
|
260
261
|
/** Platform runners own Kotlin/Swift execution; Bun must never receive their paths as test filters. */
|
|
@@ -289,6 +290,17 @@ export async function runIntegrityGate(
|
|
|
289
290
|
const runCmd = opts.runCmd ?? createRunCmd();
|
|
290
291
|
const results: GateStep[] = [];
|
|
291
292
|
const logs: string[] = [];
|
|
293
|
+
// A Pod worktree has no node_modules unless the child happened to install them (the Pod clone has none to link), and Bun
|
|
294
|
+
// then auto-installs at import time — «Unexpected while resolving package 'commander'» failed cli-smoke as an
|
|
295
|
+
// «environment deficiency» three times on 10-02 (#22849 · #22919). Install from the lockfile before measuring instead.
|
|
296
|
+
if (steps.length > 0 && existsSync(join(cwd, 'bin', 'elanous.mjs')) && existsSync(join(cwd, 'bun.lock'))
|
|
297
|
+
&& !existsSync(join(cwd, 'node_modules', 'commander', 'package.json'))) {
|
|
298
|
+
const install = await runCmd('bun', ['install', '--frozen-lockfile'], cwd, DEPENDENCY_INSTALL_TIMEOUT_MS);
|
|
299
|
+
const installed = install.code === 0 && !install.timedOut;
|
|
300
|
+
debug.log('gate.deps', 'install', { installed, code: install.code, timedOut: install.timedOut });
|
|
301
|
+
const tail = (install.stderr || install.stdout).split('\n').filter(Boolean).slice(-2).join(' | ').slice(0, 200);
|
|
302
|
+
logs.push(`[deps] ${installed ? 'INSTALLED' : 'FAIL'} bun install --frozen-lockfile — node_modules/commander was missing${installed ? '' : `: ${tail}`}`);
|
|
303
|
+
}
|
|
292
304
|
let passed = true;
|
|
293
305
|
let testPassCount: number | undefined;
|
|
294
306
|
let sawPassCountRegression = false;
|
|
@@ -107,6 +107,8 @@ export async function runDaemonPromptTurn(opts: {
|
|
|
107
107
|
surfaceResolutionReason?: SurfaceKindResolutionReason;
|
|
108
108
|
/** Optional confirm channels for the triggering chat surface. */
|
|
109
109
|
surfaceHitlChannels?: ConfirmChannel[];
|
|
110
|
+
/** Server-authenticated human owner for this request; absent for delegated or unverified callers. */
|
|
111
|
+
verifiedOwner?: { id: string };
|
|
110
112
|
/** Optional structured-question channels for the triggering chat surface. */
|
|
111
113
|
surfaceQuestionChannels?: QuestionChannel[];
|
|
112
114
|
/** CWD for fs-bound tool dispatch (Read / Grep). Defaults to
|
|
@@ -226,6 +228,7 @@ export async function runDaemonPromptTurn(opts: {
|
|
|
226
228
|
...(request.source?.kind === 'daemon-api' && request.source.route === '/a2a'
|
|
227
229
|
? { requestOrigin: 'external-agent' as const } : {}),
|
|
228
230
|
...(request.userText ? { userText: request.userText } : {}),
|
|
231
|
+
...(opts.verifiedOwner ? { verifiedOwner: opts.verifiedOwner } : {}),
|
|
229
232
|
...(opts.surface !== undefined || sourceResolution.surface !== 'unknown' ? { surface } : {}),
|
|
230
233
|
...(opts.surfaceHitlChannels ? { surfaceHitlChannels: opts.surfaceHitlChannels } : {}),
|
|
231
234
|
...(opts.surfaceQuestionChannels ? { surfaceQuestionChannels: opts.surfaceQuestionChannels } : {}),
|
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
dispatchBrowserRead,
|
|
17
17
|
} from '../../tool-runtime/browser-runtime.js';
|
|
18
18
|
import { askUserQuestionRuntime } from '../../tool-runtime/ask-user-question-runtime.js';
|
|
19
|
+
import { buildElanousHoldTool, buildPtyControlTool, dispatchElanousHold, dispatchPtyControl } from '../../tool-runtime/elanous-control-runtimes.js';
|
|
20
|
+
import { ptyAvailable } from '../../pty-shell/registry.js';
|
|
19
21
|
import { dispatchToolByName, listToolRuntimes } from '../../tool-runtime/registry.js';
|
|
20
22
|
import { ACP_SESSION_RUNTIMES } from '../../tool-runtime/acp-session-runtime.js';
|
|
21
23
|
import { ClaudeSubscriptionNotAllowedError } from '../../policy/claude-subscription-guard.js';
|
|
@@ -340,6 +342,7 @@ export function toolSurface(kind: DaemonToolSurfaceKind, cfg?: import('../../use
|
|
|
340
342
|
askUserQuestionRuntime.spec,
|
|
341
343
|
...ACP_SESSION_RUNTIMES.map((runtime) => runtime.spec),
|
|
342
344
|
...shared.specs, // L2 core + L3 finance(gated) — buildSharedAppTools 단일 출처(전 서피스 공용)
|
|
345
|
+
...(ptyAvailable() ? [buildElanousHoldTool(), buildPtyControlTool()] : []),
|
|
343
346
|
];
|
|
344
347
|
|
|
345
348
|
/** Shared dispatcher for chat-surface tools. Reused by 'chat' AND
|
|
@@ -392,11 +395,16 @@ export function toolSurface(kind: DaemonToolSurfaceKind, cfg?: import('../../use
|
|
|
392
395
|
// L2 core + L3 finance(gated) — buildSharedAppTools 단일 dispatch · B5: 진행 한 줄(emitFeedback)·세션·호출 id 를 넘긴다.
|
|
393
396
|
return shared.dispatch(name, args, {
|
|
394
397
|
surface: 'tui',
|
|
398
|
+
...(ctx.requestOrigin ? { requestOrigin: ctx.requestOrigin } : {}),
|
|
399
|
+
...(ctx.verifiedOwner ? { verifiedOwner: ctx.verifiedOwner } : {}),
|
|
395
400
|
...(ctx.sessionId ? { sessionId: ctx.sessionId } : {}),
|
|
401
|
+
...(ctx.surfaceHitlChannels ? { confirmChannels: ctx.surfaceHitlChannels } : {}),
|
|
396
402
|
...(ctx.toolCallId ? { toolCallId: ctx.toolCallId } : {}),
|
|
397
403
|
...(ctx.emitFeedback ? { emitFeedback: ctx.emitFeedback } : {}),
|
|
398
404
|
});
|
|
399
405
|
}
|
|
406
|
+
if (ptyAvailable() && name === 'ElanousHold') return dispatchElanousHold(args);
|
|
407
|
+
if (ptyAvailable() && name === 'PtyControl') return dispatchPtyControl(args);
|
|
400
408
|
if (name === 'Bash') {
|
|
401
409
|
// PLAN-ios-rich-dev-feedback-hydrate M3 (2026-05-13) — pass
|
|
402
410
|
// sessionId + emitFeedback so dispatchBash emits tool.progress
|
|
@@ -61,6 +61,8 @@ export interface DaemonToolDispatchCtx {
|
|
|
61
61
|
sessionId?: string;
|
|
62
62
|
/** Trusted server-side classification of the current turn, never supplied as a tool argument. */
|
|
63
63
|
requestOrigin?: RequestOrigin;
|
|
64
|
+
/** Owner principal verified at the server request boundary, not from input source metadata. */
|
|
65
|
+
verifiedOwner?: { id: string };
|
|
64
66
|
/** Ingestion entry class. Omit for conservative external-verbatim fallback. */
|
|
65
67
|
entry?: IngestionEntry;
|
|
66
68
|
/** M5 PR 2 (PLAN-rich-dev-feedback-multi-surface · 2026-05-13) —
|
|
@@ -41,6 +41,7 @@ export function buildWebSearchTool(): LLMToolSpec {
|
|
|
41
41
|
'Search the public web via the daemon\'s configured provider (Grok / ' +
|
|
42
42
|
'Firecrawl / etc.). When no key is available, falls back to keyless DuckDuckGo. ' +
|
|
43
43
|
'Returns a ranked list of URLs + titles + snippets. ' +
|
|
44
|
+
'For fact questions, answer directly from the snippets and URLs; read the full body only when insufficient. ' +
|
|
44
45
|
'Pair with a follow-up Read or external WebFetch when the model needs ' +
|
|
45
46
|
'the full article body.',
|
|
46
47
|
parameters: {
|
|
@@ -18,6 +18,7 @@ import { listCodexAccountsInStore } from '../oauth/codex-account-store.js';
|
|
|
18
18
|
import { loadTokens } from '../oauth/store.js';
|
|
19
19
|
import { envLiteral } from '../platform/env-literal.js';
|
|
20
20
|
import { describeResetCreditExpiry } from './codex-reset-credit-state.js';
|
|
21
|
+
import { visibleWidth } from '../tui.js';
|
|
21
22
|
import { listCodexResetCredits, type CodexResetCreditsResult } from './codex-reset-credits.js';
|
|
22
23
|
import type {
|
|
23
24
|
AccountUsageRow,
|
|
@@ -268,6 +269,80 @@ async function collectOpenRouterRows(deps: UnifiedUsageDeps): Promise<AccountUsa
|
|
|
268
269
|
}
|
|
269
270
|
}
|
|
270
271
|
|
|
272
|
+
export function formatUsageCompact(report: UnifiedUsageReport, options: { width: number; nowMs: number }): string[] {
|
|
273
|
+
return report.rows.map((row) => {
|
|
274
|
+
const credits = row.credits;
|
|
275
|
+
const subscription = row.subscription;
|
|
276
|
+
const remaining = subscription.status === 'available' && Number.isFinite(subscription.remainingPercent)
|
|
277
|
+
? subscription.remainingPercent
|
|
278
|
+
: credits.status === 'ok' && credits.usedPercent !== null && Number.isFinite(credits.usedPercent)
|
|
279
|
+
? Math.round((100 - credits.usedPercent) * 10) / 10
|
|
280
|
+
: null;
|
|
281
|
+
const period = subscription.status === 'available'
|
|
282
|
+
? subscription.windowKind
|
|
283
|
+
: credits.status === 'ok' ? credits.periodType : null;
|
|
284
|
+
const reset = subscription.status === 'available' ? subscription.resetsAt
|
|
285
|
+
: credits.status === 'ok' && credits.periodEnd ? Date.parse(credits.periodEnd) : Number.NaN;
|
|
286
|
+
const periodLabel = period ? `${formatCompactPeriod(period)} ` : '';
|
|
287
|
+
const when = period === 'prepaid-usd' ? '' : !isValidEpochMs(reset)
|
|
288
|
+
? ` · ${periodLabel}시각 모름`
|
|
289
|
+
: reset <= options.nowMs
|
|
290
|
+
? ` · ${periodLabel}재조회 필요`
|
|
291
|
+
: ` · ${periodLabel}${formatCompactReset(reset, options.nowMs)}에 다시 참`;
|
|
292
|
+
const balance = credits.status === 'ok' && credits.balance !== null && Number.isFinite(credits.balance)
|
|
293
|
+
? ` · 크레딧 ${new Intl.NumberFormat('en-US', { maximumFractionDigits: credits.balance >= 1_000 ? 0 : 1 }).format(credits.balance)}` : '';
|
|
294
|
+
const prefix = `${row.provider} `;
|
|
295
|
+
const name = row.accountName.replace(/[\r\n\t\x00-\x1f\x7f]/g, ' ');
|
|
296
|
+
const remainingLabel = `남은 ${remaining === null ? '잔량 모름' : `${remaining}%`}`;
|
|
297
|
+
const width = Number.isFinite(options.width) ? Math.max(0, Math.floor(options.width)) : 120;
|
|
298
|
+
const suffix = ` · ${remainingLabel}${when}${balance}`;
|
|
299
|
+
const available = width - visibleWidth(prefix) - visibleWidth(suffix);
|
|
300
|
+
if (available >= 1) {
|
|
301
|
+
const account = visibleWidth(name) <= available ? name
|
|
302
|
+
: available === 1 ? '…' : `${compactSlice(name, available - 1)}…`;
|
|
303
|
+
return `${prefix}${account}${suffix}`;
|
|
304
|
+
}
|
|
305
|
+
// The fixed labels can exceed very narrow widths even with no account name.
|
|
306
|
+
// Keep the measured value when possible rather than slicing its digits.
|
|
307
|
+
const essential = `${row.provider} · ${remainingLabel}`;
|
|
308
|
+
if (visibleWidth(essential) <= width) return essential;
|
|
309
|
+
if (visibleWidth(remainingLabel) <= width) return remainingLabel;
|
|
310
|
+
const value = remaining === null ? '잔량 모름' : `${remaining}%`;
|
|
311
|
+
if (visibleWidth(value) <= width) return value;
|
|
312
|
+
return width > 0 ? '…' : '';
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function formatCompactPeriod(value: string): string {
|
|
317
|
+
const period = value.replace(/^USAGE_PERIOD_TYPE_/, '').toLowerCase();
|
|
318
|
+
switch (period) {
|
|
319
|
+
case 'weekly': return '주간';
|
|
320
|
+
case 'monthly': return '월간';
|
|
321
|
+
case 'daily': return '일간';
|
|
322
|
+
default: return period;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function formatCompactReset(reset: number, nowMs: number): string {
|
|
327
|
+
const remaining = reset - nowMs;
|
|
328
|
+
if (remaining < 86_400_000) {
|
|
329
|
+
if (remaining < 3_600_000) return `${Math.ceil(remaining / 60_000)}분 뒤`;
|
|
330
|
+
return `${Math.ceil(remaining / 3_600_000)}시간 뒤`;
|
|
331
|
+
}
|
|
332
|
+
const date = new Date(reset);
|
|
333
|
+
const weekdays = ['일', '월', '화', '수', '목', '금', '토'];
|
|
334
|
+
return `${date.getMonth() + 1}/${date.getDate()} (${weekdays[date.getDay()]}) ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function compactSlice(text: string, width: number): string {
|
|
338
|
+
let result = '';
|
|
339
|
+
for (const { segment } of new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(text)) {
|
|
340
|
+
if (visibleWidth(result + segment) > width) break;
|
|
341
|
+
result += segment;
|
|
342
|
+
}
|
|
343
|
+
return result;
|
|
344
|
+
}
|
|
345
|
+
|
|
271
346
|
export function formatUnifiedUsage(report: UnifiedUsageReport): string {
|
|
272
347
|
const lines: string[] = [
|
|
273
348
|
'usage — 계정마다 한 행 · 크레딧 축과 구독 축은 각각 칸',
|
package/src/channel-bot-token.ts
CHANGED
|
@@ -1,35 +1,62 @@
|
|
|
1
1
|
import { existsSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { debug } from './debug/log.js';
|
|
4
|
-
import { getSecret, setSecretAsync } from './nexus/config/secrets/index.js';
|
|
5
|
-
import { userConfigPath } from './nexus/config/paths.js';
|
|
4
|
+
import { getSecret, setSecretAsync, currentBackendId } from './nexus/config/secrets/index.js';
|
|
5
|
+
import { userConfigPath, secretsPath } from './nexus/config/paths.js';
|
|
6
6
|
import { isSecretRef, makeSecretRef, secretIdFromRef } from './nexus/config/types.js';
|
|
7
7
|
import { acquireLockAsync } from './storage/file-lock.js';
|
|
8
8
|
import type { UserConfig } from './user-config.js';
|
|
9
9
|
|
|
10
10
|
export type ChannelBotPlatform = 'telegram' | 'discord';
|
|
11
|
-
export type ChannelBotTokenSource = 'tokenRef' | 'env' | '
|
|
11
|
+
export type ChannelBotTokenSource = 'tokenRef' | 'env' | 'botToken' | 'channels.main';
|
|
12
|
+
export type ChannelBotTokenResolution = { token: string; source: ChannelBotTokenSource; reason?: never }
|
|
13
|
+
| { token: ''; source?: never; reason: string };
|
|
12
14
|
|
|
13
15
|
/** The tab reference is read from the same persistent config as the NEXUS switch. */
|
|
14
16
|
export function resolveChannelBotToken(
|
|
15
17
|
platform: ChannelBotPlatform,
|
|
16
18
|
cfg: Pick<UserConfig, 'telegram' | 'discord'> & { tabs?: Record<string, { tokenRef?: unknown }> },
|
|
17
|
-
):
|
|
19
|
+
): ChannelBotTokenResolution {
|
|
20
|
+
let reason: string | undefined;
|
|
18
21
|
const ref = cfg.tabs?.[`${platform}:1`]?.tokenRef;
|
|
19
22
|
if (isSecretRef(ref)) {
|
|
20
23
|
const id = secretIdFromRef(ref);
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
if (id) {
|
|
25
|
+
const backend = currentBackendId();
|
|
26
|
+
if (backend !== 'file') {
|
|
27
|
+
reason = `backend ${backend} not sync-readable`;
|
|
28
|
+
} else {
|
|
29
|
+
const token = getSecret(id);
|
|
30
|
+
if (token) {
|
|
31
|
+
debug.log('channel-bot.token', 'resolved', { kind: platform, source: 'tokenRef' });
|
|
32
|
+
return { token, source: 'tokenRef' };
|
|
33
|
+
}
|
|
34
|
+
reason = `ref-id-missing ${id} in ${secretsPath()}`;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
23
37
|
}
|
|
24
38
|
const envName = platform === 'telegram' ? 'ELANOUS_TELEGRAM_BOT_TOKEN' : 'ELANOUS_DISCORD_BOT_TOKEN';
|
|
25
39
|
const envToken = process.env[envName];
|
|
26
|
-
if (envToken)
|
|
40
|
+
if (envToken) {
|
|
41
|
+
debug.log('channel-bot.token', 'resolved', { kind: platform, source: 'env' });
|
|
42
|
+
return { token: envToken, source: 'env' };
|
|
43
|
+
}
|
|
27
44
|
const token = cfg[platform]?.botToken;
|
|
28
45
|
if (token) {
|
|
29
|
-
debug.log('channel-bot.token', '
|
|
30
|
-
return { token, source: '
|
|
46
|
+
debug.log('channel-bot.token', 'resolved', { kind: platform, source: 'botToken' });
|
|
47
|
+
return { token, source: 'botToken' };
|
|
48
|
+
}
|
|
49
|
+
if (platform === 'telegram') {
|
|
50
|
+
const channels = cfg.telegram?.channels;
|
|
51
|
+
const channelToken = (channels?.find(c => c.name === 'main') ?? channels?.[0])?.botToken;
|
|
52
|
+
if (channelToken) {
|
|
53
|
+
debug.log('channel-bot.token', 'resolved', { kind: platform, source: 'channels.main' });
|
|
54
|
+
return { token: channelToken, source: 'channels.main' };
|
|
55
|
+
}
|
|
31
56
|
}
|
|
32
|
-
|
|
57
|
+
reason ??= 'no-source';
|
|
58
|
+
debug.log('channel-bot.token', 'unresolved', { kind: platform, reason });
|
|
59
|
+
return { token: '', reason };
|
|
33
60
|
}
|
|
34
61
|
|
|
35
62
|
/** Publish the secret before its reference, under the config lock. Other config keys survive unchanged. */
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** U1a — how long an empty-input Ctrl+C stays armed (and its hint stays on screen). */
|
|
2
|
+
export const CTRL_C_ARM_MS = 2_000;
|
|
3
|
+
|
|
4
|
+
export function resolveCtrlCInInput({ text, now, armedAt }: {
|
|
5
|
+
text: string;
|
|
6
|
+
now: number;
|
|
7
|
+
armedAt: number | null;
|
|
8
|
+
}): { action: 'clear-input' | 'arm-exit' | 'exit'; armedAt: number | null; hint: string | null } {
|
|
9
|
+
if (text.length > 0) return { action: 'clear-input', armedAt: null, hint: null };
|
|
10
|
+
if (armedAt !== null && now >= armedAt && now - armedAt <= CTRL_C_ARM_MS) {
|
|
11
|
+
return { action: 'exit', armedAt: null, hint: null };
|
|
12
|
+
}
|
|
13
|
+
return { action: 'arm-exit', armedAt: now, hint: '한 번 더 누르면 나갑니다 · /quit' };
|
|
14
|
+
}
|