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
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import type { TaskCard } from '../task-cards/card-store.js';
|
|
2
|
+
import { decideBudget, readBudgetInputsLive, type BudgetDecision, type BudgetInputs } from '../self-implement/budget-gate.js';
|
|
3
|
+
import { resolveHarnessSubstrate, type ResolvedHarnessSubstrate } from '../harness/harness-substrate-default.js';
|
|
4
|
+
import { podMemoryLimitFor } from '../task-orchestrator/surfaces/self-implement-pod.js';
|
|
5
|
+
import { recallMemoryContext } from '../agent-substrate/execution/memory-context.js';
|
|
6
|
+
|
|
7
|
+
export interface GateResult<T> {
|
|
8
|
+
decision: T;
|
|
9
|
+
explanation: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface BudgetGateDeps {
|
|
13
|
+
readInputs: () => Promise<BudgetInputs>;
|
|
14
|
+
decide: (inputs: BudgetInputs) => BudgetDecision;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Observe the existing quota decision; callers record it, not apply it to the launch. */
|
|
18
|
+
export async function budgetGate(
|
|
19
|
+
deps: BudgetGateDeps = { readInputs: readBudgetInputsLive, decide: decideBudget },
|
|
20
|
+
): Promise<GateResult<BudgetDecision>> {
|
|
21
|
+
const decision = deps.decide(await deps.readInputs());
|
|
22
|
+
return { decision, explanation: decision.reasons.join(' · ') || `budget: ${decision.action}` };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface PlacementGateInput {
|
|
26
|
+
goal: string;
|
|
27
|
+
/** A local-only capability or file is an observation, not a reason to probe a remote pool. */
|
|
28
|
+
needsBrowser?: boolean;
|
|
29
|
+
needsVideo?: boolean;
|
|
30
|
+
needsAppSigning?: boolean;
|
|
31
|
+
localFiles?: readonly string[];
|
|
32
|
+
flag?: { substrate?: 'pod' | 'local'; podPool?: string };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface PlacementGateDeps {
|
|
36
|
+
resolveSubstrate: (input: { flag?: { substrate?: 'pod' | 'local'; podPool?: string } }) => ResolvedHarnessSubstrate;
|
|
37
|
+
memoryLimit: (goal: string) => { limit: string; tier: string; source: string };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type PlacementDecision = (ResolvedHarnessSubstrate | {
|
|
41
|
+
substrate: 'unknown';
|
|
42
|
+
pool: null;
|
|
43
|
+
source: 'unknown';
|
|
44
|
+
}) & {
|
|
45
|
+
/** Reachability is deliberately not probed by this observational gate. */
|
|
46
|
+
poolReachability: 'unknown' | 'not-applicable';
|
|
47
|
+
memory?: { limit: string; tier: string; source: string };
|
|
48
|
+
localReasons: string[];
|
|
49
|
+
/** Omitted observations must not be silently treated as negative evidence. */
|
|
50
|
+
unknownInputs: string[];
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const defaultPlacementDeps: PlacementGateDeps = {
|
|
54
|
+
resolveSubstrate: ({ flag }) => resolveHarnessSubstrate({ flag, env: process.env, currentContext: () => undefined }),
|
|
55
|
+
memoryLimit: (goal) => podMemoryLimitFor(goal, process.env),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export function placementGate(
|
|
59
|
+
input: PlacementGateInput,
|
|
60
|
+
deps: PlacementGateDeps = defaultPlacementDeps,
|
|
61
|
+
): GateResult<PlacementDecision> {
|
|
62
|
+
const unknownInputs = [
|
|
63
|
+
...(input.needsBrowser === undefined ? ['needsBrowser'] : []),
|
|
64
|
+
...(input.needsVideo === undefined ? ['needsVideo'] : []),
|
|
65
|
+
...(input.needsAppSigning === undefined ? ['needsAppSigning'] : []),
|
|
66
|
+
...(input.localFiles === undefined ? ['localFiles'] : []),
|
|
67
|
+
...(input.flag?.substrate === undefined ? ['substrate'] : []),
|
|
68
|
+
];
|
|
69
|
+
const localReasons = [
|
|
70
|
+
...(input.needsBrowser ? ['browser'] : []),
|
|
71
|
+
...(input.needsVideo ? ['video'] : []),
|
|
72
|
+
...(input.needsAppSigning ? ['app signing'] : []),
|
|
73
|
+
...(input.localFiles?.length ? [`local files: ${input.localFiles.join(', ')}`] : []),
|
|
74
|
+
];
|
|
75
|
+
// A measured local-only need wins; otherwise unknown needs cannot justify a remote assertion.
|
|
76
|
+
if (!localReasons.length && input.flag?.substrate !== 'local' && unknownInputs.length) {
|
|
77
|
+
return {
|
|
78
|
+
decision: { substrate: 'unknown', pool: null, source: 'unknown', poolReachability: 'unknown', localReasons, unknownInputs },
|
|
79
|
+
explanation: `placement=unknown; unmeasured=${unknownInputs.join(', ')}; pool reachability not checked`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const flag = localReasons.length ? { substrate: 'local' as const } : input.flag;
|
|
83
|
+
let resolved: ResolvedHarnessSubstrate;
|
|
84
|
+
try {
|
|
85
|
+
resolved = deps.resolveSubstrate({ flag });
|
|
86
|
+
} catch (error) {
|
|
87
|
+
if (flag?.substrate !== 'pod' || !(error instanceof Error) || !error.message.includes('Pod 풀 또는 현재 컨텍스트에 닿지 못했다')) throw error;
|
|
88
|
+
// No configured pool is not evidence that a remote pool is unreachable.
|
|
89
|
+
resolved = { substrate: 'pod', pool: null, source: 'flag' };
|
|
90
|
+
}
|
|
91
|
+
const poolReachability = resolved.substrate === 'pod' ? 'unknown' : 'not-applicable';
|
|
92
|
+
const memory = resolved.substrate === 'pod' ? deps.memoryLimit(input.goal) : undefined;
|
|
93
|
+
const decision: PlacementDecision = {
|
|
94
|
+
...resolved, poolReachability, localReasons, unknownInputs,
|
|
95
|
+
...(memory ? { memory } : {}),
|
|
96
|
+
};
|
|
97
|
+
return {
|
|
98
|
+
decision,
|
|
99
|
+
explanation: (resolved.substrate === 'local'
|
|
100
|
+
? `local: ${localReasons.join(' · ') || `substrate ${resolved.source}`}`
|
|
101
|
+
: `pod: pool=${resolved.pool ?? 'unknown'}; reachability=unknown (not checked); memory=${memory?.tier ?? 'unknown'} (${memory?.limit ?? 'unknown'})`)
|
|
102
|
+
+ (unknownInputs.length ? `; unmeasured=${unknownInputs.join(', ')}` : ''),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface RelationGateDeps {
|
|
107
|
+
openCards: () => Promise<readonly TaskCard[]> | readonly TaskCard[];
|
|
108
|
+
/** A previous completed run is reported as a fact, never inferred from a matching title. */
|
|
109
|
+
priorTermination: (goalId: string) => Promise<string | undefined> | string | undefined;
|
|
110
|
+
/** 'unknown' = the caller cannot observe running runs; recorded as unknown, never as none. */
|
|
111
|
+
activeRuns: (goalId: string) => Promise<readonly string[] | 'unknown'> | readonly string[] | 'unknown';
|
|
112
|
+
/** Optional preflight overlap observations are kept separate from open-card overlap. */
|
|
113
|
+
preflightOverlaps?: (paths: readonly string[]) => Promise<readonly string[]> | readonly string[];
|
|
114
|
+
dependsOn?: (goalId: string) => Promise<readonly string[]> | readonly string[];
|
|
115
|
+
similarCards?: (card: TaskCard, openCards: readonly TaskCard[]) => Promise<readonly string[]> | readonly string[];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface RelationDecision {
|
|
119
|
+
overlappingCards: string[];
|
|
120
|
+
preflightOverlaps: string[] | 'unknown';
|
|
121
|
+
dependsOn: string[] | 'unknown';
|
|
122
|
+
similarCards: string[] | 'unknown';
|
|
123
|
+
sameGoalActiveRuns: string[] | 'unknown';
|
|
124
|
+
priorTermination?: string;
|
|
125
|
+
/** Overlaps and similarity are observations, not dispatch locks. */
|
|
126
|
+
action: 'record';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function relationGate(
|
|
130
|
+
card: TaskCard,
|
|
131
|
+
targetPaths: readonly string[],
|
|
132
|
+
deps: RelationGateDeps,
|
|
133
|
+
): Promise<GateResult<RelationDecision>> {
|
|
134
|
+
const [cards, priorTermination, sameGoalActiveRuns] = await Promise.all([
|
|
135
|
+
deps.openCards(), deps.priorTermination(card.goalId), deps.activeRuns(card.goalId),
|
|
136
|
+
]);
|
|
137
|
+
const [preflightOverlaps, dependsOn, similarCards] = await Promise.all([
|
|
138
|
+
deps.preflightOverlaps?.(targetPaths) ?? 'unknown',
|
|
139
|
+
deps.dependsOn?.(card.goalId) ?? 'unknown',
|
|
140
|
+
deps.similarCards?.(card, cards) ?? 'unknown',
|
|
141
|
+
]);
|
|
142
|
+
const target = new Set(targetPaths);
|
|
143
|
+
const overlappingCards = cards
|
|
144
|
+
.filter((other) => other.id !== card.id && other.status === 'open' && other.sections.some((section) => {
|
|
145
|
+
if (!section.key.startsWith('workspace')) return false;
|
|
146
|
+
try {
|
|
147
|
+
const workspace = JSON.parse(section.content) as { targetPaths?: unknown; paths?: unknown };
|
|
148
|
+
const paths = workspace.targetPaths ?? workspace.paths;
|
|
149
|
+
return Array.isArray(paths) && paths.some((path) => typeof path === 'string' && target.has(path));
|
|
150
|
+
} catch { return false; }
|
|
151
|
+
}))
|
|
152
|
+
.map((other) => other.id);
|
|
153
|
+
const observed = (value: readonly string[] | string): string =>
|
|
154
|
+
typeof value === 'string' ? value : value.join(',') || 'none';
|
|
155
|
+
const decision: RelationDecision = {
|
|
156
|
+
action: 'record', overlappingCards,
|
|
157
|
+
preflightOverlaps: preflightOverlaps === 'unknown' ? 'unknown' : [...preflightOverlaps],
|
|
158
|
+
dependsOn: dependsOn === 'unknown' ? 'unknown' : [...dependsOn],
|
|
159
|
+
similarCards: similarCards === 'unknown' ? 'unknown' : [...similarCards],
|
|
160
|
+
sameGoalActiveRuns: sameGoalActiveRuns === 'unknown' ? 'unknown' : [...sameGoalActiveRuns],
|
|
161
|
+
...(priorTermination === undefined ? {} : { priorTermination }),
|
|
162
|
+
};
|
|
163
|
+
return {
|
|
164
|
+
decision,
|
|
165
|
+
explanation: `overlap=${observed(overlappingCards)}; preflight=${observed(preflightOverlaps)}; dependsOn=${observed(dependsOn)}; similar=${observed(similarCards)}; active=${observed(sameGoalActiveRuns)}; prior=${priorTermination ?? 'unknown'}; overlaps do not block launch`,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface MemoryGateDeps {
|
|
170
|
+
recall: (query: string) => Promise<string>;
|
|
171
|
+
priorAbandonment: (goalId: string) => Promise<string | undefined> | string | undefined;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export interface MemoryDecision {
|
|
175
|
+
context: string;
|
|
176
|
+
priorAbandonment?: string;
|
|
177
|
+
/** The existing recall API returns formatted context, not stable fragment IDs. */
|
|
178
|
+
fragmentIds: string[];
|
|
179
|
+
action: 'record';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export async function memoryGate(
|
|
183
|
+
card: Pick<TaskCard, 'goalId' | 'title'>,
|
|
184
|
+
deps: MemoryGateDeps = { recall: recallMemoryContext, priorAbandonment: () => undefined },
|
|
185
|
+
): Promise<GateResult<MemoryDecision>> {
|
|
186
|
+
const [context, priorAbandonment] = await Promise.all([
|
|
187
|
+
deps.recall(card.title), deps.priorAbandonment(card.goalId),
|
|
188
|
+
]);
|
|
189
|
+
const decision: MemoryDecision = {
|
|
190
|
+
action: 'record', context, fragmentIds: [],
|
|
191
|
+
...(priorAbandonment === undefined ? {} : { priorAbandonment }),
|
|
192
|
+
};
|
|
193
|
+
return {
|
|
194
|
+
decision,
|
|
195
|
+
explanation: `memory=${context ? 'recalled' : 'none or unavailable'}; prior abandonment=${priorAbandonment ?? 'unknown'}; fragment IDs unavailable from recall API`,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFileSync, realpathSync, statSync } from 'node:fs';
|
|
3
|
+
import { isAbsolute, join, relative, resolve } from 'node:path';
|
|
4
|
+
import { debug } from '../debug/log.js';
|
|
5
|
+
import { listInstalledPlugins, type InstalledPlugin } from '../plugins/install/plugin-install.js';
|
|
6
|
+
import { loadPluginManifestFromDir, type PluginManifest } from '../plugins/core/manifest.js';
|
|
7
|
+
import { loadPluginNodes } from './plugin-nodes.js';
|
|
8
|
+
import { getNodeKindRegistration, listNodeKinds, unregisterPluginNodeKind, type GraphKind, type NodeKindEntry } from './registry.js';
|
|
9
|
+
|
|
10
|
+
export interface InstalledPluginNodeSyncResult {
|
|
11
|
+
registered: number;
|
|
12
|
+
removed: number;
|
|
13
|
+
errors: Array<{ plugin: string; reason: string }>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type OwnedNode = { graph: GraphKind; kind: string; plugin: string; registered: NodeKindEntry };
|
|
17
|
+
type InstalledNodeState = { stamp: string; owned: Map<string, { fingerprint: string; nodes: OwnedNode[] }> };
|
|
18
|
+
const states = new Map<string, InstalledNodeState>();
|
|
19
|
+
|
|
20
|
+
function fingerprint(item: InstalledPlugin, manifest: PluginManifest): string {
|
|
21
|
+
const root = realpathSync(item.path);
|
|
22
|
+
const hash = createHash('sha256');
|
|
23
|
+
hash.update(JSON.stringify([item.name, item.version, item.market, item.sha256, item.installedAt, manifest]));
|
|
24
|
+
for (const path of manifest.contributes.nodes ?? []) {
|
|
25
|
+
const file = realpathSync(resolve(root, path));
|
|
26
|
+
const rel = relative(root, file);
|
|
27
|
+
if (!path.startsWith('./') || !rel || rel === '..' || rel.startsWith('../') || isAbsolute(rel)) {
|
|
28
|
+
throw new Error(`node path must remain inside the plugin dir: ${path}`);
|
|
29
|
+
}
|
|
30
|
+
hash.update(path);
|
|
31
|
+
hash.update(readFileSync(file));
|
|
32
|
+
}
|
|
33
|
+
return hash.digest('hex');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Reconcile only registrations made by this sync; the registry's identity check protects other owners. */
|
|
37
|
+
export function syncInstalledPluginNodes(root: string): InstalledPluginNodeSyncResult {
|
|
38
|
+
const result: InstalledPluginNodeSyncResult = { registered: 0, removed: 0, errors: [] };
|
|
39
|
+
const stateRoot = resolve(root);
|
|
40
|
+
const ledger = statSync(join(stateRoot, 'plugins', 'installed.json'), { throwIfNoEntry: false, bigint: true });
|
|
41
|
+
// The installer atomically replaces the ledger, so mtime and size alone can
|
|
42
|
+
// coincide across installs. File identity and ctime detect that replacement.
|
|
43
|
+
const stamp = ledger ? `${ledger.dev}:${ledger.ino}:${ledger.ctimeNs}:${ledger.mtimeNs}:${ledger.size}` : 'missing';
|
|
44
|
+
const previous = states.get(stateRoot);
|
|
45
|
+
if (previous?.stamp === stamp) return result;
|
|
46
|
+
|
|
47
|
+
// The installer commits a new ledger via rename after the package directory is in place.
|
|
48
|
+
// If discovery itself fails, the stamp is not advanced and the next request retries.
|
|
49
|
+
const installed = listInstalledPlugins(stateRoot);
|
|
50
|
+
const current = new Set(installed.map(item => item.path));
|
|
51
|
+
const owned = previous?.owned ?? new Map<string, { fingerprint: string; nodes: OwnedNode[] }>();
|
|
52
|
+
const removeOwned = (path: string): void => {
|
|
53
|
+
for (const node of owned.get(path)?.nodes ?? []) {
|
|
54
|
+
if (unregisterPluginNodeKind(node.graph, node.kind, node.plugin, node.registered)) result.removed++;
|
|
55
|
+
}
|
|
56
|
+
owned.delete(path);
|
|
57
|
+
};
|
|
58
|
+
for (const path of owned.keys()) {
|
|
59
|
+
if (!current.has(path)) removeOwned(path);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
for (const item of installed) {
|
|
63
|
+
try {
|
|
64
|
+
const manifest = loadPluginManifestFromDir(item.path, { id: item.name }).manifest;
|
|
65
|
+
if (!manifest.contributes.nodes?.length) {
|
|
66
|
+
removeOwned(item.path);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const version = fingerprint(item, manifest);
|
|
70
|
+
if (owned.get(item.path)?.fingerprint === version) continue;
|
|
71
|
+
removeOwned(item.path);
|
|
72
|
+
const before = new Set(listNodeKinds().filter(entry => entry.plugin === manifest.id).map(entry => `${entry.graph}:${entry.kind}`));
|
|
73
|
+
const loaded = loadPluginNodes(item.path, manifest);
|
|
74
|
+
const nodes: OwnedNode[] = [];
|
|
75
|
+
for (const entry of listNodeKinds().filter(entry => entry.plugin === manifest.id && !before.has(`${entry.graph}:${entry.kind}`))) {
|
|
76
|
+
const registered = getNodeKindRegistration(entry.graph, entry.kind);
|
|
77
|
+
if (registered) nodes.push({ graph: entry.graph, kind: entry.kind, plugin: manifest.id, registered });
|
|
78
|
+
}
|
|
79
|
+
if (loaded.errors.length) {
|
|
80
|
+
for (const node of nodes) unregisterPluginNodeKind(node.graph, node.kind, node.plugin, node.registered);
|
|
81
|
+
result.errors.push({ plugin: item.name, reason: loaded.errors.join('; ') });
|
|
82
|
+
owned.set(item.path, { fingerprint: version, nodes: [] });
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
result.registered += nodes.length;
|
|
86
|
+
owned.set(item.path, { fingerprint: version, nodes });
|
|
87
|
+
} catch (error) {
|
|
88
|
+
removeOwned(item.path);
|
|
89
|
+
result.errors.push({ plugin: item.name, reason: error instanceof Error ? error.message : String(error) });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
states.set(stateRoot, { stamp, owned });
|
|
93
|
+
debug.log('graph.kinds', 'installed-sync', { root: stateRoot, ...result });
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
@@ -34,8 +34,16 @@ export function loadPluginNodes(pluginDir: string, manifest: PluginManifest): Pl
|
|
|
34
34
|
if (node.graph === 'workflow') {
|
|
35
35
|
if (!node.run || typeof node.run !== 'object' || Array.isArray(node.run)) throw new Error('run must be an object');
|
|
36
36
|
const spec = node.run as Record<string, unknown>;
|
|
37
|
-
if (Object.keys(spec).length !== 1 || !['bash', 'http', 'skill'].some((key) => key in spec)) {
|
|
38
|
-
throw new Error('run must contain exactly one bash, http or
|
|
37
|
+
if (Object.keys(spec).length !== 1 || !['bash', 'http', 'skill', 'mcp'].some((key) => key in spec)) {
|
|
38
|
+
throw new Error('run must contain exactly one bash, http, skill or mcp executor');
|
|
39
|
+
}
|
|
40
|
+
if ('mcp' in spec) {
|
|
41
|
+
const mcp = spec.mcp;
|
|
42
|
+
if (!mcp || typeof mcp !== 'object' || Array.isArray(mcp)
|
|
43
|
+
|| typeof (mcp as Record<string, unknown>).server !== 'string'
|
|
44
|
+
|| typeof (mcp as Record<string, unknown>).tool !== 'string') {
|
|
45
|
+
throw new Error('mcp run requires server and tool');
|
|
46
|
+
}
|
|
39
47
|
}
|
|
40
48
|
run = spec as NodeKindRun;
|
|
41
49
|
} else if (node.run !== undefined) {
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import { execFile } from 'node:child_process';
|
|
3
|
-
import { linkSync, mkdirSync, readFileSync, readdirSync, renameSync, rmdirSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
4
|
-
import { dirname, join, resolve } from 'node:path';
|
|
3
|
+
import { existsSync, linkSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmdirSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { dirname, join, resolve, sep } from 'node:path';
|
|
5
5
|
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
6
|
+
import { getElanousConfigDir, getElanousConfigDirOverride } from '../elanous-config-dir.js';
|
|
6
7
|
import { debug } from '../debug/log.js';
|
|
8
|
+
import { elanousStateRoot } from '../autopilot/state-paths.js';
|
|
9
|
+
import { listInstalledPlugins } from '../plugins/install/plugin-install.js';
|
|
10
|
+
import { credentialStatus, pluginEnv } from '../plugins/install/plugin-credentials.js';
|
|
7
11
|
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
8
12
|
import { loadNodeCatalog } from '../self-implement/graph-catalog.js';
|
|
9
13
|
import { parseGraphTemplateYaml, type GraphEdgeSpec, type GraphNodeSpec } from '../self-implement/graph-yaml.js';
|
|
@@ -215,6 +219,17 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
215
219
|
const parsed = parseGraphTemplateYaml(stringifyYaml(document), path);
|
|
216
220
|
if (!parsed.template || parsed.errors.length) throw new Error(parsed.errors.map((e) => `${e.path}: ${e.message}`).join('\n'));
|
|
217
221
|
const graph = parsed.template;
|
|
222
|
+
const pluginRoot = elanousStateRoot();
|
|
223
|
+
const graphFile = realpathSync(path);
|
|
224
|
+
const installed = listInstalledPlugins(pluginRoot);
|
|
225
|
+
const owner = installed.find(item => {
|
|
226
|
+
const graphsDir = join(item.path, 'graphs');
|
|
227
|
+
return existsSync(graphsDir) && graphFile.startsWith(`${realpathSync(graphsDir)}${sep}`);
|
|
228
|
+
});
|
|
229
|
+
const credentials = owner ? pluginEnv(owner.name, pluginRoot) : {};
|
|
230
|
+
const declaredEnv = [...new Set(installed.map(item => item.name))]
|
|
231
|
+
.flatMap(name => credentialStatus(name, pluginRoot).fields.map(field => field.env));
|
|
232
|
+
const credentialFields = owner ? credentialStatus(owner.name, pluginRoot).fields.filter(field => field.set).map(field => field.name) : [];
|
|
218
233
|
for (const terminal of graph.terminalNodes) {
|
|
219
234
|
if (terminal !== 'done' && terminal !== 'failed') {
|
|
220
235
|
throw new Error(`unsupported terminal node: ${terminal} (expected done or failed)`);
|
|
@@ -390,17 +405,32 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
390
405
|
mkdirSync(dirname(contextPath), { recursive: true });
|
|
391
406
|
writeFileSync(contextPath, JSON.stringify({ graphId, runId, nodeId: current, input: state.input ?? null, outputs }, null, 2) + '\n');
|
|
392
407
|
let code: number | null = null;
|
|
408
|
+
const redactCredentials = (text: string) => Object.values(credentials).filter(value => value.length > 0)
|
|
409
|
+
.reduce((safe, value) => safe.replaceAll(value, '[REDACTED]'), text);
|
|
393
410
|
const runBash: BashRun = async (body, opts) => {
|
|
394
411
|
const result = await (options.deps?.runBash ?? realRunBash)(body, opts);
|
|
395
412
|
code = result.exitCode;
|
|
396
|
-
return result;
|
|
413
|
+
return { ...result, stdout: redactCredentials(result.stdout), stderr: redactCredentials(result.stderr) };
|
|
397
414
|
};
|
|
398
|
-
|
|
415
|
+
if (owner) debug.log('plugin.credentials', 'injected', { plugin: owner.name, fields: credentialFields, count: credentialFields.length });
|
|
416
|
+
const env = { ...process.env };
|
|
417
|
+
for (const key of declaredEnv) delete env[key];
|
|
418
|
+
Object.assign(env, credentials);
|
|
419
|
+
// 러너가 명시로 받은 우주(`--config-dir`)를 `cmd:` 자식에 못 박는다 — 자식이 코드 위치(소스 트리)로 시험 우주를 새로 고르던 구멍
|
|
420
|
+
// (09-30 🅢 스튜어드: 작업 트리 `graph run … --config-dir ~/.elanous` 의 자식 triage 가 «apiKey missing» · 0.2.5 컷 릴리스 루프도 같은 발사 줄).
|
|
421
|
+
if (getElanousConfigDirOverride()) {
|
|
422
|
+
env.ELANOUS_CONFIG_DIR = getElanousConfigDir();
|
|
423
|
+
env.ELANOUS_STATE_DIR = effectiveInstanceRoot();
|
|
424
|
+
debug.log('graph-runner', 'child-universe-pinned', { graphId, runId, node: current, configDir: env.ELANOUS_CONFIG_DIR, stateDir: env.ELANOUS_STATE_DIR });
|
|
425
|
+
}
|
|
426
|
+
env.ELANOUS_GRAPH_CONTEXT = contextPath;
|
|
427
|
+
env.ELANOUS_GRAPH_DIR = dirname(resolve(path));
|
|
428
|
+
const ctx = { arguments: '', artifactsDir: dirname(statePath), outputs: {}, resolvedProvider: undefined, resolvedModel: undefined, toolPolicy: {}, env } as NodeExecContext;
|
|
399
429
|
const result = await executeBashNode({ id: current, type: 'bash', bash: command.command, ...(command.timeout_ms ? { idle_timeout: command.timeout_ms } : {}) } as BashNode, ctx, { runBash } as WorkflowDeps);
|
|
400
430
|
output = result.output;
|
|
401
431
|
ok = result.ok;
|
|
402
432
|
exit = code;
|
|
403
|
-
error = result.error;
|
|
433
|
+
error = result.error ? redactCredentials(result.error) : undefined;
|
|
404
434
|
state.executed++;
|
|
405
435
|
}
|
|
406
436
|
if (resumingCompleted && completed) {
|
package/src/grounding/token.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Pod 는 호스트 우주를 «직접» 읽지 않는다(4우주·최소 자격). 대신 호스트 `POST /v1/grounding/query` 에
|
|
4
4
|
* «질의 → 인용»만 묻는다. 그 문을 여는 것이 이 토큰이다.
|
|
5
5
|
* - 모양: `base64url(payload).base64url(HMAC-SHA256)` · payload = `{v:1, runId, job, scope, exp}`.
|
|
6
|
-
* scope 는 `'grounding'`
|
|
6
|
+
* scope 는 `'grounding'` · `'llm-credential'` · `'gh-credential'` — 같은 서명·검증·회수. 문은 scope 를 엄격히 가른다.
|
|
7
7
|
* - 상태 없는 검증 — 엔드포인트는 `verifyGroundingToken` 하나만 부른다.
|
|
8
8
|
* - 키: 비밀 백엔드 `grounding/hmac`(없으면 처음 발급 때 무작위 32바이트로 만든다) — 호스트 밖으로 안 나간다.
|
|
9
9
|
* - 수명: `exp`(Job 기한) ⊕ 조기 회수 `revokeGroundingRun(runId)`(Job 이 끝나면 호스트가 부른다).
|
|
@@ -19,13 +19,15 @@ export const GROUNDING_TOKEN_ENV = 'ELANOUS_GROUNDING_TOKEN';
|
|
|
19
19
|
export const GROUNDING_URL_ENV = 'ELANOUS_GROUNDING_URL';
|
|
20
20
|
export const GROUNDING_HMAC_SECRET_ID = 'grounding/hmac';
|
|
21
21
|
|
|
22
|
-
export const GROUNDING_TOKEN_SCOPES = ['grounding', 'llm-credential'] as const;
|
|
22
|
+
export const GROUNDING_TOKEN_SCOPES = ['grounding', 'llm-credential', 'gh-credential'] as const;
|
|
23
23
|
export type GroundingTokenScope = (typeof GROUNDING_TOKEN_SCOPES)[number];
|
|
24
24
|
|
|
25
25
|
export interface GroundingClaims {
|
|
26
26
|
readonly runId: string;
|
|
27
27
|
readonly job: string;
|
|
28
28
|
readonly scope: GroundingTokenScope;
|
|
29
|
+
/** GitHub credential relay only: full owner/repository bound by the host at issuance. */
|
|
30
|
+
readonly repository?: string;
|
|
29
31
|
readonly exp: number;
|
|
30
32
|
}
|
|
31
33
|
|
|
@@ -59,19 +61,22 @@ function sign(payload: string, key: string): string {
|
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
export async function mintGroundingToken(
|
|
62
|
-
claims: { runId: string; job: string; ttlMs: number; scope?: GroundingTokenScope },
|
|
64
|
+
claims: { runId: string; job: string; ttlMs: number; scope?: GroundingTokenScope; repository?: string },
|
|
63
65
|
deps: GroundingTokenDeps = {},
|
|
64
66
|
): Promise<{ token: string; exp: number }> {
|
|
65
67
|
const key = await (deps.key ?? (() => readKey(true)))();
|
|
66
68
|
if (!key) throw new Error('grounding: 서명 키를 만들 수 없다(비밀 백엔드)');
|
|
67
69
|
const scope: GroundingTokenScope = claims.scope ?? 'grounding';
|
|
70
|
+
if (claims.repository !== undefined && (scope !== 'gh-credential' || !/^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(claims.repository) || claims.repository.split('/').some((part) => part === '.' || part === '..'))) {
|
|
71
|
+
throw new Error('grounding: invalid credential repository');
|
|
72
|
+
}
|
|
68
73
|
const exp = (deps.now ?? Date.now)() + claims.ttlMs;
|
|
69
|
-
const payload = Buffer.from(JSON.stringify({ v: 1, runId: claims.runId, job: claims.job, scope, exp })).toString('base64url');
|
|
74
|
+
const payload = Buffer.from(JSON.stringify({ v: 1, runId: claims.runId, job: claims.job, scope, ...(scope === 'gh-credential' ? { repository: claims.repository } : {}), exp })).toString('base64url');
|
|
70
75
|
debug.log('grounding.token', 'minted', { runId: claims.runId, job: claims.job, scope, exp });
|
|
71
76
|
return { token: `${payload}.${sign(payload, key)}`, exp };
|
|
72
77
|
}
|
|
73
78
|
|
|
74
|
-
/** 그라운딩 문은 scope `'grounding'` 만 받는다.
|
|
79
|
+
/** 그라운딩 문은 scope `'grounding'` 만 받는다. 자격 증명 scope 는 `expectedScope` 로 따로 연다. */
|
|
75
80
|
export async function verifyGroundingToken(
|
|
76
81
|
token: string,
|
|
77
82
|
deps: GroundingTokenDeps & { readonly expectedScope?: GroundingTokenScope } = {},
|
|
@@ -92,9 +97,10 @@ export async function verifyGroundingToken(
|
|
|
92
97
|
if (claims.v !== 1 || typeof claims.runId !== 'string' || typeof claims.job !== 'string' || typeof claims.exp !== 'number') return reject('malformed');
|
|
93
98
|
const expected: GroundingTokenScope = deps.expectedScope ?? 'grounding';
|
|
94
99
|
if (claims.scope !== expected) return reject('scope', claims.runId);
|
|
100
|
+
if (expected === 'gh-credential' && claims.repository !== undefined && (typeof claims.repository !== 'string' || !/^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(claims.repository) || claims.repository.split('/').some((part) => part === '.' || part === '..'))) return reject('malformed', claims.runId);
|
|
95
101
|
if ((deps.now ?? Date.now)() >= claims.exp) return reject('expired', claims.runId);
|
|
96
102
|
if (isRevoked(claims.runId, deps)) return reject('revoked', claims.runId);
|
|
97
|
-
return { ok: true, runId: claims.runId, job: claims.job, scope: expected, exp: claims.exp };
|
|
103
|
+
return { ok: true, runId: claims.runId, job: claims.job, scope: expected, ...(expected === 'gh-credential' && claims.repository !== undefined ? { repository: claims.repository } : {}), exp: claims.exp };
|
|
98
104
|
}
|
|
99
105
|
|
|
100
106
|
/** Job 이 끝나면 호스트가 부른다 — 그 런의 토큰은 기한 전이라도 더는 안 통한다. */
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
2
3
|
import { readFileSync } from 'node:fs';
|
|
3
4
|
import { resolve } from 'node:path';
|
|
4
5
|
import { Command, Option } from 'commander';
|
|
5
6
|
import { runGitCommand } from '../git-fs/runner.js';
|
|
6
|
-
import { GOAL_TYPES, parseGoalType, type GoalType } from '../self-implement/goal-author.js';
|
|
7
|
+
import { GOAL_TYPES, lintGoalFile, parseGoalId, parseGoalType, tracedPathReferences, type GoalType } from '../self-implement/goal-author.js';
|
|
7
8
|
import { templateForGoalType } from '../self-implement/graph-templates.js';
|
|
8
9
|
import { listRunLedgers, loadFederatedRunLedger, loadRunLedger, runLedgerDir, type RunLedgerMatch } from '../self-implement/run-ledger.js';
|
|
9
10
|
import { resolveChildLlmEffort, resolveImplementationChildModel } from '../self-dev/dev-cli.js';
|
|
@@ -12,6 +13,7 @@ import { queryRunningRuns } from '../self-implement/running-runs.js';
|
|
|
12
13
|
import { DevPipelineError } from '../self-dev/dev-pipeline.js';
|
|
13
14
|
import { runDraftSweep, type DraftSweepAdapters, type DraftSweepResult, type SweepDraft, type SweepMergedPr } from '../self-dev/draft-sweep.js';
|
|
14
15
|
import { debug } from '../debug/log.js';
|
|
16
|
+
import { dispatchTask, type DispatchTaskInput, type DispatchTaskDeps } from '../execution-loop/dispatch-task.js';
|
|
15
17
|
import { PR_LABELS } from '../github/pr-labels.js';
|
|
16
18
|
import { decideBudget, readBudgetInputsLive, type BudgetDecision, type BudgetInputs } from '../self-implement/budget-gate.js';
|
|
17
19
|
import { installDeliverableVerifyCliCommand, type InstallDeliverableVerifyCliDeps } from './deliverable-verify-cli.js';
|
|
@@ -198,13 +200,16 @@ function printHarnessAskMarkerDryRun(goalPath?: string): void {
|
|
|
198
200
|
const warnings = inspectHarnessAskMarkerWarnings(ask);
|
|
199
201
|
if (warnings.length === 0) {
|
|
200
202
|
console.log('[dry-run] ✅ ask 마커 — 경고 없음');
|
|
201
|
-
|
|
203
|
+
} else {
|
|
204
|
+
for (const warning of warnings) console.log(`[dry-run] ⚠️ ask 마커 — ${warning}`);
|
|
202
205
|
}
|
|
203
|
-
for (const warning of warnings) console.log(`[dry-run] ⚠️ ask 마커 — ${warning}`);
|
|
204
206
|
} catch (error) {
|
|
205
207
|
const message = error instanceof Error ? error.message.split('\n')[0] : String(error);
|
|
206
208
|
console.log(`[dry-run] ⚠️ ask 마커 — 검사 실패: ${message}`);
|
|
207
209
|
}
|
|
210
|
+
for (const finding of lintGoalFile(ask, 'main').filter(({ tag }) => tag === 'release-note')) {
|
|
211
|
+
console.log(`[dry-run] ⚠️ 릴리스 노트 — ${finding.message}`);
|
|
212
|
+
}
|
|
208
213
|
}
|
|
209
214
|
|
|
210
215
|
function printHarnessLaunchDryRun(preview: {
|
|
@@ -218,7 +223,9 @@ function printHarnessLaunchDryRun(preview: {
|
|
|
218
223
|
console.log(`[dry-run] 입력: ${preview.input}`);
|
|
219
224
|
console.log(`[dry-run] 입구: ${preview.entrance}`);
|
|
220
225
|
console.log(`[dry-run] 시작 예정: ${preview.wouldStart}`);
|
|
221
|
-
console.log(
|
|
226
|
+
console.log(preview.goalPath
|
|
227
|
+
? '[dry-run] 전제 검사: ask 마커·릴리스 노트 린트만 돌렸다 · 원격 조회(열린 PR·런 원장)는 돌리지 않음'
|
|
228
|
+
: '[dry-run] 전제 검사: ask 마커만 돌렸다 · 원격 조회(열린 PR·런 원장)는 돌리지 않음');
|
|
222
229
|
printHarnessAskMarkerDryRun(preview.goalPath);
|
|
223
230
|
if (preview.goalPath) console.log(renderGoalTemplateDryRun(preview.goalPath, preview.goalType));
|
|
224
231
|
if (preview.target !== undefined) {
|
|
@@ -315,7 +322,8 @@ function resolveLaunchSubstrate(opts: HarnessSubstrateOpts): ResolvedHarnessSubs
|
|
|
315
322
|
return resolved;
|
|
316
323
|
}
|
|
317
324
|
/** ⭐ 런 계약의 실행 칸 = pod — 호스트는 그래프를 안 돌리고 Pod 로 보낸다(harness-pod-dispatch.ts). */
|
|
318
|
-
async function onPod(opts: unknown, entrance: 'cli-harness-ask' | 'cli-harness-say', input: string, pool: string
|
|
325
|
+
async function onPod(opts: unknown, entrance: 'cli-harness-ask' | 'cli-harness-say', input: string, pool: string,
|
|
326
|
+
recordDispatch: (input: DispatchTaskInput, deps?: DispatchTaskDeps) => ReturnType<typeof dispatchTask> = dispatchTask): Promise<void> {
|
|
319
327
|
const o = opts as HarnessSubstrateOpts;
|
|
320
328
|
if (o.target !== undefined) {
|
|
321
329
|
debug.log('harness.pod', 'target-refused', { target: o.target });
|
|
@@ -324,10 +332,59 @@ async function onPod(opts: unknown, entrance: 'cli-harness-ask' | 'cli-harness-s
|
|
|
324
332
|
process.exitCode = 2;
|
|
325
333
|
return;
|
|
326
334
|
}
|
|
335
|
+
let dispatchRecorded = false;
|
|
336
|
+
try {
|
|
337
|
+
const goalText = entrance === 'cli-harness-ask' ? readFileSync(input, 'utf8') : input;
|
|
338
|
+
await recordDispatch({
|
|
339
|
+
goalId: (entrance === 'cli-harness-ask' ? parseGoalId(goalText) : null)
|
|
340
|
+
?? `request-${createHash('sha256').update(goalText).digest('hex').slice(0, 32)}`,
|
|
341
|
+
title: goalText.split(/\r?\n/, 1)[0]?.trim() || 'Untitled request',
|
|
342
|
+
goalText,
|
|
343
|
+
targetPaths: entrance === 'cli-harness-ask' ? tracedPathReferences(goalText).map((reference) => reference.path) : [],
|
|
344
|
+
spec: { input: entrance === 'cli-harness-ask' ? { file: input } : { text: input }, humanReadableOutput: false },
|
|
345
|
+
});
|
|
346
|
+
dispatchRecorded = true;
|
|
347
|
+
} catch (error) {
|
|
348
|
+
try { debug.log('execution-loop.gate', 'dispatch-unavailable', { reason: String(error) }); } catch { /* fail-soft */ }
|
|
349
|
+
}
|
|
327
350
|
const { dispatchHarnessOnPod } = await import('./harness-pod-dispatch.js');
|
|
328
|
-
|
|
351
|
+
let output = '';
|
|
352
|
+
const status = await dispatchHarnessOnPod({ entrance, input, podPool: pool, ...(dispatchRecorded ? { dispatchRecorded: true } : {}), ...(o.podMemory ? { podMemory: o.podMemory } : {}), ...(o.autoMerge === false ? { autoMerge: false } : {}), ...(o.base ? { base: o.base } : {}), ...(o.json ? { json: true } : {}), ...(o.source ? { source: o.source } : {}) },
|
|
353
|
+
entrance === 'cli-harness-ask' ? { onOutput: (text) => { output = (output + text).slice(-16_000); } } : {});
|
|
329
354
|
if (status !== 0) {
|
|
330
|
-
|
|
355
|
+
let childError: string | undefined;
|
|
356
|
+
let podFailure: string | undefined;
|
|
357
|
+
let podJobFailed = false;
|
|
358
|
+
if (entrance === 'cli-harness-ask') {
|
|
359
|
+
for (const line of output.split('\n').reverse()) {
|
|
360
|
+
if (!line.trimStart().startsWith('[{') || !line.includes('"error"')) continue;
|
|
361
|
+
try {
|
|
362
|
+
const results: unknown = JSON.parse(line);
|
|
363
|
+
if (!Array.isArray(results)) continue;
|
|
364
|
+
for (const result of results) {
|
|
365
|
+
if (!result || typeof result !== 'object') continue;
|
|
366
|
+
const error = (result as { error?: { code?: string; message?: string } }).error;
|
|
367
|
+
if (typeof error?.code !== 'string' || typeof error.message !== 'string' || !error.code.startsWith('pod-')) continue;
|
|
368
|
+
if (error.code === 'pod-job-failed') {
|
|
369
|
+
podJobFailed = true;
|
|
370
|
+
childError = error.message.split('childError=')[1]?.split('\n', 1)[0];
|
|
371
|
+
}
|
|
372
|
+
podFailure = error.message;
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
} catch { /* Non-result stdout cannot establish that a Pod child ran. */ }
|
|
376
|
+
if (podFailure) break;
|
|
377
|
+
}
|
|
378
|
+
if (!podFailure) {
|
|
379
|
+
const summary = output.split('\n').find((line) => /\s❌\sfailed · .* — pod-[\w-]+: /.test(line));
|
|
380
|
+
podFailure = summary?.split(/\s❌\sfailed · .* — pod-[\w-]+: /)[1];
|
|
381
|
+
podJobFailed = summary?.includes(' — pod-job-failed: ') ?? false;
|
|
382
|
+
if (podJobFailed) childError = podFailure?.split('childError=')[1]?.split('\n', 1)[0];
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (podJobFailed && childError && childError !== 'no-result-line') console.error(`Pod 안 자식이 실패했다 — ${childError}`);
|
|
386
|
+
else if (podFailure) console.error(`Pod 실행이 실패했다 — ${podFailure.split('\n', 1)[0]}`);
|
|
387
|
+
else console.error('Pod 실행에 닿지 못했다 — 풀·컨텍스트·SSH 연결을 확인하거나 `--substrate local` 로 명시하라');
|
|
331
388
|
process.exitCode = status;
|
|
332
389
|
}
|
|
333
390
|
}
|
|
@@ -1502,6 +1559,8 @@ export interface HarnessCliCommandDeps {
|
|
|
1502
1559
|
draftSweep?: HarnessDraftSweepDeps;
|
|
1503
1560
|
ask?: HarnessAskHandler;
|
|
1504
1561
|
say?: HarnessSayHandler;
|
|
1562
|
+
/** Observe-only Pod host dispatch seam. */
|
|
1563
|
+
podDispatchTask?: (input: DispatchTaskInput, deps?: DispatchTaskDeps) => ReturnType<typeof dispatchTask>;
|
|
1505
1564
|
plan?: HarnessPlanHandler;
|
|
1506
1565
|
/** ⭐ `plan` 주입이 «없을 때» 가는 기본 분기. 시험이 이 자리로 «CLI 의 기본 배선»을 문다
|
|
1507
1566
|
* (⛔ 주입된 `plan` 이 파일을 스스로 만들면 그 시험은 배선을 «못 답한다» — 무인 리뷰 GOODHART 지적). */
|
|
@@ -1540,7 +1599,7 @@ export function installHarnessCliCommand(program: Command, deps: HarnessCliComma
|
|
|
1540
1599
|
process.exitCode = 2;
|
|
1541
1600
|
return Promise.resolve();
|
|
1542
1601
|
}
|
|
1543
|
-
return resolved.substrate === 'pod' ? onPod(opts, 'cli-harness-ask', goalPath, resolved.pool
|
|
1602
|
+
return resolved.substrate === 'pod' ? onPod(opts, 'cli-harness-ask', goalPath, resolved.pool!, deps.podDispatchTask) : ask(goalPath, normalizeHarnessAskSayOptions(opts));
|
|
1544
1603
|
},
|
|
1545
1604
|
);
|
|
1546
1605
|
});
|
|
@@ -1564,7 +1623,7 @@ export function installHarnessCliCommand(program: Command, deps: HarnessCliComma
|
|
|
1564
1623
|
process.exitCode = 2;
|
|
1565
1624
|
return Promise.resolve();
|
|
1566
1625
|
}
|
|
1567
|
-
return resolved.substrate === 'pod' ? onPod(opts, 'cli-harness-say', sentence.join(' '), resolved.pool
|
|
1626
|
+
return resolved.substrate === 'pod' ? onPod(opts, 'cli-harness-say', sentence.join(' '), resolved.pool!, deps.podDispatchTask) : say(sentence, normalizeHarnessAskSayOptions(opts));
|
|
1568
1627
|
},
|
|
1569
1628
|
);
|
|
1570
1629
|
});
|
|
@@ -19,6 +19,8 @@ export interface HarnessPodDispatchInput {
|
|
|
19
19
|
/** say 의 문장, 또는 ask 의 골 문서 경로. */
|
|
20
20
|
readonly input: string;
|
|
21
21
|
readonly podPool?: string;
|
|
22
|
+
/** Host already recorded the observe-only dispatch; suppress a second decision in the Pod. */
|
|
23
|
+
readonly dispatchRecorded?: boolean;
|
|
22
24
|
/** Pod 메모리 등급(standard|high) — 오케스트레이터 환경 `ELANOUS_POD_MEMORY_TIER` 로 Job 까지 간다. */
|
|
23
25
|
readonly podMemory?: string;
|
|
24
26
|
readonly autoMerge?: boolean;
|
|
@@ -49,7 +51,7 @@ export function podOrchestrateArgs(input: HarnessPodDispatchInput, goal: string)
|
|
|
49
51
|
];
|
|
50
52
|
}
|
|
51
53
|
|
|
52
|
-
type PodDispatchDeps = { run?: (cmd: string, args: readonly string[], env: NodeJS.ProcessEnv) => number | null; readFile?: (path: string) => string; cwd?: string; spawnChild?: typeof spawn };
|
|
54
|
+
type PodDispatchDeps = { run?: (cmd: string, args: readonly string[], env: NodeJS.ProcessEnv) => number | null; readFile?: (path: string) => string; cwd?: string; spawnChild?: typeof spawn; onOutput?: (text: string) => void };
|
|
53
55
|
|
|
54
56
|
export function dispatchHarnessOnPod(input: HarnessPodDispatchInput, deps: PodDispatchDeps & { run: NonNullable<PodDispatchDeps['run']> }): number;
|
|
55
57
|
export function dispatchHarnessOnPod(input: HarnessPodDispatchInput, deps?: PodDispatchDeps): number | Promise<number>;
|
|
@@ -68,6 +70,8 @@ export function dispatchHarnessOnPod(input: HarnessPodDispatchInput, deps: PodDi
|
|
|
68
70
|
const args = podOrchestrateArgs(input, goal);
|
|
69
71
|
const env = { ...process.env };
|
|
70
72
|
delete env.ELANOUS_POD_GOAL_DOC;
|
|
73
|
+
delete env.ELANOUS_DISPATCH_RECORDED;
|
|
74
|
+
if (input.dispatchRecorded) env.ELANOUS_DISPATCH_RECORDED = '1';
|
|
71
75
|
if (input.podMemory) env.ELANOUS_POD_MEMORY_TIER = input.podMemory;
|
|
72
76
|
if (input.entrance === 'cli-harness-ask') {
|
|
73
77
|
const root = findGitDir(cwd)?.root;
|
|
@@ -84,7 +88,9 @@ export function dispatchHarnessOnPod(input: HarnessPodDispatchInput, deps: PodDi
|
|
|
84
88
|
goalChars: goal.length, ...(input.entrance === 'cli-harness-ask' ? { goalPath: input.input } : {}),
|
|
85
89
|
});
|
|
86
90
|
const run = deps.run ?? ((cmd: string, a: readonly string[], childEnv: NodeJS.ProcessEnv) => new Promise<number>((resolveStatus) => {
|
|
87
|
-
const child = (deps.spawnChild ?? spawn)(cmd, [...a], { stdio: 'inherit', env: childEnv });
|
|
91
|
+
const child = (deps.spawnChild ?? spawn)(cmd, [...a], { stdio: deps.onOutput ? ['inherit', 'pipe', 'inherit'] : 'inherit', env: childEnv });
|
|
92
|
+
child.stdout?.setEncoding('utf8');
|
|
93
|
+
child.stdout?.on('data', (chunk: string) => { process.stdout.write(chunk); deps.onOutput?.(chunk); });
|
|
88
94
|
let forwarded: NodeJS.Signals | undefined;
|
|
89
95
|
const forward = (signal: NodeJS.Signals) => {
|
|
90
96
|
forwarded = signal;
|