elanous 0.2.4 → 0.2.5
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 +3 -3
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/06ef2eba-d1b99ac6d2ea663c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1413-7179a49332db5a80.js +1 -0
- package/apps/pwa/out/_next/static/chunks/150-25c277137a9638ab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1640-bc99ec23bbe29d12.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2415.ed6428ff334c7ce2.js → 1751.50b96ea9c37b49b6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2300-de059d0ab2a16630.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2684-05b0a81e75298c16.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3026-2ed7e317d9b02102.js +25 -0
- package/apps/pwa/out/_next/static/chunks/3611-77496ada38cd6358.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3717-68957c4dc9ceec90.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3929-46ba9af6f9d895cc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4015-62c5a3744698a8da.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4217-3c4317a7905f9eeb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4240-3230f230fae2d7d5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4271-e53e075ebd48593b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4377.3b125fef79576dfd.js +5 -0
- package/apps/pwa/out/_next/static/chunks/4440-18bb160d95819f4e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4853-35fdbe6bdbcdc0fc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2ea96958-e8c7e112de50b067.js → 4daa2798-1363d725c79f3593.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5007-6a3b30e23ff68a6e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5244-12b424c42070f5ff.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5791-bb82743fa6299562.js → 5348-bbf838e119661eee.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5391-8793f88b03b30161.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5611-df00af3e4e4a61bb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5890-dca2321773be829a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6123-37c9dc57ddaa7ab0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6588-b7975304a302ab91.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6604-c03961962b6273b6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7109-cf8c87e968e83a65.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7178-30f77877525a2efb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7262-871563fc9663730b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7294-f0eeebb92aa93459.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7429-881af44deed6958f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7527.943753b6ed829f09.js +1 -0
- package/apps/pwa/out/_next/static/chunks/764-a01e858525a190c0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7868-e35d5f7985ecd3f1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8320-51484af7b01fdfa5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{4323.c5dfb6f503304775.js → 8617.465ca22a319c95b3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 8918-482b30ce2a8ebf4c.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/9131-52f5fad51cceca04.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9238.2cd5f47f4bf7a2c3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6062-73e68343a9814f6a.js → 9704-3221c102623c8d73.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-70c404c7bb6d7f0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-3ffd80166b1050aa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f89e9a303515a033.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-4f403cd060442c95.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-9fa879ebf5959096.js → page-0ac5d053f0444d21.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-4bcc63e272ae8205.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-880734bd07d62882.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-4472ddf1e2458e69.js → page-72bd384898d88955.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-1018bbc9586ecae5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-de86f8c38076dd7b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-70c404c7bb6d7f0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-cd6447196d8bafe6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-e733630c47fa102b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-d1c1a8357b161875.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-60c514128e81d787.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8b637b7e325e13c1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-112f7e477bda77fd.js → page-bccce0babceae30f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-63301d11dbe73836.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-a8cf0dc472060db2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-02fba54881ccfa49.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-d5e8e90f0038f8de.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-b1ef6824f494466a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-d589bb0f233b1009.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-c9bbaebef4cda059.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-3639ce7916b92fed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-7450ae7478955653.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-70c404c7bb6d7f0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-b41ca4abfd618452.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-b333dd79ebee3fa9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-8be1227492793655.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-6e03e9371b552e0d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-911411e9fca18d03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-792170e649b37088.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-c40463466937bc91.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-509c798b80384536.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-1cac2aa21d77c417.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-9626799d9b4a2cd1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-84cd419494e16b0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8046cadf-211ca3c0e00f2b72.js → f6f3850d-7f324c6566002113.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/framework-361487d2da603917.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-607f5beb94cb2e07.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-d0222d219e29dddf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-a26980a31126decc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-edb2b37decf23e4d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{webpack-e5bea36f183b25e6.js → webpack-2bf59c720fe9542c.js} +1 -1
- package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +1 -0
- package/apps/pwa/out/_next/static/{zoQX5iTvXOsIvCjxCe1Ji → ktMqfrBbf8BMGac0jqLrb}/_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 +8 -1
- package/graphs/release/release-loop.yaml +6 -1
- package/graphs/video/README.md +1 -0
- package/graphs/video/explainer-line.yaml +58 -0
- package/package.json +3 -1
- package/plugins/job-coach/.codex-plugin/plugin.json +18 -1
- package/plugins/job-coach/README.md +12 -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/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/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/gate-node.ts +332 -44
- package/scripts/release-loop/publish-node.ts +7 -3
- package/scripts/release-loop/release-docs.ts +20 -13
- 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 +50 -0
- package/skills/explainer-video/SKILL.md +99 -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/vo.ts +37 -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/pr-manager.ts +51 -4
- 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/plugin-cli.ts +109 -11
- package/src/cli/pr-lineage.ts +2 -1
- package/src/cli/pwa-start.ts +3 -0
- package/src/cli/release-cli.ts +94 -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 +8 -2
- 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/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 +56 -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/graph-approvals.ts +125 -0
- package/src/nexus/api/http-server.ts +60 -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/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/prompt-library/universal-preamble.ts +8 -2
- package/src/release-loop/checklist.ts +191 -0
- package/src/release-loop/release-note.ts +6 -3
- 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/seams.ts +4 -2
- 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-command-job.ts +27 -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/tui-client/acp-transport-ws-client.ts +72 -5
- package/src/tui.ts +3 -2
- package/src/user-config.ts +28 -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/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/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/3403.4bc97b4066945a94.js +0 -5
- 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/8142-011e1f474efb8b33.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/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/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/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 → ktMqfrBbf8BMGac0jqLrb}/_ssgManifest.js +0 -0
|
@@ -3,15 +3,16 @@
|
|
|
3
3
|
// 사람은 무엇을 해야 할지 몰랐다(🅢 09-28 · 대표 승인 수리). Elanous 는 OAuth 를 쓰지 않는다 — 고정 Bearer 토큰이다.
|
|
4
4
|
// ⇒ 두 자리에서 «할 일»을 말한다: ① 401 의 `WWW-Authenticate` ② `/register` 의 RFC 7591 오류 본문(Claude Code 가 그대로 보여 준다).
|
|
5
5
|
|
|
6
|
-
/** 토큰 «값»은 절대 싣지 않는다 — 파일 경로와 명령만. */
|
|
7
|
-
export
|
|
8
|
-
'Elanous 는 OAuth 를 쓰지 않는다(고정 Bearer 토큰). 권장: stdio 로 등록 — `claude mcp add elanous -- elanous mcp serve`. '
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
/** 토큰 «값»은 절대 싣지 않는다 — 파일 경로와 명령만. 주소는 포트를 짐작하지 않고 «요청이 닿은 origin»을 쓴다(대표 09-28 통합관제). */
|
|
7
|
+
export function mcpAuthHint(origin: string): string {
|
|
8
|
+
return 'Elanous 는 OAuth 를 쓰지 않는다(고정 Bearer 토큰). 권장: stdio 로 등록 — `claude mcp add elanous -- elanous mcp serve`. '
|
|
9
|
+
+ 'HTTP 가 필요하면 ~/.elanous/acp-token 의 값을 Authorization 헤더로: '
|
|
10
|
+
+ `\`claude mcp add --transport http elanous ${origin}/v1/mcp --header "Authorization: Bearer <acp-token>"\``;
|
|
11
|
+
}
|
|
11
12
|
|
|
12
13
|
/** `/v1/mcp` 401 — 무엇을 해야 하는지 헤더로도 말한다(WWW-Authenticate 의 error_description 은 ASCII 만). */
|
|
13
|
-
export function mcpUnauthorizedResponse(): Response {
|
|
14
|
-
return new Response(JSON.stringify({ error: 'unauthorized', hint:
|
|
14
|
+
export function mcpUnauthorizedResponse(origin: string): Response {
|
|
15
|
+
return new Response(JSON.stringify({ error: 'unauthorized', hint: mcpAuthHint(origin) }), {
|
|
15
16
|
status: 401,
|
|
16
17
|
headers: {
|
|
17
18
|
'content-type': 'application/json; charset=utf-8',
|
|
@@ -22,8 +23,8 @@ export function mcpUnauthorizedResponse(): Response {
|
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
/** OAuth 동적 클라이언트 등록 거절 — RFC 7591 §3.2.2 오류 형식(Claude Code 가 본문을 사람에게 보여 준다). */
|
|
25
|
-
export function oauthRegistrationRejected(): Response {
|
|
26
|
-
return new Response(JSON.stringify({ error: 'invalid_client_metadata', error_description:
|
|
26
|
+
export function oauthRegistrationRejected(origin: string): Response {
|
|
27
|
+
return new Response(JSON.stringify({ error: 'invalid_client_metadata', error_description: mcpAuthHint(origin) }), {
|
|
27
28
|
status: 400,
|
|
28
29
|
headers: { 'content-type': 'application/json; charset=utf-8', 'access-control-allow-origin': '*' },
|
|
29
30
|
});
|
|
@@ -98,13 +98,14 @@ function asItem(pr: RawPr) {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
// A page load fires the list twice (the daemon client settles) and polls cards — identical reads share one gh call.
|
|
101
|
+
// Failures are shared too: during a GitHub rate-limit window a retry per request only deepens the limit (GIT-S83).
|
|
101
102
|
const readCache = new Map<string, { at: number; value: Promise<GhResult> }>();
|
|
102
103
|
export function clearApprovalsReadCache(): void { readCache.clear(); }
|
|
103
104
|
function cachedRead(args: string[], ttlMs: number, now: number, call: () => Promise<GhResult>): Promise<GhResult> {
|
|
104
105
|
const key = args.join('\u0000');
|
|
105
106
|
const hit = readCache.get(key);
|
|
106
107
|
if (hit && now - hit.at < ttlMs) return hit.value;
|
|
107
|
-
const value = call()
|
|
108
|
+
const value = call();
|
|
108
109
|
readCache.set(key, { at: now, value });
|
|
109
110
|
return value;
|
|
110
111
|
}
|
|
@@ -254,6 +254,10 @@ function decideAuth(req: Request, opts: MetaApiOpts): { ok: boolean; reason: Aut
|
|
|
254
254
|
if (isSameOriginRequest(req, peerAddress)) return { ok: true, reason: 'same-origin' };
|
|
255
255
|
const peerRejected = req.headers.get('sec-fetch-site') === 'same-origin'
|
|
256
256
|
&& !isTrustedSameOriginPeer(peerAddress);
|
|
257
|
+
return decideBearer(req, opts, peerRejected);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function decideBearer(req: Request, opts: MetaApiOpts, peerRejected = false): { ok: boolean; reason: AuthReason } {
|
|
257
261
|
const token = opts.bearerToken;
|
|
258
262
|
if (!token) return { ok: false, reason: peerRejected ? 'untrusted-same-origin-peer' : 'no-bearer-configured' };
|
|
259
263
|
const auth = req.headers.get('authorization') ?? '';
|
|
@@ -273,6 +277,16 @@ function decideAuth(req: Request, opts: MetaApiOpts): { ok: boolean; reason: Aut
|
|
|
273
277
|
: { ok: false, reason: 'bearer-mismatch' };
|
|
274
278
|
}
|
|
275
279
|
|
|
280
|
+
/** Bearer-only credential check that ignores the same-origin shortcut. `checkAuth` returns
|
|
281
|
+
* `same-origin` before it ever looks at the Authorization header, so a paired PWA that sends its
|
|
282
|
+
* token is still reported as same-origin — write paths that must not trust same-origin alone
|
|
283
|
+
* (graph approvals) ask this instead. Returns the credential kind, or undefined if none matched. */
|
|
284
|
+
export function bearerCredential(req: Request, opts: MetaApiOpts): 'noauth' | 'bearer-match' | 'temp-token' | undefined {
|
|
285
|
+
if (opts.noAuth) return 'noauth';
|
|
286
|
+
const decision = decideBearer(req, opts);
|
|
287
|
+
return decision.ok && (decision.reason === 'bearer-match' || decision.reason === 'temp-token') ? decision.reason : undefined;
|
|
288
|
+
}
|
|
289
|
+
|
|
276
290
|
// Exported so sibling NEXUS API modules (e.g. T5.G `tasks-scheduler.ts`)
|
|
277
291
|
// can reuse the exact same gate — same-origin + constant-time bearer
|
|
278
292
|
// check — without duplicating security-sensitive code.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { debug } from '../../debug/log.js';
|
|
2
|
+
import { credentialStatus, setPluginCredentials } from '../../plugins/install/plugin-credentials.js';
|
|
3
|
+
import { checkAuth, type MetaApiOpts } from './meta-api.js';
|
|
4
|
+
import { jsonResponse } from './json-response.js';
|
|
5
|
+
|
|
6
|
+
const NAME = /^[a-z0-9][a-z0-9-]{1,39}$/;
|
|
7
|
+
|
|
8
|
+
function failure(error: unknown): Response {
|
|
9
|
+
if (error instanceof Error && error.message.startsWith('plugin not installed:')) return jsonResponse({ error: 'not_found' }, 404);
|
|
10
|
+
if (error instanceof Error && (error.message === 'invalid plugin name' || error.message === 'invalid plugin credential field'
|
|
11
|
+
|| error.message === 'invalid plugin credential fields')) {
|
|
12
|
+
return jsonResponse({ error: 'bad_request' }, 400);
|
|
13
|
+
}
|
|
14
|
+
return jsonResponse({ error: 'credential_error' }, 500);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function handlePluginsCredentialsGet(req: Request, name: string, opts: MetaApiOpts, root?: string): Response {
|
|
18
|
+
if (!checkAuth(req, opts)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
19
|
+
if (!NAME.test(name)) return jsonResponse({ error: 'bad_request' }, 400);
|
|
20
|
+
try { return jsonResponse(credentialStatus(name, root), 200); }
|
|
21
|
+
catch (error) { return failure(error); }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** `reload` re-spawns the daemon's MCP clients so a running plugin server picks up the new values
|
|
25
|
+
* (the spawn reads `pluginEnv` fresh). A failed reload never fails the write — the reply says so. */
|
|
26
|
+
export async function handlePluginsCredentialsPut(req: Request, name: string, opts: MetaApiOpts, root?: string, reload?: () => Promise<unknown>): Promise<Response> {
|
|
27
|
+
if (!checkAuth(req, opts)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
28
|
+
if (!NAME.test(name)) return jsonResponse({ error: 'bad_request' }, 400);
|
|
29
|
+
let body: unknown;
|
|
30
|
+
try { body = await req.json(); }
|
|
31
|
+
catch { return jsonResponse({ error: 'bad_request' }, 400); }
|
|
32
|
+
const fields = body && typeof body === 'object' && !Array.isArray(body) ? (body as Record<string, unknown>).fields : undefined;
|
|
33
|
+
if (!fields || typeof fields !== 'object' || Array.isArray(fields)
|
|
34
|
+
|| Object.values(fields).some(value => typeof value !== 'string' && value !== null)) {
|
|
35
|
+
return jsonResponse({ error: 'bad_request' }, 400);
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const result = setPluginCredentials(name, fields as Record<string, string | null>, root);
|
|
39
|
+
debug.log('plugin.credentials', 'set', { plugin: name, fields: Object.keys(fields), count: Object.keys(fields).length });
|
|
40
|
+
let reloaded = false;
|
|
41
|
+
if (reload) {
|
|
42
|
+
try { await reload(); reloaded = true; }
|
|
43
|
+
catch { debug.log('plugin.credentials', 'reload-failed', { plugin: name }, { level: 'warn' }); }
|
|
44
|
+
}
|
|
45
|
+
return jsonResponse({ ...result, reloaded }, 200);
|
|
46
|
+
} catch (error) { return failure(error); }
|
|
47
|
+
}
|
|
@@ -4,7 +4,8 @@ import { elanousStateRoot } from '../../autopilot/state-paths.js';
|
|
|
4
4
|
import { debug } from '../../debug/log.js';
|
|
5
5
|
import { OFFICIAL_INDEX_KEYS } from '../../market/official-keys.js';
|
|
6
6
|
import { verifyIndex, type MarketplaceIndex } from '../../market/signed-index.js';
|
|
7
|
-
import { listInstalledPlugins } from '../../plugins/install/plugin-install.js';
|
|
7
|
+
import { installPlugin, listInstalledPlugins, removePlugin, type InstallEvent } from '../../plugins/install/plugin-install.js';
|
|
8
|
+
import { updateMarket, type MarketFetchOptions } from '../../plugins/install/market-fetch.js';
|
|
8
9
|
import { userConfigPath } from '../../user-config.js';
|
|
9
10
|
import { jsonResponse } from './json-response.js';
|
|
10
11
|
import { checkAuth, type MetaApiOpts } from './meta-api.js';
|
|
@@ -66,7 +67,7 @@ export function readMarketIndex(root = elanousStateRoot(), trustedKeys: Readonly
|
|
|
66
67
|
const dir = join(base, entry.name);
|
|
67
68
|
const indexPath = [join(dir, 'marketplace.json'), join(dir, '.agents', 'plugins', 'marketplace.json')].find(existsSync);
|
|
68
69
|
if (!indexPath) {
|
|
69
|
-
markets.push({ name: entry.name, signature: 'malformed', detail: '
|
|
70
|
+
markets.push({ name: entry.name, signature: 'malformed', detail: '마켓 인덱스를 찾지 못했습니다.', plugins: [] });
|
|
70
71
|
continue;
|
|
71
72
|
}
|
|
72
73
|
let signature: MarketSignature = 'missing';
|
|
@@ -93,7 +94,7 @@ export function readMarketIndex(root = elanousStateRoot(), trustedKeys: Readonly
|
|
|
93
94
|
signature = 'malformed';
|
|
94
95
|
detail = 'marketplace.json: invalid market index';
|
|
95
96
|
}
|
|
96
|
-
markets.push({ name: entry.name, signature, ...(detail ? { detail } : {}), plugins });
|
|
97
|
+
markets.push({ name: entry.name, signature, ...(detail ? { detail: '마켓 인덱스를 확인하지 못했습니다.' } : {}), plugins });
|
|
97
98
|
}
|
|
98
99
|
markets.sort((a, b) => a.name.localeCompare(b.name));
|
|
99
100
|
debug.log('nexus.plugins-market', 'index', { markets: markets.length, ok: markets.filter(m => m.signature === 'ok').length, notOk: markets.filter(m => m.signature !== 'ok').length });
|
|
@@ -123,3 +124,86 @@ export function handlePluginsGet(req: Request, opts: MetaApiOpts, root?: string)
|
|
|
123
124
|
if (!checkAuth(req, opts)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
124
125
|
return jsonResponse(listInstalledPlugins(root));
|
|
125
126
|
}
|
|
127
|
+
|
|
128
|
+
const MARKET_NAME = /^[a-z0-9][a-z0-9-]{1,39}$/;
|
|
129
|
+
const MARKET_SPEC = /^[a-z0-9][a-z0-9-]{1,39}@[a-z0-9][a-z0-9-]{1,39}$/;
|
|
130
|
+
|
|
131
|
+
/** 실패 원인 한 줄 — 사용자가 «왜» 를 볼 수 있게(2026-09-29 실물: «설치 중 오류가 발생했습니다» 만 보여 아티팩트 누락을 못 봤다). 홈 경로는 `~` 로 · 200자 · 한 줄. */
|
|
132
|
+
export function safeDetail(error: unknown): string | undefined {
|
|
133
|
+
const message = error instanceof Error ? error.message : typeof error === 'string' ? error : undefined;
|
|
134
|
+
if (!message) return undefined;
|
|
135
|
+
return message.replace(/\/(?:Users|home)\/[^/\s]+/g, '~').replace(/\s+/g, ' ').trim().slice(0, 200) || undefined;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function safeReason(error: unknown): string {
|
|
139
|
+
const reason = error && typeof error === 'object' && 'reason' in error ? error.reason : undefined;
|
|
140
|
+
return typeof reason === 'string' && ['signature', 'scan', 'consent-denied', 'credentials', 'conflict', 'io'].includes(reason)
|
|
141
|
+
? reason : 'io';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export async function handlePluginsMarketRefresh(req: Request, name: string, opts: MetaApiOpts, fetchOptions: MarketFetchOptions = {}): Promise<Response> {
|
|
145
|
+
if (!checkAuth(req, opts)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
146
|
+
if (!MARKET_NAME.test(name)) return jsonResponse({ ok: false, reason: 'invalid-market' }, 400);
|
|
147
|
+
try {
|
|
148
|
+
const result = await updateMarket(name, fetchOptions);
|
|
149
|
+
const plugins = projectPlugins(result.index, name);
|
|
150
|
+
debug.log('nexus.plugins-market', 'refresh', { name, ok: true, count: plugins.length });
|
|
151
|
+
return jsonResponse({ ok: true, plugins });
|
|
152
|
+
} catch (error) {
|
|
153
|
+
const reason = safeReason(error);
|
|
154
|
+
debug.log('nexus.plugins-market', 'refresh', { name, ok: false, reason });
|
|
155
|
+
return jsonResponse({ ok: false, reason });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export async function handlePluginsInstall(req: Request, opts: MetaApiOpts, root?: string, configPath?: string): Promise<Response> {
|
|
160
|
+
if (!checkAuth(req, opts)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
161
|
+
let body: unknown;
|
|
162
|
+
try { body = await req.json(); } catch { return jsonResponse({ error: 'bad_request' }, 400); }
|
|
163
|
+
const input = body as { spec?: unknown; acceptedCapabilities?: unknown } | null;
|
|
164
|
+
if (!input || typeof input.spec !== 'string' || !MARKET_SPEC.test(input.spec) ||
|
|
165
|
+
!Array.isArray(input.acceptedCapabilities) || !input.acceptedCapabilities.every(cap => typeof cap === 'string')) {
|
|
166
|
+
return jsonResponse({ error: 'bad_request' }, 400);
|
|
167
|
+
}
|
|
168
|
+
const spec = input.spec;
|
|
169
|
+
const accepted = new Set(input.acceptedCapabilities as string[]);
|
|
170
|
+
const trustedKeys = configuredTrustedKeys(configPath);
|
|
171
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
172
|
+
async start(controller) {
|
|
173
|
+
const encoder = new TextEncoder();
|
|
174
|
+
const send = (value: Record<string, unknown>) => controller.enqueue(encoder.encode(`${JSON.stringify(value)}\n`));
|
|
175
|
+
const onEvent = (event: InstallEvent) => {
|
|
176
|
+
if (event.event === 'consent') send({ event: 'consent', capabilities: event.capabilities, required: event.required });
|
|
177
|
+
else if (event.event === 'credentials') send({ event: 'credentials', required: event.connectors.some(c => c.fields.length > 0) });
|
|
178
|
+
else send({ event: event.event });
|
|
179
|
+
};
|
|
180
|
+
try {
|
|
181
|
+
await installPlugin(spec, {
|
|
182
|
+
...(root ? { root } : {}), trustedKeys: [...OFFICIAL_INDEX_KEYS, ...trustedKeys],
|
|
183
|
+
onEvent, consent: capabilities => capabilities.every(cap => accepted.has(cap)),
|
|
184
|
+
});
|
|
185
|
+
debug.log('nexus.plugins-market', 'install', { spec, ok: true });
|
|
186
|
+
} catch (error) {
|
|
187
|
+
const reason = safeReason(error);
|
|
188
|
+
const detail = safeDetail(error);
|
|
189
|
+
debug.log('nexus.plugins-market', 'install', { spec, ok: false, reason, ...(detail ? { detail } : {}) });
|
|
190
|
+
send({ event: 'failed', reason, ...(detail ? { detail } : {}) });
|
|
191
|
+
} finally { controller.close(); }
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
return new Response(stream, { headers: { 'content-type': 'application/x-ndjson; charset=utf-8', 'cache-control': 'no-store' } });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function handlePluginsRemove(req: Request, name: string, opts: MetaApiOpts, root?: string): Response {
|
|
198
|
+
if (!checkAuth(req, opts)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
199
|
+
if (!MARKET_NAME.test(name)) return jsonResponse({ error: 'bad_request' }, 400);
|
|
200
|
+
try {
|
|
201
|
+
const removed = removePlugin(name, root);
|
|
202
|
+
debug.log('nexus.plugins-market', 'remove', { name, removed });
|
|
203
|
+
return jsonResponse({ removed });
|
|
204
|
+
} catch (error) {
|
|
205
|
+
const reason = safeReason(error);
|
|
206
|
+
debug.log('nexus.plugins-market', 'remove', { name, reason });
|
|
207
|
+
return jsonResponse({ error: reason }, 500);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -4,10 +4,14 @@
|
|
|
4
4
|
* 응답 키는 `key`·`expires_at` 둘뿐. refresh·계정 id·파일 경로는 응답·로그·에러에 싣지 않는다.
|
|
5
5
|
*/
|
|
6
6
|
import { debug } from '../../debug/log.js';
|
|
7
|
-
import {
|
|
7
|
+
import { githubInstallationCredential } from '../../auth/github-app-token.js';
|
|
8
|
+
import { verifyGroundingToken, type GroundingTokenDeps, type GroundingTokenScope } from '../../grounding/token.js';
|
|
8
9
|
import { isGrokSubscriptionExpiring, readGrokSubscriptionAccess, refreshGrokSubscriptionToken } from '../../grok/credential.js';
|
|
9
10
|
|
|
10
11
|
export const POD_CREDENTIAL_GROK_PATH = '/v1/pod/credential/grok';
|
|
12
|
+
export const POD_CREDENTIAL_GITHUB_PATH = '/v1/pod/credential/github';
|
|
13
|
+
export const POD_GITHUB_CREDENTIAL_TOKEN_ENV = 'ELANOUS_POD_GITHUB_CREDENTIAL_TOKEN';
|
|
14
|
+
export const POD_GITHUB_CREDENTIAL_URL_ENV = 'ELANOUS_POD_GITHUB_CREDENTIAL_URL';
|
|
11
15
|
/** 만료까지 이 안이면 기존 호스트 갱신(`refreshGrokSubscriptionToken`)을 한 번 부른다. */
|
|
12
16
|
export const POD_CREDENTIAL_HOST_REFRESH_WINDOW_MS = 10 * 60 * 1000;
|
|
13
17
|
/** runId 당 분당 1회. */
|
|
@@ -27,34 +31,45 @@ export interface PodCredentialDeps {
|
|
|
27
31
|
readonly loadAccess?: (home: string | undefined, env: NodeJS.ProcessEnv) => { key: string; expiresAt: string } | null;
|
|
28
32
|
}
|
|
29
33
|
|
|
34
|
+
export interface PodGithubCredentialDeps {
|
|
35
|
+
readonly token?: GroundingTokenDeps;
|
|
36
|
+
readonly now?: () => number;
|
|
37
|
+
readonly mintInstallation?: (repository: string) => { token: string; expires_at: string } | null;
|
|
38
|
+
readonly installationRepositories?: (token: string) => Promise<readonly string[] | null>;
|
|
39
|
+
}
|
|
40
|
+
|
|
30
41
|
interface HitWindow {
|
|
31
42
|
readonly stamps: number[];
|
|
32
43
|
}
|
|
33
44
|
|
|
34
45
|
const hitsByRun = new Map<string, HitWindow>();
|
|
35
46
|
const globalHits: number[] = [];
|
|
47
|
+
const githubHitsByRun = new Map<string, HitWindow>();
|
|
48
|
+
const githubGlobalHits: number[] = [];
|
|
36
49
|
|
|
37
50
|
export function resetPodCredentialRateForTesting(): void {
|
|
38
51
|
hitsByRun.clear();
|
|
39
52
|
globalHits.length = 0;
|
|
53
|
+
githubHitsByRun.clear();
|
|
54
|
+
githubGlobalHits.length = 0;
|
|
40
55
|
}
|
|
41
56
|
|
|
42
57
|
function prune(stamps: number[], now: number): number[] {
|
|
43
58
|
return stamps.filter((t) => now - t < 60_000);
|
|
44
59
|
}
|
|
45
60
|
|
|
46
|
-
function takeRate(runId: string, now: number): boolean {
|
|
47
|
-
const prev = prune(
|
|
48
|
-
const global = prune(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (prev.length >= POD_CREDENTIAL_PER_RUN_PER_MIN ||
|
|
52
|
-
|
|
61
|
+
function takeRate(runId: string, now: number, byRun = hitsByRun, all = globalHits): boolean {
|
|
62
|
+
const prev = prune(byRun.get(runId)?.stamps ?? [], now);
|
|
63
|
+
const global = prune(all, now);
|
|
64
|
+
all.length = 0;
|
|
65
|
+
all.push(...global);
|
|
66
|
+
if (prev.length >= POD_CREDENTIAL_PER_RUN_PER_MIN || all.length >= POD_CREDENTIAL_GLOBAL_PER_MIN) {
|
|
67
|
+
byRun.set(runId, { stamps: prev });
|
|
53
68
|
return false;
|
|
54
69
|
}
|
|
55
70
|
prev.push(now);
|
|
56
|
-
|
|
57
|
-
|
|
71
|
+
all.push(now);
|
|
72
|
+
byRun.set(runId, { stamps: prev });
|
|
58
73
|
return true;
|
|
59
74
|
}
|
|
60
75
|
|
|
@@ -109,25 +124,64 @@ export async function handlePodGrokCredential(req: Request, deps: PodCredentialD
|
|
|
109
124
|
return Response.json({ key: loaded.key, expires_at: expiresAt });
|
|
110
125
|
}
|
|
111
126
|
|
|
127
|
+
export async function installationRepositories(token: string): Promise<readonly string[] | null> {
|
|
128
|
+
const response = await fetch('https://api.github.com/installation/repositories?per_page=100', {
|
|
129
|
+
headers: { authorization: `Bearer ${token}`, accept: 'application/vnd.github+json', 'x-github-api-version': '2022-11-28' },
|
|
130
|
+
signal: AbortSignal.timeout(10_000),
|
|
131
|
+
});
|
|
132
|
+
if (!response.ok) return null;
|
|
133
|
+
const data: unknown = await response.json();
|
|
134
|
+
if (!data || typeof data !== 'object' || Array.isArray(data) || !Array.isArray((data as { repositories?: unknown }).repositories)) return null;
|
|
135
|
+
const body = data as { total_count?: unknown; repositories: unknown[] };
|
|
136
|
+
if (body.repositories.length !== 1 || body.total_count !== 1) return null;
|
|
137
|
+
const name = body.repositories[0] && typeof body.repositories[0] === 'object' && !Array.isArray(body.repositories[0]) ? (body.repositories[0] as { full_name?: unknown }).full_name : undefined;
|
|
138
|
+
return typeof name === 'string' ? [name] : null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** No repository is accepted from the request body: the host signed its owner/name into the run token. */
|
|
142
|
+
export async function handlePodGithubCredential(req: Request, deps: PodGithubCredentialDeps = {}): Promise<Response> {
|
|
143
|
+
const gate = await authenticatePodCredential(req, deps, 'gh-credential');
|
|
144
|
+
if (!gate.ok) return gate.response;
|
|
145
|
+
if (!gate.repository || !/^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(gate.repository)) return Response.json({ error: 'unauthorized' }, { status: 401 });
|
|
146
|
+
if (!takeRate(gate.runId, (deps.now ?? Date.now)(), githubHitsByRun, githubGlobalHits)) return reasonResponse(429, 'rate');
|
|
147
|
+
|
|
148
|
+
let issued: { token: string; expires_at: string } | null;
|
|
149
|
+
try {
|
|
150
|
+
issued = (deps.mintInstallation ?? ((repository) => githubInstallationCredential({ scope: { repository: repository.split('/')[1]! } })))(gate.repository);
|
|
151
|
+
} catch {
|
|
152
|
+
return reasonResponse(503, 'unavailable');
|
|
153
|
+
}
|
|
154
|
+
if (!issued?.token || typeof issued.expires_at !== 'string' || !Number.isFinite(Date.parse(issued.expires_at)) || Date.parse(issued.expires_at) <= (deps.now ?? Date.now)()) return reasonResponse(503, 'unavailable');
|
|
155
|
+
try {
|
|
156
|
+
const available = await (deps.installationRepositories ?? installationRepositories)(issued.token);
|
|
157
|
+
if (available?.length !== 1 || available[0]?.toLowerCase() !== gate.repository.toLowerCase()) return reasonResponse(503, 'unavailable');
|
|
158
|
+
} catch {
|
|
159
|
+
return reasonResponse(503, 'unavailable');
|
|
160
|
+
}
|
|
161
|
+
debug.log('pod.credential', 'github-issued', { runId: gate.runId, job: gate.job, expiresAt: issued.expires_at });
|
|
162
|
+
return Response.json({ token: issued.token, expires_at: issued.expires_at });
|
|
163
|
+
}
|
|
164
|
+
|
|
112
165
|
/**
|
|
113
166
|
* 쓰기 관문. 본문을 읽지 않는다. 토큰이 없거나 서명이 깨지면 401,
|
|
114
167
|
* scope·만료·회수는 403 과 사유만.
|
|
115
168
|
*/
|
|
116
169
|
export async function authenticatePodCredential(
|
|
117
170
|
req: Request,
|
|
118
|
-
deps: PodCredentialDeps = {},
|
|
171
|
+
deps: Pick<PodCredentialDeps, 'token'> = {},
|
|
172
|
+
expectedScope: GroundingTokenScope = 'llm-credential',
|
|
119
173
|
): Promise<
|
|
120
|
-
| { readonly ok: true; readonly runId: string; readonly job: string; readonly exp: number }
|
|
174
|
+
| { readonly ok: true; readonly runId: string; readonly job: string; readonly repository?: string; readonly exp: number }
|
|
121
175
|
| { readonly ok: false; readonly response: Response }
|
|
122
176
|
> {
|
|
123
177
|
const token = bearerOf(req);
|
|
124
178
|
if (!token) return { ok: false, response: Response.json({ error: 'unauthorized' }, { status: 401 }) };
|
|
125
|
-
const verified = await verifyGroundingToken(token, { ...(deps.token ?? {}), expectedScope
|
|
179
|
+
const verified = await verifyGroundingToken(token, { ...(deps.token ?? {}), expectedScope });
|
|
126
180
|
if (!verified.ok) {
|
|
127
181
|
if (verified.reason === 'expired' || verified.reason === 'revoked' || verified.reason === 'scope') {
|
|
128
182
|
return { ok: false, response: reasonResponse(403, verified.reason) };
|
|
129
183
|
}
|
|
130
184
|
return { ok: false, response: Response.json({ error: 'unauthorized' }, { status: 401 }) };
|
|
131
185
|
}
|
|
132
|
-
return { ok: true, runId: verified.runId, job: verified.job, exp: verified.exp };
|
|
186
|
+
return { ok: true, runId: verified.runId, job: verified.job, ...(verified.repository ? { repository: verified.repository } : {}), exp: verified.exp };
|
|
133
187
|
}
|
|
@@ -49,6 +49,13 @@ export const PUBLIC_ROUTES: Array<PublicRoute> = [
|
|
|
49
49
|
selfVerified: true,
|
|
50
50
|
why: 'Pod grok access relay (#20789); Pods hold no nexus bearer — pod-credential-api.ts verifies its own llm-credential token (verifyGroundingToken expectedScope) before reading the body',
|
|
51
51
|
},
|
|
52
|
+
{
|
|
53
|
+
method: 'POST',
|
|
54
|
+
match: 'exact',
|
|
55
|
+
path: '/v1/pod/credential/github',
|
|
56
|
+
selfVerified: true,
|
|
57
|
+
why: 'Pod GitHub App credential relay verifies its own run-scoped gh-credential token before minting a repository-scoped installation token',
|
|
58
|
+
},
|
|
52
59
|
{
|
|
53
60
|
method: 'GET',
|
|
54
61
|
match: 'prefix',
|
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
|
|