elanous 0.2.4 → 0.2.6
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/README.md +7 -6
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/1169-696263ac7af8c4d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1883.c91a2648af4842fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1947-9d64872eeb9cfdec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2808-dc06482994b40f70.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 2984-977cfa339fcd65a5.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/3165-95515ca5f299a120.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8142-011e1f474efb8b33.js → 3190-e9282753e7ebde85.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3995-bee512a7063036ce.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6062-73e68343a9814f6a.js → 4240-7153881b0b377306.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4259-99614149577e77cb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4283-329eabeaf233e6cd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4328-cb5058b1acc2de5e.js +25 -0
- package/apps/pwa/out/_next/static/chunks/4801-ecee6ef62b349b18.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5110-8058791dd25a2e14.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3403.4bc97b4066945a94.js → 5329.8b7e4bca684b6eec.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/5330-c41e9670f594c38f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5423-e95fd125d1794349.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5478-b5eae07dc79ea619.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5787-96ee6642b9a661e5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6448-79d72368589f98bc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6522-9866c002b5531836.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6729-b4a558b7aa891e0c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6969-cb03f9410b7d2e03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7007-3b9861091bb8dd21.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5791-bb82743fa6299562.js → 763-7d0e1913b5074e98.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{4323.c5dfb6f503304775.js → 7633.0654fe6ea259c1ee.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7930-8dc7e20fc6fc19ca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8071-187edbc22a86741f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8198-d4101ad70cb9a579.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8250.4a2c5553a07cdf47.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8293-123b91e7b0330dc0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8296-d6d5d87dfa911d41.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8305-603963c33ee8eec3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8612-4a0066f20951b8fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2ea96958-e8c7e112de50b067.js → 8d0d9faf-97887ff99713b84e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9181-cf4e1b61c4783f92.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8046cadf-211ca3c0e00f2b72.js → 91ff6dda-3b33d42d20657429.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9262-26c2ecbd5a67e4a2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9328-45abdebce4fbecb7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9355-dbc67205adf8745f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9763.255b90dd69875689.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9933-66d31ec25c0231af.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-bd06fb55f4da0381.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-4e586142da455a25.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-889ad5cddcf45b0d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-47ba401d56572c27.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-0b54f9c83458e038.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-4fcc296ef6c8ed90.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-fe9fda494a87f4e5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-f9642e25ce129590.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-e1063e8ba5f3e2e7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-9d0df30027d8aa9d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-bd06fb55f4da0381.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-d97adf2c9455b8d0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-236b80d202110362.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-21a30f35f1a276ad.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-b1a5be357c4fa25d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1705a60303f7f573.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-76b337c11ad0e237.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-7c0f85fbb56f64c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-65ca16b20617b44d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-e7f9b8fc29411c1d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-92ea6213d1aeec04.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a913a005aacb5476.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-2d78804a40dfdef6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-08a99148e780ac00.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-deb674e7ed36f815.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-49f8de0534d192d7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-bd06fb55f4da0381.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-ba162a28d2176e18.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-1ff1a37b550ab41a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-ccaa698f56c0fbdb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-53b944082de1d7db.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-9b70adf1552789bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-30d482aa63241fe8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-aecf786f54651d8a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6ab9b62c7b6e08f2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-08949735455e0c9a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-a1479005234a46c2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3890e90bd232732.js +1 -0
- package/apps/pwa/out/_next/static/chunks/beb5cf10-9adcc10b83930090.js +1 -0
- package/apps/pwa/out/_next/static/chunks/framework-d68d41c88e05c801.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-11c63971b7a9593b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-d09452353bd0d9cc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-184d727cebeca767.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-21bbbbac99b720b1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{webpack-e5bea36f183b25e6.js → webpack-37b8bf78a3c6eec5.js} +1 -1
- package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +1 -0
- package/apps/pwa/out/_next/static/{zoQX5iTvXOsIvCjxCe1Ji → qMI0jnlZaBjjm4aIDYNGM}/_buildManifest.js +1 -1
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +29 -27
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +29 -27
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +29 -27
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +26 -24
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +26 -24
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +26 -24
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +29 -27
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +29 -27
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +29 -27
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/icons/elanous-16.png +0 -0
- package/apps/pwa/out/icons/elanous-180.png +0 -0
- package/apps/pwa/out/icons/elanous-192.png +0 -0
- package/apps/pwa/out/icons/elanous-32.png +0 -0
- package/apps/pwa/out/icons/elanous-512.png +0 -0
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +26 -24
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +29 -27
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +29 -27
- package/apps/pwa/out/manifest.webmanifest +5 -2
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +26 -24
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +26 -24
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +29 -27
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +29 -27
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +27 -25
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +29 -27
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +26 -24
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +29 -27
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +29 -27
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +29 -27
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +29 -27
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +29 -27
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +26 -24
- package/apps/pwa/out/sw-strategies.js +44 -0
- package/apps/pwa/out/sw.js +6 -44
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +29 -27
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +29 -27
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +29 -27
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +29 -27
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +29 -27
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +29 -27
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +29 -27
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +29 -27
- package/catalog/resources.yaml +6 -6
- package/docs/goal-context/steward-directives-2026-09-28.md +35 -37
- package/graphs/landing/landing-heal.yaml +1 -1
- package/graphs/release/recipes.yaml +11 -1
- package/graphs/release/release-loop.yaml +9 -2
- package/graphs/video/README.md +1 -0
- package/graphs/video/explainer-line.yaml +58 -0
- package/package.json +3 -1
- package/plugins/card-followup/README.md +33 -0
- package/plugins/card-followup/examples/card-sample.json +7 -0
- package/plugins/card-followup/graphs/card-followup.yaml +16 -0
- package/plugins/card-followup/graphs/recipes.yaml +12 -0
- package/plugins/card-followup/graphs/run-step.ts +172 -0
- package/plugins/card-followup/plugin.json +25 -0
- package/plugins/geo-check/README.md +13 -0
- package/plugins/geo-check/examples/brand-sample.json +8 -0
- package/plugins/geo-check/graphs/geo-check.yaml +20 -0
- package/plugins/geo-check/graphs/recipes.yaml +18 -0
- package/plugins/geo-check/graphs/run-step.ts +177 -0
- package/plugins/geo-check/plugin.json +24 -0
- package/plugins/job-coach/.codex-plugin/plugin.json +18 -1
- package/plugins/job-coach/README.md +42 -0
- package/plugins/job-coach/connectors/ncs/server.ts +4 -3
- package/plugins/job-coach/graphs/gap-judge.ts +44 -0
- package/plugins/job-coach/graphs/recipes.yaml +27 -0
- package/plugins/job-coach/graphs/report-enterprise.yaml +28 -0
- package/plugins/job-coach/graphs/run-step.ts +211 -17
- package/plugins/job-coach/plugin.json +16 -3
- package/plugins/job-coach/skills/enterprise-ai-fit/SKILL.md +8 -0
- package/plugins/job-coach/skills/enterprise-competency/SKILL.md +8 -0
- package/plugins/job-coach/skills/enterprise-needs/SKILL.md +8 -0
- package/plugins/job-coach/skills/enterprise-roadmap/SKILL.md +8 -0
- package/plugins/job-coach/skills/enterprise-task-analysis/SKILL.md +8 -0
- package/plugins/job-coach/skills/interview-to-profile/SKILL.md +18 -1
- package/scripts/backup/elanous-backup.sh +4 -1
- package/scripts/build-docs-site.ts +4 -3
- package/scripts/coord-channel-watch.sh +23 -4
- package/scripts/coord-post.sh +49 -12
- package/scripts/coord-tracks.json +5 -5
- package/scripts/daemon-port-baseline.txt +1 -2
- package/scripts/eval-bot-vs-grok.ts +79 -0
- package/scripts/eval-grok-live-baseline.ts +36 -0
- package/scripts/fixtures/front-route-corpus.sample.json +17 -0
- package/scripts/fleet/node-setup.sh +46 -7
- package/scripts/landing-heal/must-fix-match.ts +26 -0
- package/scripts/landing-heal/run-mission.ts +114 -20
- package/scripts/lib/android-test-device.ts +32 -0
- package/scripts/lib/front-route-measurement.ts +131 -0
- package/scripts/lib/tui-sim-keys.ts +42 -0
- package/scripts/measure-front-route-classifier.ts +56 -0
- package/scripts/media/ocr-leak-scan.sh +21 -0
- package/scripts/public-export.ts +2 -3
- package/scripts/public-import-pr.ts +80 -6
- package/scripts/publish-internal-dist.ts +2 -3
- package/scripts/red-sweep-diff.ts +1 -1
- package/scripts/release-loop/auto-approve-node.ts +72 -0
- package/scripts/release-loop/checklist-gate-node.ts +35 -0
- package/scripts/release-loop/gate-diff.ts +29 -0
- package/scripts/release-loop/gate-node.ts +366 -45
- package/scripts/release-loop/publish-node.ts +7 -3
- package/scripts/release-loop/release-docs.ts +20 -13
- package/scripts/release-loop/shard-plan.ts +47 -0
- package/scripts/release-loop/test-durations.ts +63 -0
- package/scripts/release-loop/upgrade-node.ts +3 -2
- package/scripts/test-audit-coverage-per-file.sh +16 -0
- package/scripts/test-audit-meaningless.ts +88 -0
- package/scripts/test-audit-overlap-and-weak.ts +73 -0
- package/scripts/test-deterministic.ts +27 -5
- package/scripts/tui-sim.ts +59 -14
- package/scripts/ux/bump-installed.sh +20 -0
- package/scripts/ux/lib/cdp.ts +72 -0
- package/scripts/ux/record-page.ts +53 -0
- package/skills/explainer-video/SKILL.md +117 -0
- package/skills/explainer-video/engine/brand/elanous-mark-on-dark.svg +1 -0
- package/skills/explainer-video/engine/build.mjs +431 -0
- package/skills/explainer-video/engine/reel.mjs +128 -0
- package/skills/explainer-video/engine/reel.sh +8 -0
- package/skills/explainer-video/engine/vo.ts +79 -0
- package/skills/explainer-video/examples/elanous-0.2.5/script.json +155 -0
- package/src/agent/nested-agent-env.ts +1 -0
- package/src/agent-mission/driver.ts +95 -16
- package/src/agent-mission/mission-cli.ts +6 -0
- package/src/agent-mission/resource-ladder.ts +73 -25
- package/src/auth/acp-token.ts +16 -0
- package/src/auth/github-app-token.ts +112 -0
- package/src/autopilot/gh-rest-fallback.ts +177 -0
- package/src/autopilot/mission-origin.ts +20 -4
- package/src/autopilot/mission-phase-track.ts +11 -1
- package/src/autopilot/pr-manager.ts +51 -4
- package/src/autopilot/track-agent.ts +3 -3
- package/src/autopilot/track-courier.ts +2 -2
- package/src/boot/acp-server.ts +2 -16
- package/src/boot/daemon-tools/web-search.ts +2 -1
- package/src/boot/ws-bridge.ts +11 -0
- package/src/chat/index.ts +37 -8
- package/src/chat/log-scroll-key.ts +12 -0
- package/src/chat/submit-trace.ts +30 -0
- package/src/cli/agent-cli.ts +7 -2
- package/src/cli/decisions-cli.ts +94 -0
- package/src/cli/doctor-cli.ts +40 -54
- package/src/cli/doctor-readiness.ts +16 -24
- package/src/cli/intake-cli.ts +1 -1
- package/src/cli/mcp-call.ts +9 -0
- package/src/cli/mcp-gateway-cli.ts +7 -3
- package/src/cli/nexus-ios-bind.ts +25 -8
- package/src/cli/phone-link.ts +117 -0
- package/src/cli/plugin-cli.ts +136 -11
- package/src/cli/pr-lineage.ts +2 -1
- package/src/cli/private-ledger-commands.ts +31 -0
- package/src/cli/pwa-start.ts +3 -0
- package/src/cli/release-cli.ts +97 -7
- package/src/cli/self-update.ts +7 -4
- package/src/cli/start-cli.ts +33 -14
- package/src/dashboard/acp-boot.ts +3 -0
- package/src/dashboard/index.ts +62 -13
- package/src/dashboard/input/no-provider-reply.ts +13 -0
- package/src/dashboard/slash-runtime/unknown-slash-reply.ts +78 -0
- package/src/dashboard/stale-install-check.ts +77 -0
- package/src/decisions/decision-ledger.ts +404 -0
- package/src/domains/outbound-alert.ts +13 -7
- package/src/domains/telegram-kind-route.ts +21 -0
- package/src/execution-loop/dispatch-task.ts +139 -0
- package/src/execution-loop/gate-decision-summary.ts +29 -0
- package/src/execution-loop/gates.ts +197 -0
- package/src/field/field-media.ts +245 -0
- package/src/graph-kinds/installed-plugin-nodes.ts +95 -0
- package/src/graph-kinds/plugin-nodes.ts +10 -2
- package/src/graph-runner/runner.ts +35 -5
- package/src/grounding/token.ts +12 -6
- package/src/harness/harness-cli-command.ts +68 -9
- package/src/harness/harness-pod-dispatch.ts +8 -2
- package/src/harness/harness-stop.ts +147 -18
- package/src/harness/self-send-decide.ts +26 -17
- package/src/harness/self-send-target.ts +22 -1
- package/src/harness/worktree-branch-prefix.ts +4 -3
- package/src/index.ts +50 -13
- package/src/instance/resolve.ts +6 -0
- package/src/intake-plane/collect-telegram-saved.ts +5 -0
- package/src/intake-plane/digest.ts +11 -2
- package/src/live/detail-switch.ts +8 -3
- package/src/llm/usable-llm.ts +32 -0
- package/src/loops/registry.ts +27 -5
- package/src/market/publish.ts +59 -3
- package/src/native-tool-catalog.ts +2 -2
- package/src/nexus/api/field-uploads.ts +143 -0
- package/src/nexus/api/graph-approvals.ts +174 -0
- package/src/nexus/api/harness-api.ts +219 -5
- package/src/nexus/api/http-server.ts +69 -6
- package/src/nexus/api/log-fabric.ts +10 -1
- package/src/nexus/api/mcp-oauth-hint.ts +10 -9
- package/src/nexus/api/merge-approvals.ts +2 -1
- package/src/nexus/api/meta-api.ts +14 -0
- package/src/nexus/api/plugins-credentials-api.ts +47 -0
- package/src/nexus/api/plugins-market.ts +87 -3
- package/src/nexus/api/pod-credential-api.ts +68 -14
- package/src/nexus/api/public-routes.ts +7 -0
- package/src/nexus/api/trace.ts +57 -6
- package/src/nexus/boot/register-mcp-clients.ts +99 -14
- package/src/nexus/index.ts +33 -32
- package/src/nexus/outbound/router.ts +1 -1
- package/src/notify/llm-budget-alert.ts +119 -0
- package/src/onboarding.ts +25 -2
- package/src/plugins/core/host.ts +14 -0
- package/src/plugins/install/market-fetch.ts +4 -3
- package/src/plugins/install/plugin-credentials.ts +106 -0
- package/src/plugins/install/plugin-install.ts +163 -32
- package/src/plugins/maker/plugin-maker.ts +208 -0
- package/src/prompt-library/universal-preamble.ts +8 -2
- package/src/relay/channel.ts +184 -0
- package/src/relay/mux.ts +182 -0
- package/src/relay/test-vectors.json +29 -0
- package/src/release-loop/checklist.ts +217 -0
- package/src/release-loop/release-note.ts +6 -3
- package/src/seat-address/seat-address.ts +34 -0
- package/src/self-dev/dev-cli.ts +40 -20
- package/src/self-dev/dev-pipeline.ts +77 -1
- package/src/self-implement/gate-baseline.ts +3 -1
- package/src/self-implement/goal-author.ts +18 -1
- package/src/self-implement/host-regate.ts +6 -2
- package/src/self-implement/orchestrator.ts +7 -2
- package/src/self-implement/run-ledger-compact.ts +1 -1
- package/src/self-implement/seams.ts +4 -2
- package/src/session/index.ts +23 -10
- package/src/skills/tools/dev-harness.ts +42 -0
- package/src/steward/steward-cards.ts +45 -0
- package/src/steward/triage.ts +50 -14
- package/src/task-orchestrator/surfaces/pod-artifact-return.ts +61 -3
- package/src/task-orchestrator/surfaces/pod-bun-cache.ts +18 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +36 -6
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +39 -9
- package/src/task-orchestrator/surfaces/pod-source-bundle.ts +16 -1
- package/src/task-orchestrator/surfaces/pod-source-receive.ts +51 -13
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +172 -28
- package/src/telegram-report.ts +12 -6
- package/src/telegram.ts +108 -1
- package/src/tui-client/acp-transport-ws-client.ts +72 -5
- package/src/tui.ts +3 -2
- package/src/user-config.ts +32 -2
- package/src/util/spawn-sync-output.ts +15 -0
- package/src/version/code-revision.ts +1 -1
- package/src/version/packed-revision.json +1 -1
- package/src/video-pipeline/recipes/explainer.ts +213 -0
- package/src/video-pipeline/walk-line.ts +3 -2
- package/src/web-search/free.ts +77 -0
- package/src/web-search/index.ts +2 -0
- package/src/workflow-runtime/deps-bridge.ts +4 -3
- package/src/workflow-runtime/plugin-kind-node.ts +32 -4
- package/src/workflow-runtime/types.ts +2 -1
- package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1191-7b13a24117a689fa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2375-3fcae5186c64fe6b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +0 -25
- package/apps/pwa/out/_next/static/chunks/319-d37df1b6e0cbeff4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4836-3c93795ab834381e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6090-1bd6077591603947.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7457-f5cb1cc8db9f7453.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8067-f74bf14d67ad98f1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8519-0059d82bdb4812bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9740-52467f244755917f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9804-64c02c3c127d5014.js +0 -1
- package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-21c3261af6ab377c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-1991445b8ea86274.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-4ebfaee2369fe248.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-9fa879ebf5959096.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-ca7c3cc305c3285a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-9951930add3d2051.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-4472ddf1e2458e69.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-4de08d4b766f134b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-7a975e2b9db1dfb7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-49270714ef35bb96.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-33fbdf353e2a5982.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-259f122e2dd64317.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-65b82726753658e1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-4b8df3748dc06285.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-112f7e477bda77fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-542a5c0006da637c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-63ffb347831262ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-98e4e359d97263b4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-97e24ac675ce576e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-ac6391f26391d89d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-9f45c9d45ffc43bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-36995b9cb5d98218.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-0c795b577988d63f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b4311e2f71c6735a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-eb2381074df20dba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-b01336e64eba01ae.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-7af7f21c1f2858ef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-a98ecd364586c89b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-57f0502dfbfa089b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-3467d2e1abd8a14a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-7842ca23d53ba682.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-ae9ac882912b0198.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-3a0d72e3f783bcef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-f3b39fe8358c2a97.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-5a22af3181ac2afb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-1457627bb302a22c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +0 -1
- package/apps/pwa/out/_next/static/css/e66b7f5efc8df3f3.css +0 -1
- /package/apps/pwa/out/_next/static/{zoQX5iTvXOsIvCjxCe1Ji → qMI0jnlZaBjjm4aIDYNGM}/_ssgManifest.js +0 -0
package/src/nexus/api/trace.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { getDefaultLogStore, resolveLogInstanceName, LogStore, type LogStoreRow, type LogQuery } from '../../mss/logging/log-store.js';
|
|
2
2
|
import { readLogInstances, type LogInstanceView } from '../../mss/logging/instance-registry.js';
|
|
3
|
-
import { existsSync } from 'node:fs';
|
|
3
|
+
import { existsSync, statSync } from 'node:fs';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { prodInstanceRoot } from '../../instance/resolve.js';
|
|
6
6
|
import { debug, redactSecretText, redactSecrets } from '../../debug/log.js';
|
|
7
|
-
import { activeStoreNames, parseSinceParam, type LogFabricDeps } from './log-fabric.js';
|
|
7
|
+
import { activeStoreNames, parseSinceParam, type LogFabricDeps, registeredLogStoreCount } from './log-fabric.js';
|
|
8
8
|
import { checkAuth, type MetaApiOpts } from './meta-api.js';
|
|
9
9
|
import { jsonResponse } from './json-response.js';
|
|
10
|
+
import { runLedgerDir, runLedgerPath } from '../../self-implement/run-ledger.js';
|
|
10
11
|
|
|
11
12
|
export type TraceLevel = 'L0' | 'L1' | 'L2' | 'L3';
|
|
12
13
|
export interface TraceEvent {
|
|
@@ -201,12 +202,19 @@ export function buildRunTrace(
|
|
|
201
202
|
|
|
202
203
|
export interface TraceDeps extends LogFabricDeps {
|
|
203
204
|
queryLog?: (details: { stores: string[]; level: TraceLevel; count: number; truncated: boolean; mode: 'rows' | 'runs'; runs: number }) => void;
|
|
205
|
+
ledgerStat?: (path: string) => void;
|
|
204
206
|
}
|
|
205
207
|
function storeNames(url: URL, deps: TraceDeps): string[] {
|
|
206
208
|
const names = [...new Set(url.searchParams.getAll('store').flatMap((v) => v.split(',')).map((v) => v.trim()).filter(Boolean))];
|
|
207
|
-
|
|
208
|
-
|
|
209
|
+
if (names.includes('@active')) {
|
|
210
|
+
let active: string[];
|
|
211
|
+
try { active = activeStoreNames(deps); }
|
|
212
|
+
catch { active = [resolveLogInstanceName()]; }
|
|
213
|
+
return [...new Set([...active, ...names.filter((n) => n !== '@active')])];
|
|
214
|
+
}
|
|
215
|
+
return names.length ? names : [url.searchParams.get('universe') || resolveLogInstanceName()];
|
|
209
216
|
}
|
|
217
|
+
const registeredStoreCount = (deps: TraceDeps): number | null => registeredLogStoreCount(deps);
|
|
210
218
|
function selectedStores(names: string[], deps: TraceDeps): { name: string; store: LogStore | null; remote: boolean; error?: string }[] {
|
|
211
219
|
const selfName = resolveLogInstanceName();
|
|
212
220
|
const self = names.includes(selfName) ? (deps.store ?? getDefaultLogStore)() : null;
|
|
@@ -266,6 +274,22 @@ function sortRows(a: LogStoreRow, b: LogStoreRow): number {
|
|
|
266
274
|
return b.ts_ms - a.ts_ms || b.id - a.id || a.instance.localeCompare(b.instance);
|
|
267
275
|
}
|
|
268
276
|
|
|
277
|
+
function findRunUniverse(runId: string, selected: readonly string[], deps: TraceDeps): { universe?: string; checked: number } {
|
|
278
|
+
let checked = 0;
|
|
279
|
+
const views = (deps.instances ?? readLogInstances)();
|
|
280
|
+
for (const view of [...views.filter((v) => selected.includes(v.name)), ...views.filter((v) => !selected.includes(v.name))]) {
|
|
281
|
+
checked++;
|
|
282
|
+
try {
|
|
283
|
+
(deps.ledgerStat ?? statSync)(runLedgerPath(runId, runLedgerDir(view.stateDir)));
|
|
284
|
+
return { universe: view.name, checked };
|
|
285
|
+
} catch (error) {
|
|
286
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') continue;
|
|
287
|
+
throw new Error(`unable to check run ledger for '${view.name}': ${error instanceof Error ? error.message : String(error)}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return { checked };
|
|
291
|
+
}
|
|
292
|
+
|
|
269
293
|
/** Read-only bounded log-fabric fan-in, authenticated before any store access. */
|
|
270
294
|
export function handleTrace(req: Request, opts: MetaApiOpts, deps: TraceDeps = {}): Response {
|
|
271
295
|
if (!checkAuth(req, opts)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
@@ -300,7 +324,7 @@ export function handleTrace(req: Request, opts: MetaApiOpts, deps: TraceDeps = {
|
|
|
300
324
|
const trace = buildRunTrace(runs, parsed.level as 'L0' | 'L1');
|
|
301
325
|
trace.truncated = runLimitReached || failures.length > 0;
|
|
302
326
|
try { (deps.queryLog ?? ((details) => debug.log('logs.trace', 'query', details)))({ stores: names, level: parsed.level, count: trace.events.length, truncated: trace.truncated, mode: 'runs', runs: runs.length }); } catch { /* query logging must not block reads */ }
|
|
303
|
-
return jsonResponse({ ok: true, ...trace, stores: names, ...(failures.length ? { failedStores: failures } : {}) }, 200);
|
|
327
|
+
return jsonResponse({ ok: true, ...trace, stores: names, registeredStores: registeredStoreCount(deps), ...(failures.length ? { failedStores: failures } : {}) }, 200);
|
|
304
328
|
}
|
|
305
329
|
const failures: Array<{ name: string; reason: string }> = [];
|
|
306
330
|
const rows: LogStoreRow[] = [];
|
|
@@ -321,10 +345,37 @@ export function handleTrace(req: Request, opts: MetaApiOpts, deps: TraceDeps = {
|
|
|
321
345
|
} catch (e) { failures.push({ name: entry.name, reason: e instanceof Error ? e.message : String(e) }); }
|
|
322
346
|
}
|
|
323
347
|
if (!opened) return jsonResponse({ ok: false, error: 'log-store-unavailable', failedStores: failures }, 503);
|
|
348
|
+
const runId = url.searchParams.get('runId');
|
|
349
|
+
let resolution: { universe?: string; checked: number } | undefined;
|
|
350
|
+
if ((parsed.level === 'L2' || parsed.level === 'L3') && runId && !rows.some((row) => matches(row, url)) && !requestedUniverse) {
|
|
351
|
+
// A store with no log db at all («has no log store yet» — e.g. the daemon's own tree-derived universe) cannot
|
|
352
|
+
// hold the run, so it must not block the ledger lookup. A store that exists but failed to open/read still does:
|
|
353
|
+
// «could not read» is never answered as «not there». The reply keeps `failedStores` either way.
|
|
354
|
+
const blocking = failures.filter((failure) => !/has no log store yet/.test(failure.reason));
|
|
355
|
+
if (blocking.length) return jsonResponse({ ok: false, error: 'log-store-unavailable', failedStores: failures }, 503);
|
|
356
|
+
try { resolution = findRunUniverse(runId, names, deps); }
|
|
357
|
+
catch (error) { return jsonResponse({ ok: false, error: 'run-universe-unavailable', reason: error instanceof Error ? error.message : String(error) }, 503); }
|
|
358
|
+
try { debug.log('nexus.trace', 'run-universe-resolved', { runId, universe: resolution.universe ?? 'not-found', checked: resolution.checked }); } catch { /* observation must not block reads */ }
|
|
359
|
+
if (resolution.universe && !names.includes(resolution.universe)) {
|
|
360
|
+
const name = resolution.universe;
|
|
361
|
+
names.push(name);
|
|
362
|
+
const [entry] = selectedStores([name], deps);
|
|
363
|
+
if (!entry?.store) return jsonResponse({ ok: false, error: 'log-store-unavailable', failedStores: [{ name, reason: entry?.error ?? 'log-store-unavailable' }] }, 503);
|
|
364
|
+
stores.push(entry);
|
|
365
|
+
try {
|
|
366
|
+
const query: LogQuery = { sinceMs: parsed.from, untilMs: parsed.to, limit: TRACE_LIMIT + 1 };
|
|
367
|
+
const search = url.searchParams.get('q');
|
|
368
|
+
if (search) query.grep = search;
|
|
369
|
+
const page = entry.store.queryTraceRun(runId, query);
|
|
370
|
+
if (page.length > TRACE_LIMIT) candidateLimitReached = true;
|
|
371
|
+
rows.push(...page.map((row) => ({ ...row, instance: row.instance || name })));
|
|
372
|
+
} catch (e) { return jsonResponse({ ok: false, error: 'log-store-unavailable', failedStores: [{ name, reason: e instanceof Error ? e.message : String(e) }] }, 503); }
|
|
373
|
+
}
|
|
374
|
+
}
|
|
324
375
|
const filtered = rows.filter((row) => matches(row, url)).sort(sortRows);
|
|
325
376
|
const trace = buildTrace(filtered.slice(0, parsed.limit), parsed.level, filtered.length > parsed.limit || candidateLimitReached || failures.length > 0);
|
|
326
377
|
try { (deps.queryLog ?? ((details) => debug.log('logs.trace', 'query', details)))({ stores: names, level: parsed.level, count: trace.events.length, truncated: trace.truncated, mode: 'rows', runs: trace.nodes.filter((node) => node.level === 'L1' && node.count > 0).length }); } catch { /* query logging must not block reads */ }
|
|
327
|
-
return jsonResponse({ ok: true, ...trace, stores: names, ...(failures.length ? { failedStores: failures } : {}) }, 200);
|
|
378
|
+
return jsonResponse({ ok: true, ...trace, stores: names, registeredStores: registeredStoreCount(deps), ...(resolution ? resolution.universe ? { resolvedFrom: 'ledger' as const, runUniverse: resolution.universe } : { runUniverse: 'not-found' as const, checked: resolution.checked } : {}), ...(failures.length ? { failedStores: failures } : {}) }, 200);
|
|
328
379
|
} finally { closeStores(stores, deps); }
|
|
329
380
|
}
|
|
330
381
|
|
|
@@ -28,13 +28,21 @@
|
|
|
28
28
|
// daemon exit.
|
|
29
29
|
|
|
30
30
|
import type { McpServerSpec } from '../../user-config.js';
|
|
31
|
-
import { McpClient, McpConnectionError } from '../../mcp/client.js';
|
|
31
|
+
import { McpClient, McpConnectionError, type McpToolCallResult } from '../../mcp/client.js';
|
|
32
32
|
import {
|
|
33
33
|
createMcpProxyRuntime,
|
|
34
34
|
createMcpToolAuthorizer,
|
|
35
35
|
} from '../../mcp/proxy-runtime.js';
|
|
36
36
|
import { registerToolRuntime, unregisterToolRuntime } from '../../tool-runtime/registry.js';
|
|
37
37
|
import { debug, redactSecretText } from '../../debug/log.js';
|
|
38
|
+
import { elanousStateRoot } from '../../autopilot/state-paths.js';
|
|
39
|
+
import { spawn as nodeSpawn } from 'node:child_process';
|
|
40
|
+
import { listNodeKinds } from '../../graph-kinds/registry.js';
|
|
41
|
+
import { syncInstalledPluginNodes } from '../../graph-kinds/installed-plugin-nodes.js';
|
|
42
|
+
import { listInstalledPlugins } from '../../plugins/install/plugin-install.js';
|
|
43
|
+
import { pluginEnv, credentialStatus } from '../../plugins/install/plugin-credentials.js';
|
|
44
|
+
import { existsSync, realpathSync } from 'node:fs';
|
|
45
|
+
import { resolve, sep } from 'node:path';
|
|
38
46
|
|
|
39
47
|
export interface McpClientsHandle {
|
|
40
48
|
/** Live clients (one per successfully started server). */
|
|
@@ -172,24 +180,64 @@ export async function registerMcpClients(
|
|
|
172
180
|
// breadcrumbs when a child (e.g. xcrun mcpbridge) deadlocked elanous's
|
|
173
181
|
// boot. The trace category prefix `mcp.client.boot.*` keeps these
|
|
174
182
|
// distinct from any per-call instrumentation a future PR adds.
|
|
183
|
+
const injectedValuesByServer = new Map<string, string[]>();
|
|
184
|
+
const redactPluginValues = (text: string): string => {
|
|
185
|
+
try {
|
|
186
|
+
const root = elanousStateRoot();
|
|
187
|
+
const current = [...new Set(listInstalledPlugins(root).map(item => item.name))]
|
|
188
|
+
.flatMap(name => Object.values(pluginEnv(name, root)));
|
|
189
|
+
return redactValues(text, [...current, ...injectedValuesByServer.values()].flat());
|
|
190
|
+
} catch { return '[REDACTED]'; }
|
|
191
|
+
};
|
|
175
192
|
const bootLogger = (id: string) => (event: string, data?: Record<string, unknown>) => {
|
|
176
193
|
try {
|
|
177
|
-
|
|
194
|
+
const safe = data ? Object.fromEntries(Object.entries(data).map(([key, value]) =>
|
|
195
|
+
[key, typeof value === 'string' ? redactPluginValues(value) : value])) : {};
|
|
196
|
+
debug.log('mcp.client.boot', event, { id, ...safe });
|
|
178
197
|
} catch { /* logger must never throw the boot */ }
|
|
179
198
|
};
|
|
180
199
|
const createClient =
|
|
181
200
|
opts.createClient ??
|
|
182
|
-
((spec: McpServerSpec) =>
|
|
183
|
-
spec.transport === 'http'
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
201
|
+
((spec: McpServerSpec) => {
|
|
202
|
+
if (spec.transport === 'http') return new McpClient({
|
|
203
|
+
id: spec.id, url: spec.url, logger: bootLogger(spec.id),
|
|
204
|
+
...(spec.oauthIssuer ? { oauthIssuer: spec.oauthIssuer } : {}),
|
|
205
|
+
...(spec.oauthTokenEndpoint ? { oauthTokenEndpoint: spec.oauthTokenEndpoint } : {}),
|
|
206
|
+
...(spec.bearerTokenEnv ? { bearerTokenEnv: spec.bearerTokenEnv } : {}),
|
|
207
|
+
});
|
|
208
|
+
const root = elanousStateRoot();
|
|
209
|
+
const installed = listInstalledPlugins(root);
|
|
210
|
+
const owners = [...new Set(listNodeKinds('workflow').filter(entry =>
|
|
211
|
+
!entry.core && entry.plugin && entry.run && 'mcp' in entry.run && entry.run.mcp.server === spec.id,
|
|
212
|
+
).map(entry => entry.plugin!))];
|
|
213
|
+
const commandOwners = [...new Set(installed.filter(item => {
|
|
214
|
+
const dir = realpathSync(item.path);
|
|
215
|
+
return spec.command.some(arg => {
|
|
216
|
+
if (typeof arg !== 'string' || !existsSync(arg)) return false;
|
|
217
|
+
try { return realpathSync(resolve(arg)).startsWith(`${dir}${sep}`); }
|
|
218
|
+
catch { return false; }
|
|
219
|
+
});
|
|
220
|
+
}).map(item => item.name))];
|
|
221
|
+
const selected = commandOwners.length === 1 && owners.length === 1 && owners[0] === commandOwners[0]
|
|
222
|
+
? commandOwners[0] : undefined;
|
|
223
|
+
return new McpClient({ id: spec.id, command: spec.command, logger: bootLogger(spec.id),
|
|
224
|
+
spawn: (cmd, args) => {
|
|
225
|
+
const declaredEnv = [...new Set(listInstalledPlugins(root).map(item => item.name))]
|
|
226
|
+
.flatMap(name => credentialStatus(name, root).fields.map(field => field.env));
|
|
227
|
+
const childEnv = { ...process.env };
|
|
228
|
+
for (const key of declaredEnv) delete childEnv[key];
|
|
229
|
+
injectedValuesByServer.delete(spec.id);
|
|
230
|
+
if (selected) {
|
|
231
|
+
const env = pluginEnv(selected, root);
|
|
232
|
+
const fields = credentialStatus(selected, root).fields.filter(field => field.set).map(field => field.name);
|
|
233
|
+
debug.log('plugin.credentials', 'injected', { plugin: selected, fields, count: fields.length });
|
|
234
|
+
Object.assign(childEnv, env);
|
|
235
|
+
injectedValuesByServer.set(spec.id, Object.values(env));
|
|
236
|
+
}
|
|
237
|
+
return nodeSpawn(cmd, args, { stdio: ['pipe', 'pipe', 'pipe'], env: childEnv });
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
});
|
|
193
241
|
const handshakeTimeoutMs = opts.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS;
|
|
194
242
|
const setTimeoutFn = opts.setTimeoutFn
|
|
195
243
|
?? ((cb: () => void, ms: number) => {
|
|
@@ -204,6 +252,11 @@ export async function registerMcpClients(
|
|
|
204
252
|
const perServer: Record<string, McpServerBootResult> = {};
|
|
205
253
|
let registered = 0;
|
|
206
254
|
|
|
255
|
+
if (opts.servers.some(spec => spec.transport === 'stdio') && !opts.createClient) {
|
|
256
|
+
try { syncInstalledPluginNodes(elanousStateRoot()); }
|
|
257
|
+
catch (error) { debug.log('graph.kinds', 'installed-sync-failed', { reason: String(error) }); }
|
|
258
|
+
}
|
|
259
|
+
|
|
207
260
|
for (const spec of opts.servers) {
|
|
208
261
|
const serverHandshakeTimeoutMs = spec.handshakeTimeoutMs ?? handshakeTimeoutMs;
|
|
209
262
|
if (spec.enabled === false) {
|
|
@@ -213,6 +266,20 @@ export async function registerMcpClients(
|
|
|
213
266
|
let client: McpClient | undefined;
|
|
214
267
|
try {
|
|
215
268
|
client = createClient(spec) as McpClient;
|
|
269
|
+
if (!opts.createClient && spec.transport === 'stdio') {
|
|
270
|
+
const originalCallTool = client.callTool.bind(client);
|
|
271
|
+
client.callTool = async (name: string, args: Record<string, unknown>): Promise<McpToolCallResult> => {
|
|
272
|
+
const values = injectedValuesByServer.get(spec.id) ?? [];
|
|
273
|
+
try {
|
|
274
|
+
const result = await originalCallTool(name, args);
|
|
275
|
+
return maskInjectedValues(result, values) as McpToolCallResult;
|
|
276
|
+
} catch (err) {
|
|
277
|
+
if (err instanceof Error) err.message = redactValues(err.message, values);
|
|
278
|
+
else throw new Error(redactValues(String(err), values));
|
|
279
|
+
throw err;
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
}
|
|
216
283
|
await withTimeout(`mcp.${spec.id}.start`, () => client!.start(), serverHandshakeTimeoutMs, setTimeoutFn, clearTimeoutFn);
|
|
217
284
|
const tools = await withTimeout(
|
|
218
285
|
`mcp.${spec.id}.listTools`,
|
|
@@ -287,7 +354,7 @@ export async function registerMcpClients(
|
|
|
287
354
|
}
|
|
288
355
|
const msg = err instanceof Error ? err.message : String(err);
|
|
289
356
|
const reasonClass = classifyMcpConnectFailure(err);
|
|
290
|
-
const detail = redactSecretText(msg);
|
|
357
|
+
const detail = redactPluginValues(redactSecretText(msg));
|
|
291
358
|
perServer[spec.id] = { status: 'failed', toolCount: 0, reason: detail, reasonClass };
|
|
292
359
|
try {
|
|
293
360
|
debug.log('mcp.client.boot', 'connect-failed', { id: spec.id, reason: reasonClass, detail });
|
|
@@ -303,6 +370,7 @@ export async function registerMcpClients(
|
|
|
303
370
|
if (client) {
|
|
304
371
|
try { await client.dispose(); } catch { /* best-effort */ }
|
|
305
372
|
}
|
|
373
|
+
injectedValuesByServer.delete(spec.id);
|
|
306
374
|
}
|
|
307
375
|
}
|
|
308
376
|
|
|
@@ -328,6 +396,7 @@ export async function registerMcpClients(
|
|
|
328
396
|
}),
|
|
329
397
|
),
|
|
330
398
|
);
|
|
399
|
+
injectedValuesByServer.clear();
|
|
331
400
|
},
|
|
332
401
|
};
|
|
333
402
|
}
|
|
@@ -338,3 +407,19 @@ function defaultLogger(): McpBootLogger {
|
|
|
338
407
|
warn: (line) => console.warn(line),
|
|
339
408
|
};
|
|
340
409
|
}
|
|
410
|
+
|
|
411
|
+
function redactValues(text: string, values: readonly string[]): string {
|
|
412
|
+
return values.filter(value => value.length > 0)
|
|
413
|
+
.reduce((safe, value) => safe.replaceAll(value, '[REDACTED]'), text);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/** Mask injected credential values anywhere in an MCP result — string values **and object keys**
|
|
417
|
+
* (a server can echo a secret back as a key, e.g. `structuredContent: { [secret]: "ok" }`). */
|
|
418
|
+
export function maskInjectedValues(value: unknown, values: readonly string[]): unknown {
|
|
419
|
+
if (typeof value === 'string') return redactValues(value, values);
|
|
420
|
+
if (Array.isArray(value)) return value.map(item => maskInjectedValues(item, values));
|
|
421
|
+
if (value && typeof value === 'object') {
|
|
422
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [redactValues(key, values), maskInjectedValues(item, values)]));
|
|
423
|
+
}
|
|
424
|
+
return value;
|
|
425
|
+
}
|
package/src/nexus/index.ts
CHANGED
|
@@ -79,6 +79,8 @@ import { type PtyBackend, createWebtermSpawn, resolveWebtermCwd } from './webter
|
|
|
79
79
|
import { SettingsTabController } from './config/settings-controller.js';
|
|
80
80
|
import { tryRegisterSettings } from './boot/register-settings.js';
|
|
81
81
|
import { registerMcpClients, type McpClientsHandle } from './boot/register-mcp-clients.js';
|
|
82
|
+
import { syncInstalledPluginNodes } from '../graph-kinds/installed-plugin-nodes.js';
|
|
83
|
+
import { elanousStateRoot } from '../autopilot/state-paths.js';
|
|
82
84
|
import type { McpReloadOutcome, McpReloadServerResult } from './api/admin-mcp-reload.js';
|
|
83
85
|
import { loadOrCreateSigningKey, NonceStore } from './api/edit-in-pwa-core.js';
|
|
84
86
|
import { subscribeErrorSnapshotWriter } from './supervisor/error-snapshot.js';
|
|
@@ -238,9 +240,8 @@ import {
|
|
|
238
240
|
type PwaVoiceAdapter,
|
|
239
241
|
} from '../voice/channel-adapters/pwa-voice-adapter.js';
|
|
240
242
|
import { getIntakeStore } from '../intake-plane/runtime.js';
|
|
241
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
242
243
|
import { join as joinPath } from 'node:path';
|
|
243
|
-
import {
|
|
244
|
+
import { ensureAuthToken } from '../auth/acp-token.js';
|
|
244
245
|
import type { SupervisorSpawnBackend } from './supervisor/spawn.js';
|
|
245
246
|
import type { HealthProbeBackend } from './supervisor/health.js';
|
|
246
247
|
import { gracefulExit, cleanExit } from './supervisor/graceful-exit.js';
|
|
@@ -264,8 +265,10 @@ import { createGlobalSubagentCallable } from '../agent/subagent-callable.js';
|
|
|
264
265
|
import { setAgentHopCap } from '../agent/registry.js';
|
|
265
266
|
import type { SidebarTabSurface } from '../ui/widgets/sidebar-tab-surface.js';
|
|
266
267
|
import { defaultIO, runOnboardingStep, type WizardIO } from '../onboarding.js';
|
|
268
|
+
import { packageVersion } from '../version/code-revision.js';
|
|
267
269
|
|
|
268
|
-
|
|
270
|
+
// 데몬 배너·`nexus status`·연결 라벨이 보이는 판 = 제품 판(`package.json`) — 옛 내부 상수 `0.17.0` 이 0.2.x 제품 옆에 떴다(🅞 M5 · M5c).
|
|
271
|
+
export const NEXUS_VERSION = packageVersion();
|
|
269
272
|
export const NEXUS_PHASE = 'N-5 PR χ (graceful exit + restart pending)';
|
|
270
273
|
|
|
271
274
|
type TelegramAgentRunTurnFactory = typeof import('../telegram-agent.js').makeTelegramAgentRunTurn;
|
|
@@ -397,6 +400,8 @@ export interface RunNexusOptions {
|
|
|
397
400
|
* registry stays empty so the dispatcher's webterm interception
|
|
398
401
|
* is a no-op). */
|
|
399
402
|
webtermSpawn?: (spec: { id: string; cwd?: string }) => PtyBackend;
|
|
403
|
+
/** Explicitly disable HTTP and ACP websocket token auth for this run. */
|
|
404
|
+
noAuth?: boolean;
|
|
400
405
|
/** Skip the HTTP API server boot. Default: true in tests via
|
|
401
406
|
* detachForTesting; production runs always start the server. Tests
|
|
402
407
|
* that DO want the server can pass false explicitly. */
|
|
@@ -653,7 +658,7 @@ export interface RunNexusHandle {
|
|
|
653
658
|
/** ACP WS auth gate for the NEXUS HTTP bridge.
|
|
654
659
|
* Same shape as `src/boot/acp-server.ts` websocket auth: pass
|
|
655
660
|
* `createAuthVerifier` only when a bearer token is configured;
|
|
656
|
-
*
|
|
661
|
+
* explicit no-auth opt-out omits the verifier.
|
|
657
662
|
* ⛔ `noAuth` was declared on WsBridgeOpts but the bridge NEVER read it
|
|
658
663
|
* (0 executable references) while this file assigned it — a value that
|
|
659
664
|
* flowed in and died. We removed the field rather than leave a knob that
|
|
@@ -710,7 +715,7 @@ export function buildNexusWsBridgeAuth(
|
|
|
710
715
|
bearerToken: string | undefined,
|
|
711
716
|
paths?: TokenStorePaths,
|
|
712
717
|
): Pick<NexusWsBridgeInit, 'wsAuthVerifier'> {
|
|
713
|
-
//
|
|
718
|
+
// Explicit opt-out supplies no boot token, so the bridge has no verifier.
|
|
714
719
|
if (!bearerToken) return {};
|
|
715
720
|
return {
|
|
716
721
|
// ⭐ 배열이 아니라 «공급자» — 매 핸드셰이크마다 봉투를 다시 읽는다.
|
|
@@ -764,6 +769,13 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
|
|
|
764
769
|
}
|
|
765
770
|
|
|
766
771
|
const leasePortFromLauncher = checkLauncherLeaseBoot(opts, process.env, () => resolveCurrentInstance().kind);
|
|
772
|
+
const noAuth = opts.noAuth === true || process.env.ELANOUS_NEXUS_NO_AUTH === '1';
|
|
773
|
+
// Resolve before acquiring the daemon lock or binding HTTP. A broken token
|
|
774
|
+
// store must fail closed without leaving a half-booted Nexus behind.
|
|
775
|
+
const httpBootEnabled = !(opts.skipHttpServer ?? !!opts.detachForTesting);
|
|
776
|
+
const needsToken = httpBootEnabled && !(opts.skipRuntimeApi ?? !!opts.detachForTesting) && !noAuth;
|
|
777
|
+
const bearerToken = needsToken ? ensureAuthToken().token : undefined;
|
|
778
|
+
if (needsToken && !bearerToken) throw new Error('Nexus auth token is empty');
|
|
767
779
|
|
|
768
780
|
// 설치본 전환 RFC 0b — 키는 launchd plist 가 아니라 키 캐시에서(캐시 우선 · 자식도 물려받는다). 값은 안 찍고 «이름만».
|
|
769
781
|
if (!opts.detachForTesting) {
|
|
@@ -973,6 +985,11 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
|
|
|
973
985
|
});
|
|
974
986
|
const startLevel = resolvedLevel.level;
|
|
975
987
|
dbgMod.debug.setLevel(startLevel);
|
|
988
|
+
if (httpBootEnabled && noAuth) {
|
|
989
|
+
dbgMod.debug.log('nexus.auth', 'explicit-opt-out', {
|
|
990
|
+
source: opts.noAuth === true ? '--no-auth' : 'ELANOUS_NEXUS_NO_AUTH',
|
|
991
|
+
}, { level: 'warn' });
|
|
992
|
+
}
|
|
976
993
|
dbgMod.debug.log('logging.level', 'startup-resolved', {
|
|
977
994
|
level: startLevel, source: resolvedLevel.source,
|
|
978
995
|
gateOpen: scopedMod.hotPathGateOpen(startLevel), surface: 'nexus',
|
|
@@ -1340,6 +1357,10 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
|
|
|
1340
1357
|
current: () => false,
|
|
1341
1358
|
};
|
|
1342
1359
|
|
|
1360
|
+
// Installed graph kinds do not depend on the runtime API or MCP server configuration.
|
|
1361
|
+
try { syncInstalledPluginNodes(elanousStateRoot()); }
|
|
1362
|
+
catch (error) { debug.log('graph.kinds', 'installed-sync-failed', { reason: String(error) }); }
|
|
1363
|
+
|
|
1343
1364
|
if (!skipRuntime) {
|
|
1344
1365
|
// Tool surface — flag · env · UserConfig switch · switch default.
|
|
1345
1366
|
// See resolveToolsKind() jsdoc for resolution order.
|
|
@@ -2000,11 +2021,6 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
|
|
|
2000
2021
|
let metaApiOpts: import('./api/meta-api.js').MetaApiOpts | undefined;
|
|
2001
2022
|
|
|
2002
2023
|
if (!skipRuntime && runtimeHistory) {
|
|
2003
|
-
// Bearer token — read from getElanousConfigDir()/acp-token (the
|
|
2004
|
-
// canonical store `elanous serve` uses). Loopback default is
|
|
2005
|
-
// noAuth; tests opt in by writing a token file before boot.
|
|
2006
|
-
const bearerToken = readAcpToken();
|
|
2007
|
-
|
|
2008
2024
|
// Voice REST + adapter. createVoiceRestHandler is stateless;
|
|
2009
2025
|
// the STT provider singleton (getDaemonSttProvider) returns
|
|
2010
2026
|
// null until `initDaemonSttProvider()` is called — fine, the
|
|
@@ -2081,9 +2097,8 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
|
|
|
2081
2097
|
const surfaceForOpts = toolSurface(toolsKind);
|
|
2082
2098
|
const toolCwdForOpts = resolveToolCwd({ tools: toolsKind, toolCwd: opts.toolCwd });
|
|
2083
2099
|
|
|
2084
|
-
//
|
|
2085
|
-
//
|
|
2086
|
-
// entirely for first-boot dogfood (tokenless ACP upgrade).
|
|
2100
|
+
// ACP websocket and REST share the same boot token; explicit
|
|
2101
|
+
// opt-out omits the websocket verifier as well.
|
|
2087
2102
|
wsBridgeOpts = {
|
|
2088
2103
|
...(acpHandlerRef ? {
|
|
2089
2104
|
acpOnConnection: async (conn) => {
|
|
@@ -2099,7 +2114,7 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
|
|
|
2099
2114
|
metaApiOpts = {
|
|
2100
2115
|
voiceRest,
|
|
2101
2116
|
...(bearerToken ? { bearerToken } : {}),
|
|
2102
|
-
noAuth
|
|
2117
|
+
noAuth,
|
|
2103
2118
|
history: runtimeHistory,
|
|
2104
2119
|
intakeStore: getIntakeStore(),
|
|
2105
2120
|
toolSurface: surfaceForOpts,
|
|
@@ -3048,7 +3063,7 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
|
|
|
3048
3063
|
}
|
|
3049
3064
|
runtime.httpPort = httpServer.port;
|
|
3050
3065
|
runtime.httpHost = httpServer.hostname;
|
|
3051
|
-
runtime.httpAuth = 'off';
|
|
3066
|
+
runtime.httpAuth = metaApiOpts && !metaApiOpts.noAuth ? 'on' : 'off';
|
|
3052
3067
|
// The actual bound port (not the requested port or registry entry) is the member endpoint.
|
|
3053
3068
|
try {
|
|
3054
3069
|
const configured = (getUserConfig().raw?.nexus as { primary?: unknown } | undefined)?.primary;
|
|
@@ -3475,6 +3490,9 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
|
|
|
3475
3490
|
}
|
|
3476
3491
|
|
|
3477
3492
|
printBootBanner(runtime, pwaStaticDir);
|
|
3493
|
+
if (runtime.httpPort && noAuth) {
|
|
3494
|
+
console.warn(' ⚠ NEXUS AUTH DISABLED (--no-auth / ELANOUS_NEXUS_NO_AUTH=1) — HTTP API and ACP websocket are unprotected.');
|
|
3495
|
+
}
|
|
3478
3496
|
|
|
3479
3497
|
// P.3 — first-boot PWA share wizard. Only runs in interactive TUI mode
|
|
3480
3498
|
// when the switch is still 'ask'. Headless / non-TTY boots skip it (the
|
|
@@ -3784,23 +3802,6 @@ function shouldAskPwaShareSwitch(): boolean {
|
|
|
3784
3802
|
}
|
|
3785
3803
|
}
|
|
3786
3804
|
|
|
3787
|
-
/** PR k — load the persistent ACP bearer token at
|
|
3788
|
-
* `getElanousConfigDir()/acp-token`. Mirrors the helper in
|
|
3789
|
-
* `boot/acp-server.ts` that `elanous serve` uses, so the same token
|
|
3790
|
-
* gates daemon and NEXUS HTTP. Returns undefined when the token
|
|
3791
|
-
* file is absent (loopback noAuth default — first-boot dogfood
|
|
3792
|
-
* works without configuration). Read failures are swallowed. */
|
|
3793
|
-
export function readAcpToken(): string | undefined {
|
|
3794
|
-
try {
|
|
3795
|
-
const tokenPath = joinPath(getElanousConfigDir(), 'acp-token');
|
|
3796
|
-
if (existsSync(tokenPath)) {
|
|
3797
|
-
const tok = readFileSync(tokenPath, 'utf-8').trim();
|
|
3798
|
-
return tok || undefined;
|
|
3799
|
-
}
|
|
3800
|
-
} catch { /* swallow */ }
|
|
3801
|
-
return undefined;
|
|
3802
|
-
}
|
|
3803
|
-
|
|
3804
3805
|
function printBootBanner(runtime: NexusRuntimeMeta, pwaStaticDir?: string): void {
|
|
3805
3806
|
const lines = [
|
|
3806
3807
|
'',
|
|
@@ -141,7 +141,7 @@ export function resolveChannels(
|
|
|
141
141
|
async function deliverTelegram(cfg: UserConfig, msg: OutboundMsg, deps: RouterDeps): Promise<ChannelResult> {
|
|
142
142
|
const fmt = formatForChannel('telegram', msg);
|
|
143
143
|
const send = deps.telegramSend ?? sendTelegramReport;
|
|
144
|
-
const ok = await send(cfg, fmt.text, { markdown: fmt.markdown ?? msg.markdown, fetchImpl: deps.fetchImpl });
|
|
144
|
+
const ok = await send(cfg, fmt.text, { markdown: fmt.markdown ?? msg.markdown, fetchImpl: deps.fetchImpl, kind: msg.kind });
|
|
145
145
|
return ok ? { type: 'telegram', ok: true } : { type: 'telegram', ok: false, error: 'not-configured' };
|
|
146
146
|
}
|
|
147
147
|
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// NT1 — LLM 예산 알림 «모양» (UX · 대표 2026-09-30 22:30 재요청 · OP 결정 B6 ⊕ NT1).
|
|
2
|
+
// 숫자·정책은 TC 의 B6 로더가 채운다(`내부 문서 `RFC-llm-policy-single-file-2026-09-30``) — 이 파일은 문구만 정한다.
|
|
3
|
+
//
|
|
4
|
+
// 규칙 셋(대표 가 받은 알림이 어긴 것들):
|
|
5
|
+
// ① 한 알림 = 한 결정·한 행동 — 맨 윗줄이 «무엇이 바뀌었나», 마지막 줄이 «할 일».
|
|
6
|
+
// ② 바뀐 게 없으면 침묵 — 같은 상태(key)면 send=false.
|
|
7
|
+
// ③ 모순 금지 — «한도 참»과 «크레딧으로 진행 중»을 한 알림에 같이 쓰지 않는다. 상태 하나만 고른다.
|
|
8
|
+
// ⊕ 내부 낱말 0 — «자»·«판정»·계정 내부 이름 나열을 쓰지 않는다(계정은 «계정 N개»로 센다).
|
|
9
|
+
|
|
10
|
+
export interface LlmBudgetAccount {
|
|
11
|
+
name: string;
|
|
12
|
+
/** 구독 잔량 %(0~100). 모르면 undefined. */
|
|
13
|
+
subscriptionRemainingPct?: number;
|
|
14
|
+
/** 이 계정의 크레딧 잔액. */
|
|
15
|
+
credits?: number;
|
|
16
|
+
resetInHours?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface LlmBudgetSnapshot {
|
|
20
|
+
/** ISO 시각(Asia/Seoul 로 날짜를 자른다). */
|
|
21
|
+
at: string;
|
|
22
|
+
credits: {
|
|
23
|
+
total: number;
|
|
24
|
+
usedToday: number;
|
|
25
|
+
/** 하루 목표 소진량(정책 `credits.pace`). */
|
|
26
|
+
paceTarget: number;
|
|
27
|
+
/** ISO 날짜 — 크레딧이 사라지는 날. */
|
|
28
|
+
expiresAt?: string;
|
|
29
|
+
/** 정책이 «크레딧 먼저»인가(`until` 전). */
|
|
30
|
+
useFirst?: boolean;
|
|
31
|
+
};
|
|
32
|
+
accounts: LlmBudgetAccount[];
|
|
33
|
+
selected: { account?: string; reason: 'subscription' | 'credits' | 'fallback' | 'none' };
|
|
34
|
+
fallback?: { provider: string; remainingPct?: number };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface LlmBudgetAlert {
|
|
38
|
+
send: boolean;
|
|
39
|
+
/** 상태 열쇠 — 다음 호출의 prevKey 로 넘긴다. */
|
|
40
|
+
key: string;
|
|
41
|
+
text: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const EXPIRY_WARN_DAYS = 7;
|
|
45
|
+
/** 이 시각(서울) 뒤에야 «오늘 페이스가 느리다»를 말한다 — 아침에 0 인 건 정상이다. */
|
|
46
|
+
const PACE_CHECK_HOUR = 18;
|
|
47
|
+
|
|
48
|
+
const n = (v: number) => Math.round(v).toLocaleString('ko-KR');
|
|
49
|
+
|
|
50
|
+
function seoulParts(iso: string): { date: string; hour: number } {
|
|
51
|
+
const d = new Date(iso);
|
|
52
|
+
const f = new Intl.DateTimeFormat('en-CA', { timeZone: 'Asia/Seoul', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', hourCycle: 'h23' });
|
|
53
|
+
const p = Object.fromEntries(f.formatToParts(d).map((x) => [x.type, x.value]));
|
|
54
|
+
return { date: `${p.year}-${p.month}-${p.day}`, hour: Number(p.hour) };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function daysBetween(fromIso: string, toIso: string): number {
|
|
58
|
+
const a = Date.parse(seoulParts(fromIso).date);
|
|
59
|
+
const b = Date.parse(seoulParts(toIso).date);
|
|
60
|
+
return Math.round((b - a) / 86_400_000);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function mmdd(iso: string): string {
|
|
64
|
+
const { date } = seoulParts(iso);
|
|
65
|
+
return `${date.slice(5, 7)}-${date.slice(8, 10)}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function creditsLine(s: LlmBudgetSnapshot): string {
|
|
69
|
+
const parts = [`오늘 ${n(s.credits.usedToday)} / 목표 ${n(s.credits.paceTarget)}`, `남은 크레딧 ${n(s.credits.total)}`];
|
|
70
|
+
if (s.credits.expiresAt) parts.push(`${mmdd(s.credits.expiresAt)} 만료`);
|
|
71
|
+
return parts.join(' · ');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** 지금 상태 하나를 고른다(위에서부터 먼저 맞는 것). 아무것도 아니면 침묵. */
|
|
75
|
+
export function buildLlmBudgetAlert(now: LlmBudgetSnapshot, prevKey?: string): LlmBudgetAlert {
|
|
76
|
+
const { date, hour } = seoulParts(now.at);
|
|
77
|
+
const accounts = now.accounts.length;
|
|
78
|
+
let key = 'ok';
|
|
79
|
+
let lines: string[] = [];
|
|
80
|
+
|
|
81
|
+
const expiryDays = now.credits.expiresAt ? daysBetween(now.at, now.credits.expiresAt) : undefined;
|
|
82
|
+
|
|
83
|
+
if (now.selected.reason === 'none') {
|
|
84
|
+
key = 'blocked';
|
|
85
|
+
lines = [
|
|
86
|
+
'⛔ LLM 을 쓸 곳이 없어 작업이 멈췄습니다.',
|
|
87
|
+
`구독 계정 ${accounts}개 한도 · 크레딧·대체 모델도 못 씁니다.`,
|
|
88
|
+
'할 일: 크레딧 사용을 허락하거나 대체 모델 키를 확인해 주세요.',
|
|
89
|
+
];
|
|
90
|
+
} else if (now.selected.reason === 'fallback' && now.fallback) {
|
|
91
|
+
const soonest = now.accounts.map((a) => a.resetInHours).filter((h): h is number => typeof h === 'number').sort((a, b) => a - b)[0];
|
|
92
|
+
key = `fallback:${now.fallback.provider}`;
|
|
93
|
+
lines = [
|
|
94
|
+
`⚠️ codex 를 못 써서 ${now.fallback.provider} 로 일하고 있습니다${typeof now.fallback.remainingPct === 'number' ? `(남은 ${Math.round(now.fallback.remainingPct)}%)` : ''}.`,
|
|
95
|
+
soonest !== undefined ? `codex 는 ${Math.ceil(soonest)}시간 뒤 돌아옵니다.` : 'codex 가 언제 돌아오는지 아직 모릅니다.',
|
|
96
|
+
'할 일: 없음 — 참고만 하세요.',
|
|
97
|
+
];
|
|
98
|
+
} else if (expiryDays !== undefined && expiryDays <= EXPIRY_WARN_DAYS && now.credits.total > 0) {
|
|
99
|
+
key = `expiry:${Math.max(0, expiryDays)}`;
|
|
100
|
+
lines = [
|
|
101
|
+
`💳 크레딧 ${n(now.credits.total)} 이 ${expiryDays <= 0 ? '오늘' : `${expiryDays}일 뒤`} 사라집니다.`,
|
|
102
|
+
creditsLine(now),
|
|
103
|
+
'할 일: 무거운 작업을 지금 맡기면 크레딧으로 돕니다.',
|
|
104
|
+
];
|
|
105
|
+
} else if (now.credits.useFirst && now.credits.total > 0 && hour >= PACE_CHECK_HOUR && now.credits.usedToday < now.credits.paceTarget * 0.5) {
|
|
106
|
+
// 하루 한 번만 — 날짜를 열쇠에 넣는다.
|
|
107
|
+
key = `pace-behind:${date}`;
|
|
108
|
+
lines = [
|
|
109
|
+
now.selected.reason === 'credits'
|
|
110
|
+
? '💳 크레딧을 먼저 쓰는 중인데 오늘 쓴 양이 목표의 절반이 안 됩니다.'
|
|
111
|
+
: '💳 크레딧을 먼저 쓰라는 방침인데 오늘은 구독 계정이 먼저 쓰였습니다.',
|
|
112
|
+
creditsLine(now),
|
|
113
|
+
now.selected.reason === 'credits' ? '할 일: 없음 — 맡긴 일이 적었던 날입니다.' : '할 일: 없음 — 기술 담당이 순서를 고치는 중입니다.',
|
|
114
|
+
];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const send = key !== 'ok' && key !== prevKey;
|
|
118
|
+
return { send, key, text: lines.join('\n') };
|
|
119
|
+
}
|
package/src/onboarding.ts
CHANGED
|
@@ -1193,6 +1193,27 @@ export function shouldRefuseInteractiveOnboarding(
|
|
|
1193
1193
|
return o.ctx !== 'production' || !o.stdinIsTTY;
|
|
1194
1194
|
}
|
|
1195
1195
|
|
|
1196
|
+
export class OnboardingRefusedError extends Error {
|
|
1197
|
+
readonly code = 'onboarding-refused' as const;
|
|
1198
|
+
|
|
1199
|
+
constructor(message: string, readonly reason: 'non-tty' | 'autonomous-empty-universe') {
|
|
1200
|
+
super(message);
|
|
1201
|
+
this.name = 'OnboardingRefusedError';
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
/** Handle only typed refusals; the agent setup hint applies to its non-TTY path alone. */
|
|
1206
|
+
export function handleOnboardingRefusal(error: unknown, entrance: 'agent' | 'other'): boolean {
|
|
1207
|
+
if (!(error instanceof OnboardingRefusedError) || error.code !== 'onboarding-refused') return false;
|
|
1208
|
+
if (entrance === 'agent' && error.reason === 'non-tty') {
|
|
1209
|
+
console.error('elanous agent needs a configured LLM. Run `elanous setup` in a terminal, or `elanous setup --non-interactive --config <path>` for unattended setup.');
|
|
1210
|
+
} else {
|
|
1211
|
+
console.error(error.message);
|
|
1212
|
+
}
|
|
1213
|
+
process.exitCode = 2;
|
|
1214
|
+
return true;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1196
1217
|
function refuseInteractiveOnboardingIfNeeded(opts: RunWizardOpts, path: string): void {
|
|
1197
1218
|
const ctx = getRunContext();
|
|
1198
1219
|
const stdinIsTTY = !!input.isTTY;
|
|
@@ -1208,15 +1229,17 @@ function refuseInteractiveOnboardingIfNeeded(opts: RunWizardOpts, path: string):
|
|
|
1208
1229
|
: '자율 컨텍스트에서 대화형 온보딩 요청 — 우주가 비어 있다(물질화 누락)',
|
|
1209
1230
|
}, { level: 'error' });
|
|
1210
1231
|
if (nonTTY) {
|
|
1211
|
-
throw new
|
|
1232
|
+
throw new OnboardingRefusedError(
|
|
1212
1233
|
'대화형 온보딩은 stdin TTY가 있는 자리에서만 실행할 수 있다. '
|
|
1213
1234
|
+ '무인 설정은 `elanous setup --non-interactive --config <path>`를 사용하라.',
|
|
1235
|
+
'non-tty',
|
|
1214
1236
|
);
|
|
1215
1237
|
}
|
|
1216
|
-
throw new
|
|
1238
|
+
throw new OnboardingRefusedError(
|
|
1217
1239
|
`온보딩 마법사는 자율 컨텍스트(${ctx})에서 뜰 수 없다 — config 가 비어 있다(${path}). `
|
|
1218
1240
|
+ '자식 우주가 물질화되지 않았다는 뜻이다: `elanous config sync-test --state-dir <그 우주>` '
|
|
1219
1241
|
+ '로 깔거나, 스포너가 provisionDerivedUniverse 를 부르는지 확인하라.',
|
|
1242
|
+
'autonomous-empty-universe',
|
|
1220
1243
|
);
|
|
1221
1244
|
}
|
|
1222
1245
|
|