elanous 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -6
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/1169-696263ac7af8c4d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1883.c91a2648af4842fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1947-9d64872eeb9cfdec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2808-dc06482994b40f70.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 2984-977cfa339fcd65a5.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/3165-95515ca5f299a120.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8142-011e1f474efb8b33.js → 3190-e9282753e7ebde85.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3995-bee512a7063036ce.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6062-73e68343a9814f6a.js → 4240-7153881b0b377306.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4259-99614149577e77cb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4283-329eabeaf233e6cd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4328-cb5058b1acc2de5e.js +25 -0
- package/apps/pwa/out/_next/static/chunks/4801-ecee6ef62b349b18.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5110-8058791dd25a2e14.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3403.4bc97b4066945a94.js → 5329.8b7e4bca684b6eec.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/5330-c41e9670f594c38f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5423-e95fd125d1794349.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5478-b5eae07dc79ea619.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5787-96ee6642b9a661e5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6448-79d72368589f98bc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6522-9866c002b5531836.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6729-b4a558b7aa891e0c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6969-cb03f9410b7d2e03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7007-3b9861091bb8dd21.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5791-bb82743fa6299562.js → 763-7d0e1913b5074e98.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{4323.c5dfb6f503304775.js → 7633.0654fe6ea259c1ee.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7930-8dc7e20fc6fc19ca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8071-187edbc22a86741f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8198-d4101ad70cb9a579.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8250.4a2c5553a07cdf47.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8293-123b91e7b0330dc0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8296-d6d5d87dfa911d41.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8305-603963c33ee8eec3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8612-4a0066f20951b8fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2ea96958-e8c7e112de50b067.js → 8d0d9faf-97887ff99713b84e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9181-cf4e1b61c4783f92.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8046cadf-211ca3c0e00f2b72.js → 91ff6dda-3b33d42d20657429.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9262-26c2ecbd5a67e4a2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9328-45abdebce4fbecb7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9355-dbc67205adf8745f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9763.255b90dd69875689.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9933-66d31ec25c0231af.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-bd06fb55f4da0381.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-4e586142da455a25.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-889ad5cddcf45b0d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-47ba401d56572c27.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-0b54f9c83458e038.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-4fcc296ef6c8ed90.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-fe9fda494a87f4e5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-f9642e25ce129590.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-e1063e8ba5f3e2e7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-9d0df30027d8aa9d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-bd06fb55f4da0381.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-d97adf2c9455b8d0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-236b80d202110362.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-21a30f35f1a276ad.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-b1a5be357c4fa25d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1705a60303f7f573.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-76b337c11ad0e237.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-7c0f85fbb56f64c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-65ca16b20617b44d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-e7f9b8fc29411c1d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-92ea6213d1aeec04.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a913a005aacb5476.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-2d78804a40dfdef6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-08a99148e780ac00.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-deb674e7ed36f815.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-49f8de0534d192d7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-bd06fb55f4da0381.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-ba162a28d2176e18.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-1ff1a37b550ab41a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-ccaa698f56c0fbdb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-53b944082de1d7db.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-9b70adf1552789bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-30d482aa63241fe8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-aecf786f54651d8a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6ab9b62c7b6e08f2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-08949735455e0c9a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-a1479005234a46c2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3890e90bd232732.js +1 -0
- package/apps/pwa/out/_next/static/chunks/beb5cf10-9adcc10b83930090.js +1 -0
- package/apps/pwa/out/_next/static/chunks/framework-d68d41c88e05c801.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-11c63971b7a9593b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-d09452353bd0d9cc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-184d727cebeca767.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-21bbbbac99b720b1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{webpack-e5bea36f183b25e6.js → webpack-37b8bf78a3c6eec5.js} +1 -1
- package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +1 -0
- package/apps/pwa/out/_next/static/{zoQX5iTvXOsIvCjxCe1Ji → qMI0jnlZaBjjm4aIDYNGM}/_buildManifest.js +1 -1
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +29 -27
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +29 -27
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +29 -27
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +26 -24
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +26 -24
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +26 -24
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +29 -27
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +29 -27
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +29 -27
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/icons/elanous-16.png +0 -0
- package/apps/pwa/out/icons/elanous-180.png +0 -0
- package/apps/pwa/out/icons/elanous-192.png +0 -0
- package/apps/pwa/out/icons/elanous-32.png +0 -0
- package/apps/pwa/out/icons/elanous-512.png +0 -0
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +26 -24
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +29 -27
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +29 -27
- package/apps/pwa/out/manifest.webmanifest +5 -2
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +26 -24
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +26 -24
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +29 -27
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +29 -27
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +27 -25
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +29 -27
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +26 -24
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +29 -27
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +29 -27
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +29 -27
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +29 -27
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +29 -27
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +26 -24
- package/apps/pwa/out/sw-strategies.js +44 -0
- package/apps/pwa/out/sw.js +6 -44
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +29 -27
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +29 -27
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +29 -27
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +29 -27
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +29 -27
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +29 -27
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +29 -27
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +29 -27
- package/catalog/resources.yaml +6 -6
- package/docs/goal-context/steward-directives-2026-09-28.md +35 -37
- package/graphs/landing/landing-heal.yaml +1 -1
- package/graphs/release/recipes.yaml +11 -1
- package/graphs/release/release-loop.yaml +9 -2
- package/graphs/video/README.md +1 -0
- package/graphs/video/explainer-line.yaml +58 -0
- package/package.json +3 -1
- package/plugins/card-followup/README.md +33 -0
- package/plugins/card-followup/examples/card-sample.json +7 -0
- package/plugins/card-followup/graphs/card-followup.yaml +16 -0
- package/plugins/card-followup/graphs/recipes.yaml +12 -0
- package/plugins/card-followup/graphs/run-step.ts +172 -0
- package/plugins/card-followup/plugin.json +25 -0
- package/plugins/geo-check/README.md +13 -0
- package/plugins/geo-check/examples/brand-sample.json +8 -0
- package/plugins/geo-check/graphs/geo-check.yaml +20 -0
- package/plugins/geo-check/graphs/recipes.yaml +18 -0
- package/plugins/geo-check/graphs/run-step.ts +177 -0
- package/plugins/geo-check/plugin.json +24 -0
- package/plugins/job-coach/.codex-plugin/plugin.json +18 -1
- package/plugins/job-coach/README.md +42 -0
- package/plugins/job-coach/connectors/ncs/server.ts +4 -3
- package/plugins/job-coach/graphs/gap-judge.ts +44 -0
- package/plugins/job-coach/graphs/recipes.yaml +27 -0
- package/plugins/job-coach/graphs/report-enterprise.yaml +28 -0
- package/plugins/job-coach/graphs/run-step.ts +211 -17
- package/plugins/job-coach/plugin.json +16 -3
- package/plugins/job-coach/skills/enterprise-ai-fit/SKILL.md +8 -0
- package/plugins/job-coach/skills/enterprise-competency/SKILL.md +8 -0
- package/plugins/job-coach/skills/enterprise-needs/SKILL.md +8 -0
- package/plugins/job-coach/skills/enterprise-roadmap/SKILL.md +8 -0
- package/plugins/job-coach/skills/enterprise-task-analysis/SKILL.md +8 -0
- package/plugins/job-coach/skills/interview-to-profile/SKILL.md +18 -1
- package/scripts/backup/elanous-backup.sh +4 -1
- package/scripts/build-docs-site.ts +4 -3
- package/scripts/coord-channel-watch.sh +23 -4
- package/scripts/coord-post.sh +49 -12
- package/scripts/coord-tracks.json +5 -5
- package/scripts/daemon-port-baseline.txt +1 -2
- package/scripts/eval-bot-vs-grok.ts +79 -0
- package/scripts/eval-grok-live-baseline.ts +36 -0
- package/scripts/fixtures/front-route-corpus.sample.json +17 -0
- package/scripts/fleet/node-setup.sh +46 -7
- package/scripts/landing-heal/must-fix-match.ts +26 -0
- package/scripts/landing-heal/run-mission.ts +114 -20
- package/scripts/lib/android-test-device.ts +32 -0
- package/scripts/lib/front-route-measurement.ts +131 -0
- package/scripts/lib/tui-sim-keys.ts +42 -0
- package/scripts/measure-front-route-classifier.ts +56 -0
- package/scripts/media/ocr-leak-scan.sh +21 -0
- package/scripts/public-export.ts +2 -3
- package/scripts/public-import-pr.ts +80 -6
- package/scripts/publish-internal-dist.ts +2 -3
- package/scripts/red-sweep-diff.ts +1 -1
- package/scripts/release-loop/auto-approve-node.ts +72 -0
- package/scripts/release-loop/checklist-gate-node.ts +35 -0
- package/scripts/release-loop/gate-diff.ts +29 -0
- package/scripts/release-loop/gate-node.ts +366 -45
- package/scripts/release-loop/publish-node.ts +7 -3
- package/scripts/release-loop/release-docs.ts +20 -13
- package/scripts/release-loop/shard-plan.ts +47 -0
- package/scripts/release-loop/test-durations.ts +63 -0
- package/scripts/release-loop/upgrade-node.ts +3 -2
- package/scripts/test-audit-coverage-per-file.sh +16 -0
- package/scripts/test-audit-meaningless.ts +88 -0
- package/scripts/test-audit-overlap-and-weak.ts +73 -0
- package/scripts/test-deterministic.ts +27 -5
- package/scripts/tui-sim.ts +59 -14
- package/scripts/ux/bump-installed.sh +20 -0
- package/scripts/ux/lib/cdp.ts +72 -0
- package/scripts/ux/record-page.ts +53 -0
- package/skills/explainer-video/SKILL.md +117 -0
- package/skills/explainer-video/engine/brand/elanous-mark-on-dark.svg +1 -0
- package/skills/explainer-video/engine/build.mjs +431 -0
- package/skills/explainer-video/engine/reel.mjs +128 -0
- package/skills/explainer-video/engine/reel.sh +8 -0
- package/skills/explainer-video/engine/vo.ts +79 -0
- package/skills/explainer-video/examples/elanous-0.2.5/script.json +155 -0
- package/src/agent/nested-agent-env.ts +1 -0
- package/src/agent-mission/driver.ts +95 -16
- package/src/agent-mission/mission-cli.ts +6 -0
- package/src/agent-mission/resource-ladder.ts +73 -25
- package/src/auth/acp-token.ts +16 -0
- package/src/auth/github-app-token.ts +112 -0
- package/src/autopilot/gh-rest-fallback.ts +177 -0
- package/src/autopilot/mission-origin.ts +20 -4
- package/src/autopilot/mission-phase-track.ts +11 -1
- package/src/autopilot/pr-manager.ts +51 -4
- package/src/autopilot/track-agent.ts +3 -3
- package/src/autopilot/track-courier.ts +2 -2
- package/src/boot/acp-server.ts +2 -16
- package/src/boot/daemon-tools/web-search.ts +2 -1
- package/src/boot/ws-bridge.ts +11 -0
- package/src/chat/index.ts +37 -8
- package/src/chat/log-scroll-key.ts +12 -0
- package/src/chat/submit-trace.ts +30 -0
- package/src/cli/agent-cli.ts +7 -2
- package/src/cli/decisions-cli.ts +94 -0
- package/src/cli/doctor-cli.ts +40 -54
- package/src/cli/doctor-readiness.ts +16 -24
- package/src/cli/intake-cli.ts +1 -1
- package/src/cli/mcp-call.ts +9 -0
- package/src/cli/mcp-gateway-cli.ts +7 -3
- package/src/cli/nexus-ios-bind.ts +25 -8
- package/src/cli/phone-link.ts +117 -0
- package/src/cli/plugin-cli.ts +136 -11
- package/src/cli/pr-lineage.ts +2 -1
- package/src/cli/private-ledger-commands.ts +31 -0
- package/src/cli/pwa-start.ts +3 -0
- package/src/cli/release-cli.ts +97 -7
- package/src/cli/self-update.ts +7 -4
- package/src/cli/start-cli.ts +33 -14
- package/src/dashboard/acp-boot.ts +3 -0
- package/src/dashboard/index.ts +62 -13
- package/src/dashboard/input/no-provider-reply.ts +13 -0
- package/src/dashboard/slash-runtime/unknown-slash-reply.ts +78 -0
- package/src/dashboard/stale-install-check.ts +77 -0
- package/src/decisions/decision-ledger.ts +404 -0
- package/src/domains/outbound-alert.ts +13 -7
- package/src/domains/telegram-kind-route.ts +21 -0
- package/src/execution-loop/dispatch-task.ts +139 -0
- package/src/execution-loop/gate-decision-summary.ts +29 -0
- package/src/execution-loop/gates.ts +197 -0
- package/src/field/field-media.ts +245 -0
- package/src/graph-kinds/installed-plugin-nodes.ts +95 -0
- package/src/graph-kinds/plugin-nodes.ts +10 -2
- package/src/graph-runner/runner.ts +35 -5
- package/src/grounding/token.ts +12 -6
- package/src/harness/harness-cli-command.ts +68 -9
- package/src/harness/harness-pod-dispatch.ts +8 -2
- package/src/harness/harness-stop.ts +147 -18
- package/src/harness/self-send-decide.ts +26 -17
- package/src/harness/self-send-target.ts +22 -1
- package/src/harness/worktree-branch-prefix.ts +4 -3
- package/src/index.ts +50 -13
- package/src/instance/resolve.ts +6 -0
- package/src/intake-plane/collect-telegram-saved.ts +5 -0
- package/src/intake-plane/digest.ts +11 -2
- package/src/live/detail-switch.ts +8 -3
- package/src/llm/usable-llm.ts +32 -0
- package/src/loops/registry.ts +27 -5
- package/src/market/publish.ts +59 -3
- package/src/native-tool-catalog.ts +2 -2
- package/src/nexus/api/field-uploads.ts +143 -0
- package/src/nexus/api/graph-approvals.ts +174 -0
- package/src/nexus/api/harness-api.ts +219 -5
- package/src/nexus/api/http-server.ts +69 -6
- package/src/nexus/api/log-fabric.ts +10 -1
- package/src/nexus/api/mcp-oauth-hint.ts +10 -9
- package/src/nexus/api/merge-approvals.ts +2 -1
- package/src/nexus/api/meta-api.ts +14 -0
- package/src/nexus/api/plugins-credentials-api.ts +47 -0
- package/src/nexus/api/plugins-market.ts +87 -3
- package/src/nexus/api/pod-credential-api.ts +68 -14
- package/src/nexus/api/public-routes.ts +7 -0
- package/src/nexus/api/trace.ts +57 -6
- package/src/nexus/boot/register-mcp-clients.ts +99 -14
- package/src/nexus/index.ts +33 -32
- package/src/nexus/outbound/router.ts +1 -1
- package/src/notify/llm-budget-alert.ts +119 -0
- package/src/onboarding.ts +25 -2
- package/src/plugins/core/host.ts +14 -0
- package/src/plugins/install/market-fetch.ts +4 -3
- package/src/plugins/install/plugin-credentials.ts +106 -0
- package/src/plugins/install/plugin-install.ts +163 -32
- package/src/plugins/maker/plugin-maker.ts +208 -0
- package/src/prompt-library/universal-preamble.ts +8 -2
- package/src/relay/channel.ts +184 -0
- package/src/relay/mux.ts +182 -0
- package/src/relay/test-vectors.json +29 -0
- package/src/release-loop/checklist.ts +217 -0
- package/src/release-loop/release-note.ts +6 -3
- package/src/seat-address/seat-address.ts +34 -0
- package/src/self-dev/dev-cli.ts +40 -20
- package/src/self-dev/dev-pipeline.ts +77 -1
- package/src/self-implement/gate-baseline.ts +3 -1
- package/src/self-implement/goal-author.ts +18 -1
- package/src/self-implement/host-regate.ts +6 -2
- package/src/self-implement/orchestrator.ts +7 -2
- package/src/self-implement/run-ledger-compact.ts +1 -1
- package/src/self-implement/seams.ts +4 -2
- package/src/session/index.ts +23 -10
- package/src/skills/tools/dev-harness.ts +42 -0
- package/src/steward/steward-cards.ts +45 -0
- package/src/steward/triage.ts +50 -14
- package/src/task-orchestrator/surfaces/pod-artifact-return.ts +61 -3
- package/src/task-orchestrator/surfaces/pod-bun-cache.ts +18 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +36 -6
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +39 -9
- package/src/task-orchestrator/surfaces/pod-source-bundle.ts +16 -1
- package/src/task-orchestrator/surfaces/pod-source-receive.ts +51 -13
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +172 -28
- package/src/telegram-report.ts +12 -6
- package/src/telegram.ts +108 -1
- package/src/tui-client/acp-transport-ws-client.ts +72 -5
- package/src/tui.ts +3 -2
- package/src/user-config.ts +32 -2
- package/src/util/spawn-sync-output.ts +15 -0
- package/src/version/code-revision.ts +1 -1
- package/src/version/packed-revision.json +1 -1
- package/src/video-pipeline/recipes/explainer.ts +213 -0
- package/src/video-pipeline/walk-line.ts +3 -2
- package/src/web-search/free.ts +77 -0
- package/src/web-search/index.ts +2 -0
- package/src/workflow-runtime/deps-bridge.ts +4 -3
- package/src/workflow-runtime/plugin-kind-node.ts +32 -4
- package/src/workflow-runtime/types.ts +2 -1
- package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1191-7b13a24117a689fa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2375-3fcae5186c64fe6b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +0 -25
- package/apps/pwa/out/_next/static/chunks/319-d37df1b6e0cbeff4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4836-3c93795ab834381e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6090-1bd6077591603947.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7457-f5cb1cc8db9f7453.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8067-f74bf14d67ad98f1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8519-0059d82bdb4812bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9740-52467f244755917f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9804-64c02c3c127d5014.js +0 -1
- package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-21c3261af6ab377c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-1991445b8ea86274.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-4ebfaee2369fe248.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-9fa879ebf5959096.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-ca7c3cc305c3285a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-9951930add3d2051.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-4472ddf1e2458e69.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-4de08d4b766f134b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-7a975e2b9db1dfb7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-49270714ef35bb96.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-33fbdf353e2a5982.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-259f122e2dd64317.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-65b82726753658e1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-4b8df3748dc06285.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-112f7e477bda77fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-542a5c0006da637c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-63ffb347831262ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-98e4e359d97263b4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-97e24ac675ce576e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-ac6391f26391d89d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-9f45c9d45ffc43bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-36995b9cb5d98218.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-0c795b577988d63f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b4311e2f71c6735a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-eb2381074df20dba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-b01336e64eba01ae.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-7af7f21c1f2858ef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-a98ecd364586c89b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-57f0502dfbfa089b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-3467d2e1abd8a14a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-7842ca23d53ba682.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-ae9ac882912b0198.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-3a0d72e3f783bcef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-f3b39fe8358c2a97.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-5a22af3181ac2afb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-1457627bb302a22c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +0 -1
- package/apps/pwa/out/_next/static/css/e66b7f5efc8df3f3.css +0 -1
- /package/apps/pwa/out/_next/static/{zoQX5iTvXOsIvCjxCe1Ji → qMI0jnlZaBjjm4aIDYNGM}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Explainer VO — one voice file per beat plus per-character times; the times drive caption & highlight timing.
|
|
2
|
+
// Usage: ELEVENLABS_API_KEY=… bun vo.ts <project>/script.json (ElevenLabs /with-timestamps — exact word times)
|
|
3
|
+
// bun vo.ts <project>/script.json (no key → macOS `say`, times spread evenly — a draft voice)
|
|
4
|
+
// Writes source/vo/<key>.mp3 ⊕ <key>.json (alignment ⊕ text hash ⊕ engine). Skips a beat whose hash is unchanged. Never prints the key.
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { createHash } from 'node:crypto';
|
|
7
|
+
import { spawnSync } from 'node:child_process';
|
|
8
|
+
import { join, dirname, resolve } from 'node:path';
|
|
9
|
+
|
|
10
|
+
const KEY = process.env.ELEVENLABS_API_KEY?.trim();
|
|
11
|
+
const scriptPath = resolve(process.argv[2] ?? 'script.json');
|
|
12
|
+
const script = JSON.parse(readFileSync(scriptPath, 'utf8'));
|
|
13
|
+
const dir = join(dirname(scriptPath), 'source', 'vo');
|
|
14
|
+
mkdirSync(dir, { recursive: true });
|
|
15
|
+
|
|
16
|
+
export function beatKeys(s: any): { key: string; vo: string }[] {
|
|
17
|
+
const out: { key: string; vo: string }[] = [];
|
|
18
|
+
s.intro.beats.forEach((b: any, i: number) => out.push({ key: `i-${i}`, vo: b.vo }));
|
|
19
|
+
s.chapters.forEach((c: any, ci: number) => c.beats.forEach((b: any, i: number) => out.push({ key: `c${ci + 1}-${i}`, vo: b.vo })));
|
|
20
|
+
s.outro.beats.forEach((b: any, i: number) => out.push({ key: `o-${i}`, vo: b.vo }));
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type Alignment = { characters: string[]; character_start_times_seconds: number[]; character_end_times_seconds: number[] };
|
|
25
|
+
|
|
26
|
+
async function elevenlabs(vo: string): Promise<{ mp3: Buffer; alignment: Alignment }> {
|
|
27
|
+
const res = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${script.voice.id}/with-timestamps?output_format=mp3_44100_192`, {
|
|
28
|
+
method: 'POST',
|
|
29
|
+
headers: { 'xi-api-key': KEY!, 'content-type': 'application/json' },
|
|
30
|
+
body: JSON.stringify({ text: vo, model_id: script.voice.model, language_code: 'ko' }),
|
|
31
|
+
});
|
|
32
|
+
if (!res.ok) throw new Error(`ElevenLabs HTTP ${res.status} ${(await res.text()).slice(0, 200)}`);
|
|
33
|
+
const data = await res.json() as { audio_base64: string; alignment: Alignment };
|
|
34
|
+
return { mp3: Buffer.from(data.audio_base64, 'base64'), alignment: data.alignment };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Keyless draft voice: macOS `say` → mp3 · character times spread in proportion over the measured length. */
|
|
38
|
+
function sayVoice(): string {
|
|
39
|
+
const want = process.env.EXPLAINER_SAY_VOICE;
|
|
40
|
+
if (want) return want;
|
|
41
|
+
const list = spawnSync('say', ['-v', '?'], { encoding: 'utf8' });
|
|
42
|
+
if (list.status !== 0) throw new Error('no ELEVENLABS_API_KEY and `say` is unavailable (macOS only) — set the key via the plugin connector');
|
|
43
|
+
const ko = list.stdout.split('\n').filter((l) => /\bko_KR\b/.test(l)).map((l) => l.split(/\s{2,}|\s+\(/)[0].trim());
|
|
44
|
+
const pick = ko.find((v) => /^Yuna/.test(v)) ?? ko[0];
|
|
45
|
+
if (!pick) throw new Error('no Korean `say` voice installed — System Settings › Accessibility › Spoken Content, or set ELEVENLABS_API_KEY');
|
|
46
|
+
return pick;
|
|
47
|
+
}
|
|
48
|
+
function say(vo: string, key: string): { mp3: Buffer; alignment: Alignment } {
|
|
49
|
+
const voice = sayVoice();
|
|
50
|
+
const aiff = join(dir, `${key}.tmp.aiff`), mp3 = join(dir, `${key}.tmp.mp3`);
|
|
51
|
+
const r = spawnSync('say', ['-v', voice, '-o', aiff, vo], { encoding: 'utf8' });
|
|
52
|
+
if (r.status !== 0) throw new Error(`say failed: ${r.stderr.slice(0, 200)}`);
|
|
53
|
+
const f = spawnSync('ffmpeg', ['-v', 'error', '-y', '-i', aiff, '-ar', '44100', '-b:a', '192k', mp3], { encoding: 'utf8' });
|
|
54
|
+
if (f.status !== 0) throw new Error(`ffmpeg failed: ${f.stderr.slice(0, 200)}`);
|
|
55
|
+
const p = spawnSync('ffprobe', ['-v', 'error', '-show_entries', 'format=duration', '-of', 'csv=p=0', mp3], { encoding: 'utf8' });
|
|
56
|
+
const dur = Number(p.stdout.trim());
|
|
57
|
+
if (!(dur > 0)) throw new Error('ffprobe could not measure the say output');
|
|
58
|
+
const chars = [...vo];
|
|
59
|
+
const weight = chars.map((c) => (/\s/.test(c) ? 0.3 : /[,.·!?]/.test(c) ? 0.8 : 1));
|
|
60
|
+
const total = weight.reduce((a, b) => a + b, 0);
|
|
61
|
+
const starts: number[] = [], ends: number[] = [];
|
|
62
|
+
let t = 0;
|
|
63
|
+
for (const w of weight) { starts.push(+t.toFixed(3)); t += (w / total) * dur; ends.push(+t.toFixed(3)); }
|
|
64
|
+
const out = readFileSync(mp3);
|
|
65
|
+
rmSync(aiff, { force: true }); rmSync(mp3, { force: true });
|
|
66
|
+
return { mp3: out, alignment: { characters: chars, character_start_times_seconds: starts, character_end_times_seconds: ends } };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const engine = KEY ? 'elevenlabs' : 'say';
|
|
70
|
+
if (!KEY) console.log('no ELEVENLABS_API_KEY — draft voice with macOS `say` (caption times are estimates)');
|
|
71
|
+
for (const { key, vo } of beatKeys(script)) {
|
|
72
|
+
const hash = createHash('sha256').update(`${engine === 'say' ? 'say|' : ''}${script.voice.id}|${script.voice.model}|${vo}`).digest('hex').slice(0, 16);
|
|
73
|
+
const jf = join(dir, `${key}.json`);
|
|
74
|
+
if (existsSync(jf) && JSON.parse(readFileSync(jf, 'utf8')).hash === hash) { console.log(`${key} cached`); continue; }
|
|
75
|
+
const { mp3, alignment } = KEY ? await elevenlabs(vo) : say(vo, key);
|
|
76
|
+
writeFileSync(join(dir, `${key}.mp3`), mp3);
|
|
77
|
+
writeFileSync(jf, JSON.stringify({ hash, engine, vo, alignment }));
|
|
78
|
+
console.log(`${key} ${(alignment.character_end_times_seconds.at(-1) ?? 0).toFixed(2)}s ${engine} ${vo}`);
|
|
79
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "엘라누스 0.2.5 — 다섯 가지 한 번에",
|
|
3
|
+
"voice": {
|
|
4
|
+
"id": "ksaI0TCD9BstzEzlxj4q",
|
|
5
|
+
"name": "Seulki",
|
|
6
|
+
"model": "eleven_v3"
|
|
7
|
+
},
|
|
8
|
+
"sources": "docs/marketing (LAUNCH-0.2.5 초안) · PR #21849 #21887 #21878 #21855 #22054 #21908 #21995 #22001 — 전부 컷 e24cb6e 안 MERGED",
|
|
9
|
+
"intro": {
|
|
10
|
+
"beats": [
|
|
11
|
+
{
|
|
12
|
+
"vo": "엘라누스 0.2.5, 다섯 가지로 정리해 드립니다.",
|
|
13
|
+
"cap": "엘라누스 0.2.5, **다섯 가지**로 정리합니다"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
"chapters": [
|
|
18
|
+
{
|
|
19
|
+
"title": "미션 전 플러그인 자기 설치",
|
|
20
|
+
"sub": "모자란 것을 먼저 따진다",
|
|
21
|
+
"diagram": "mission",
|
|
22
|
+
"beats": [
|
|
23
|
+
{
|
|
24
|
+
"vo": "첫 번째는 플러그인 자기 설치입니다.",
|
|
25
|
+
"cap": "첫 번째, **플러그인 자기 설치**"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"vo": "미션을 보내기 전에, 엘라누스가 무엇이 모자란지 먼저 따집니다.",
|
|
29
|
+
"cap": "보내기 전에 **무엇이 모자란지** 먼저 따진다"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"vo": "실제 첫 판에서는 플러그인 이름을 주지 않았는데, 엘라누스 베이직스를 골라 코딩 에이전트 화면 안에서 설치했습니다.",
|
|
33
|
+
"cap": "이름을 안 줬는데 **elanous-basics**를 골라 설치",
|
|
34
|
+
"hl": [
|
|
35
|
+
[
|
|
36
|
+
"elanous-basics",
|
|
37
|
+
"베이직스"
|
|
38
|
+
]
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"vo": "사람이 챙겨 주던 도구를, 에이전트가 먼저 챙기는 거죠.",
|
|
43
|
+
"cap": "도구를 **에이전트가 먼저** 챙긴다"
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"title": "plugin add",
|
|
49
|
+
"sub": "이름@마켓 한 줄",
|
|
50
|
+
"diagram": "terminal",
|
|
51
|
+
"beats": [
|
|
52
|
+
{
|
|
53
|
+
"vo": "두 번째는 플러그인 애드 명령입니다.",
|
|
54
|
+
"cap": "두 번째, **plugin add**",
|
|
55
|
+
"hl": [
|
|
56
|
+
[
|
|
57
|
+
"plugin add",
|
|
58
|
+
"애드"
|
|
59
|
+
]
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"vo": "이름과 마켓만 적으면 됩니다.",
|
|
64
|
+
"cap": "**이름@마켓**만 적는다",
|
|
65
|
+
"hl": [
|
|
66
|
+
[
|
|
67
|
+
"이름@마켓",
|
|
68
|
+
"마켓"
|
|
69
|
+
]
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"vo": "색인 서명과 패키지 해시를 확인한 뒤에만 설치합니다.",
|
|
74
|
+
"cap": "**서명**과 **해시**를 확인한 뒤에만 설치"
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"title": "편집기 플러그인 노드",
|
|
80
|
+
"sub": "입력 폼이 저절로",
|
|
81
|
+
"diagram": "nodes",
|
|
82
|
+
"beats": [
|
|
83
|
+
{
|
|
84
|
+
"vo": "세 번째는 워크플로 편집기의 플러그인 노드입니다.",
|
|
85
|
+
"cap": "세 번째, **플러그인 노드**"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"vo": "플러그인이 노드를 더하고,",
|
|
89
|
+
"cap": "플러그인이 **노드를 더하고**"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"vo": "입력 폼은 노드의 입력 정의에서 저절로 만들어집니다.",
|
|
93
|
+
"cap": "입력 폼은 **저절로** 만들어진다"
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"title": "플러그인별 자격 키",
|
|
99
|
+
"sub": "키는 그 플러그인에게만",
|
|
100
|
+
"diagram": "keys",
|
|
101
|
+
"beats": [
|
|
102
|
+
{
|
|
103
|
+
"vo": "네 번째는 플러그인별 자격 키입니다.",
|
|
104
|
+
"cap": "네 번째, **플러그인별 자격 키**"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"vo": "키를 넣을 문이 생겼고,",
|
|
108
|
+
"cap": "키를 **넣을 문**이 생겼고"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"vo": "그 키는 그 플러그인에게만 전달됩니다.",
|
|
112
|
+
"cap": "키는 **그 플러그인에게만** 간다"
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"title": "승인 화면",
|
|
118
|
+
"sub": "웹 앱에서 승인·거절",
|
|
119
|
+
"diagram": "approve",
|
|
120
|
+
"beats": [
|
|
121
|
+
{
|
|
122
|
+
"vo": "다섯 번째는 웹 앱의 승인 화면입니다.",
|
|
123
|
+
"cap": "다섯 번째, **승인 화면**"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"vo": "릴리스나 공지처럼 사람의 승인에서 멈춘 런을,",
|
|
127
|
+
"cap": "사람의 승인에서 **멈춘 런**을"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"vo": "웹 앱에서 바로 승인하거나 거절합니다.",
|
|
131
|
+
"cap": "웹 앱에서 바로 **승인·거절**"
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
|
+
],
|
|
136
|
+
"outro": {
|
|
137
|
+
"beats": [
|
|
138
|
+
{
|
|
139
|
+
"vo": "정리하면, 이번 판의 핵심은 이것입니다.",
|
|
140
|
+
"cap": "이번 판의 **핵심**"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"vo": "필요한 도구는 에이전트가 스스로 챙기고, 사람은 승인합니다.",
|
|
144
|
+
"cap": "도구는 **에이전트가 스스로**, 사람은 **승인**"
|
|
145
|
+
}
|
|
146
|
+
],
|
|
147
|
+
"cta": {
|
|
148
|
+
"line": "Elanous by Elanvital AI",
|
|
149
|
+
"links": [
|
|
150
|
+
"elanous.ai",
|
|
151
|
+
"github.com/ElanvitalAI/elanous"
|
|
152
|
+
]
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// 재사용: startPty(Bun 네이티브 PTY)·createWorktree·streamLLM·worktreeHasChanges·commitWorktree.
|
|
10
10
|
import { execFileSync, spawnSync, spawn as spawnChild } from 'node:child_process';
|
|
11
11
|
import { homedir } from 'node:os';
|
|
12
|
-
import { existsSync, writeFileSync, mkdirSync, readdirSync, readFileSync, statSync, readlinkSync, realpathSync, lstatSync } from 'node:fs';
|
|
12
|
+
import { existsSync, writeFileSync, mkdirSync, readdirSync, readFileSync, statSync, readlinkSync, realpathSync, lstatSync, unlinkSync } from 'node:fs';
|
|
13
13
|
import { createHash } from 'node:crypto';
|
|
14
14
|
import { join, relative, resolve, isAbsolute, sep } from 'node:path';
|
|
15
15
|
import { startPty, mintPtyId, onPtyEvent, type PtyHandle, type StartOpts } from '../pty-shell/registry.js';
|
|
@@ -34,8 +34,10 @@ import { runWithControlObserve } from '../capture/control-observe-adapter.js';
|
|
|
34
34
|
import { buildMissionProvision, sanitizeForPtyInput, type ProvisionRequest, type ProvisionResult } from './provision.js';
|
|
35
35
|
import { checkClaudeSubscription } from './claude-subscription.js';
|
|
36
36
|
import { handoffMission, waitForHumanLogin, type HandoffOperations } from './handoff.js';
|
|
37
|
+
import { dispatchOpenPullRequest } from '../tool-runtime/git-pr-runtime.js';
|
|
37
38
|
import { runClaudeHeadless } from './claude-headless.js';
|
|
38
39
|
import { installPluginInsideAgent, type PluginRequest, type PluginDeps } from './in-agent-plugin.js';
|
|
40
|
+
import { planMissionResources, type ResourceLadderDeps, type ResourcePlan } from './resource-ladder.js';
|
|
39
41
|
import { ensureRunId } from '../harness/harness-space.js';
|
|
40
42
|
import { recordHarnessWorktreeProvenance } from '../harness/harness-worktree-add.js';
|
|
41
43
|
import { resolveInstanceName } from '../instance-identity.js';
|
|
@@ -459,6 +461,10 @@ export interface AgentMissionSpec {
|
|
|
459
461
|
headless?: boolean;
|
|
460
462
|
/** Install in the agent's own UI before sending the mission. */
|
|
461
463
|
plugin?: Pick<PluginRequest, 'plugin' | 'marketplace'>;
|
|
464
|
+
/** Pre-mission resource judgment; off skips it. */
|
|
465
|
+
resources?: 'on' | 'off';
|
|
466
|
+
/** Only the caller's explicit backend selection, not the resolved default. */
|
|
467
|
+
backendExplicit?: string;
|
|
462
468
|
/** Explicit backend sequence; transitions use the same handoff coordinator as brain decisions. */
|
|
463
469
|
chain?: readonly ('codex' | 'claude' | 'elanous')[];
|
|
464
470
|
/**
|
|
@@ -496,7 +502,8 @@ export interface AgentMissionResult {
|
|
|
496
502
|
ok: boolean;
|
|
497
503
|
worktree: string;
|
|
498
504
|
branch: string | null;
|
|
499
|
-
reason?: 'aborted' | 'outside-write';
|
|
505
|
+
reason?: 'aborted' | 'outside-write' | 'origin-not-github';
|
|
506
|
+
pr?: 'skipped';
|
|
500
507
|
paths?: string[];
|
|
501
508
|
rounds: number;
|
|
502
509
|
evidencePath: string | null;
|
|
@@ -526,6 +533,7 @@ export interface AgentMissionDeps {
|
|
|
526
533
|
runClaudeHeadless?: typeof runClaudeHeadless;
|
|
527
534
|
checkEvidence?: typeof checkEvidence;
|
|
528
535
|
commitWorktree?: typeof commitWorktree;
|
|
536
|
+
dispatchOpenPullRequest?: typeof dispatchOpenPullRequest;
|
|
529
537
|
terminateProcessTree?: typeof terminateMissionProcessTree;
|
|
530
538
|
repoStatus?: (repo: string) => string | Promise<string>;
|
|
531
539
|
emitDecision?: typeof emitDecision;
|
|
@@ -546,6 +554,8 @@ export interface AgentMissionDeps {
|
|
|
546
554
|
initialTsc?: ReturnType<typeof collectTscDiagnostics> | null;
|
|
547
555
|
installPlugin?: typeof installPluginInsideAgent;
|
|
548
556
|
pluginDeps?: PluginDeps;
|
|
557
|
+
resourceLadder?: ResourceLadderDeps;
|
|
558
|
+
planMissionResources?: typeof planMissionResources;
|
|
549
559
|
}
|
|
550
560
|
|
|
551
561
|
const DEFAULT_OMNI = `${process.env.HOME}/.claude/skills/omni-crawl/scripts/main.ts`;
|
|
@@ -1644,16 +1654,55 @@ async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDep
|
|
|
1644
1654
|
screen = await (deps.awaitMission ? deps.awaitMission(trusted) : trusted);
|
|
1645
1655
|
}
|
|
1646
1656
|
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
+
let resourcePlan: ResourcePlan | undefined;
|
|
1658
|
+
// Only an explicit `on` runs the ladder (the CLI passes `on` by default). Library callers and fake-PTY
|
|
1659
|
+
// tests that leave it unset keep the pre-ladder behaviour — 09-29 #21849 made them wait on a real LLM.
|
|
1660
|
+
if (spec.resources === 'on') {
|
|
1661
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
1662
|
+
try {
|
|
1663
|
+
resourcePlan = await Promise.race([
|
|
1664
|
+
(deps.planMissionResources ?? planMissionResources)(spec.mission, deps.resourceLadder ?? {}, {
|
|
1665
|
+
backend: spec.backendExplicit,
|
|
1666
|
+
plugin: spec.plugin ? `${spec.plugin.plugin}@${spec.plugin.marketplace}` : undefined,
|
|
1667
|
+
resources: spec.resources,
|
|
1668
|
+
deadlineMs: 20_000,
|
|
1669
|
+
}),
|
|
1670
|
+
new Promise<never>((_resolve, reject) => { timer = setTimeout(() => reject(new Error('resource ladder timeout (20s)')), 20_000); }),
|
|
1671
|
+
]);
|
|
1672
|
+
debug.log('agent-mission.resources', 'planned', {
|
|
1673
|
+
have: resourcePlan.have.length, gaps: resourcePlan.gaps.length,
|
|
1674
|
+
suggestedPlugin: resourcePlan.plugin?.plugin, suggestedBackend: resourcePlan.backend?.name,
|
|
1675
|
+
executedBackend: backend.name,
|
|
1676
|
+
});
|
|
1677
|
+
} catch (error) {
|
|
1678
|
+
debug.log('agent-mission.resources', 'plan-failed', { reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' });
|
|
1679
|
+
} finally { if (timer) clearTimeout(timer); }
|
|
1680
|
+
}
|
|
1681
|
+
const suggestedPlugin = resourcePlan?.plugin?.source === 'official-index' && !spec.plugin
|
|
1682
|
+
&& !spec.headless && (backend.name === 'codex' || backend.name === 'claude')
|
|
1683
|
+
&& resourcePlan.plugin.marketplace === 'elanous'
|
|
1684
|
+
&& /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(resourcePlan.plugin.plugin)
|
|
1685
|
+
? { plugin: resourcePlan.plugin.plugin, marketplace: resourcePlan.plugin.marketplace } : undefined;
|
|
1686
|
+
const requestedPlugin = spec.plugin ?? suggestedPlugin;
|
|
1687
|
+
let installedPlugin: typeof requestedPlugin;
|
|
1688
|
+
if (requestedPlugin) {
|
|
1689
|
+
try {
|
|
1690
|
+
const plugin = await (deps.installPlugin ?? installPluginInsideAgent)(
|
|
1691
|
+
{ agent: backend.name as 'codex' | 'claude', ...requestedPlugin }, h,
|
|
1692
|
+
{ ...(backend.handleTrust ? { handleTrust: backend.handleTrust } : {}), ...deps.pluginDeps, write: drive },
|
|
1693
|
+
);
|
|
1694
|
+
if (plugin.outcome !== 'installed') {
|
|
1695
|
+
if (spec.plugin) {
|
|
1696
|
+
stopLive();
|
|
1697
|
+
try { h.kill(); } catch { /* already exited */ }
|
|
1698
|
+
return { ok: false, worktree: wt.path, branch: wt.branch, rounds: 0, evidencePath: null,
|
|
1699
|
+
committed: false, usedOmniCrawl: false, detail: `플러그인 설치 ESCALATE (${plugin.reason}) — 미션은 전송하지 않았다` };
|
|
1700
|
+
}
|
|
1701
|
+
debug.log('agent-mission.resources', 'install-failed', { plugin: requestedPlugin.plugin, reason: plugin.reason }, { level: 'warn' });
|
|
1702
|
+
} else installedPlugin = requestedPlugin;
|
|
1703
|
+
} catch (error) {
|
|
1704
|
+
if (spec.plugin) throw error;
|
|
1705
|
+
debug.log('agent-mission.resources', 'install-failed', { plugin: requestedPlugin.plugin, reason: 'installer-error' }, { level: 'warn' });
|
|
1657
1706
|
}
|
|
1658
1707
|
}
|
|
1659
1708
|
|
|
@@ -1683,7 +1732,15 @@ async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDep
|
|
|
1683
1732
|
else debug.log('agent-mission', 'memory', { injected: false });
|
|
1684
1733
|
}
|
|
1685
1734
|
|
|
1686
|
-
if (
|
|
1735
|
+
if (installedPlugin) missionText = `${missionText}\n\n설치된 ${installedPlugin.plugin} 플러그인에서 실제 스킬을 확인하고 ${installedPlugin.plugin}:<skill> 형식으로 해당 스킬을 사용하라.`;
|
|
1736
|
+
if (resourcePlan) {
|
|
1737
|
+
const safeName = (name: string) => sanitizeForPtyInput(name, 80)
|
|
1738
|
+
.replace(/\b(?:bearer|secret|password|token|api.?key|authorization)\s*[:=]\s*\S+|(?:sk-[\w-]+|gh[pousr]_[\w-]+|github_pat_[\w-]+|AIza[\w-]+|xox[baprs]-[\w-]+)/gi, '[redacted]')
|
|
1739
|
+
.replace(/https?:\/\/\S+/gi, '[redacted]');
|
|
1740
|
+
const have = resourcePlan.have.slice(0, 5).map(safeName);
|
|
1741
|
+
const gaps = resourcePlan.gaps.slice(0, 5).map(gap => safeName(gap.need));
|
|
1742
|
+
missionText = `${missionText}\n\n[elanous 자원]\n보유: ${have.join(', ') || '없음'}\n설치: ${installedPlugin ? `${installedPlugin.plugin}@${installedPlugin.marketplace}` : '없음'}\n미충족: ${gaps.join(', ') || '없음'}`;
|
|
1743
|
+
}
|
|
1687
1744
|
if (deps.isMissionAborted?.()) throw new Error('MISSION_ABORTED');
|
|
1688
1745
|
// 미션 전송 — 인핸싱/기억으로 가공됐으면(멀티라인) 파일로 떨궈 read 지시(TUI 멀티라인 위험·verbatim 보존).
|
|
1689
1746
|
// 원문 그대로면(단문) 타이핑.
|
|
@@ -1738,6 +1795,7 @@ async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDep
|
|
|
1738
1795
|
};
|
|
1739
1796
|
let pendingHandoff: { to: 'codex' | 'claude'; mission: string; summary: string } | undefined;
|
|
1740
1797
|
let gatedHandoff = false;
|
|
1798
|
+
let handoffPrSkipped = false;
|
|
1741
1799
|
let transitionFailure: string | undefined;
|
|
1742
1800
|
const handoffOps: HandoffOperations = {
|
|
1743
1801
|
start: async (to, mission) => { pendingHandoff = { to, mission, summary: controllerSummary }; },
|
|
@@ -1765,11 +1823,30 @@ async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDep
|
|
|
1765
1823
|
if (spec.commit === false) throw new Error('elanous PR handoff requires commit authorization');
|
|
1766
1824
|
const ev = (deps.checkEvidence ?? evidenceCheck)(worktree, spec.evidence);
|
|
1767
1825
|
if (!ev.ok) throw new Error(`handoff evidence gate failed: ${ev.retry ?? 'missing evidence'}`);
|
|
1826
|
+
const promptFile = join(worktree, '.mission-prompt.md');
|
|
1827
|
+
let promptExisted = false;
|
|
1828
|
+
try { lstatSync(promptFile); promptExisted = true; } catch (error) {
|
|
1829
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
|
1830
|
+
}
|
|
1831
|
+
if (promptExisted) unlinkSync(promptFile);
|
|
1832
|
+
debug.log('agent-mission', 'handoff-prompt-file-removed', { existed: promptExisted });
|
|
1833
|
+
const stagedPrompt = execFileSync('git', ['ls-files', '--cached', '--', '.mission-prompt.md'], { cwd: worktree, encoding: 'utf8' });
|
|
1834
|
+
if (stagedPrompt.trim()) {
|
|
1835
|
+
execFileSync('git', ['rm', '-f', '--cached', '--', '.mission-prompt.md'], { cwd: worktree, encoding: 'utf8' });
|
|
1836
|
+
}
|
|
1837
|
+
if (execFileSync('git', ['ls-files', '--cached', '--', '.mission-prompt.md'], { cwd: worktree, encoding: 'utf8' }).trim()) {
|
|
1838
|
+
throw new Error('handoff prompt file remains in commit index');
|
|
1839
|
+
}
|
|
1768
1840
|
const commit = (deps.commitWorktree ?? commitWorktree)(worktree, `chore(agent-mission): ${spec.branch} handoff`);
|
|
1769
1841
|
if (!commit.ok) throw new Error(`handoff commit failed: ${commit.out}`);
|
|
1770
1842
|
execFileSync('git', ['push', '-u', 'origin', `HEAD:${spec.branch}`], { cwd: worktree, encoding: 'utf8', timeout: 120_000 });
|
|
1771
|
-
const
|
|
1772
|
-
|
|
1843
|
+
const pushUrls = execFileSync('git', ['remote', 'get-url', '--push', '--all', 'origin'], { cwd: worktree, encoding: 'utf8' }).trim().split('\n');
|
|
1844
|
+
if (!pushUrls.length || !pushUrls.every((url) => /^(?:https:\/\/github\.com\/|ssh:\/\/git@github\.com\/|git@github\.com:)[^/]+\/[^/]+\/?$/i.test(url))) {
|
|
1845
|
+
handoffPrSkipped = true;
|
|
1846
|
+
debug.log('agent-mission', 'handoff-pr-skipped', { reason: 'origin-not-github' });
|
|
1847
|
+
} else {
|
|
1848
|
+
(deps.dispatchOpenPullRequest ?? dispatchOpenPullRequest)({ title: `agent-mission: ${spec.branch}`, body: deps.originalMission ?? spec.mission, head: spec.branch, base: spec.base ?? 'main' }, { cwd: worktree });
|
|
1849
|
+
}
|
|
1773
1850
|
gatedHandoff = true;
|
|
1774
1851
|
},
|
|
1775
1852
|
...deps.handoffOperations,
|
|
@@ -1879,7 +1956,9 @@ async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDep
|
|
|
1879
1956
|
if (gatedHandoff && control.termination.kind !== 'error') {
|
|
1880
1957
|
stopLive();
|
|
1881
1958
|
try { h.kill(); } catch { /* noop */ }
|
|
1882
|
-
return { ok: true, worktree: wt.path, branch: wt.branch, rounds: control.steps, evidencePath: wt.path, committed: true, usedOmniCrawl: usedOmni,
|
|
1959
|
+
return { ok: true, worktree: wt.path, branch: wt.branch, rounds: control.steps, evidencePath: wt.path, committed: true, usedOmniCrawl: usedOmni,
|
|
1960
|
+
...(handoffPrSkipped ? { pr: 'skipped' as const, reason: 'origin-not-github' as const } : {}),
|
|
1961
|
+
detail: handoffPrSkipped ? '완료(증거 게이트·push·PR 생략)' : '완료(증거 게이트·PR)' };
|
|
1883
1962
|
}
|
|
1884
1963
|
const done = control.termination.kind === 'success';
|
|
1885
1964
|
const round = control.steps;
|
|
@@ -18,6 +18,7 @@ export interface MissionCliOpts {
|
|
|
18
18
|
chain?: string;
|
|
19
19
|
headless?: boolean;
|
|
20
20
|
plugin?: string;
|
|
21
|
+
resources?: 'on' | 'off';
|
|
21
22
|
evidence?: string; // 'doc' | 'tsc' | 'test'
|
|
22
23
|
docDir?: string;
|
|
23
24
|
docGlob?: string;
|
|
@@ -61,6 +62,9 @@ export async function runAgentMissionCliCommand(
|
|
|
61
62
|
if (opts.plugin !== undefined && (!plugin || !['codex', 'claude'].includes(agentBackend.name) || opts.headless)) {
|
|
62
63
|
return { ok: false, message: '--plugin 은 codex/claude 대화형 backend 에서 <name>@<market> 형식이어야 합니다', exitCode: 1 };
|
|
63
64
|
}
|
|
65
|
+
if (opts.resources !== undefined && opts.resources !== 'on' && opts.resources !== 'off') {
|
|
66
|
+
return { ok: false, message: '--resources 는 on|off 여야 합니다', exitCode: 1 };
|
|
67
|
+
}
|
|
64
68
|
|
|
65
69
|
// ── mission 텍스트(verbatim: --mission-file 우선) ──
|
|
66
70
|
const readFile = deps.readFile ?? ((p: string) => readFileSync(p, 'utf8'));
|
|
@@ -97,6 +101,8 @@ export async function runAgentMissionCliCommand(
|
|
|
97
101
|
backend: agentBackend.name,
|
|
98
102
|
...(chain ? { chain: chain as ('codex' | 'claude' | 'elanous')[] } : {}),
|
|
99
103
|
...(plugin ? { plugin } : {}),
|
|
104
|
+
resources: opts.resources ?? 'on',
|
|
105
|
+
...(opts.backend ? { backendExplicit: opts.backend } : {}),
|
|
100
106
|
...(opts.headless ? { headless: true } : {}),
|
|
101
107
|
branch: opts.branch,
|
|
102
108
|
...(opts.base ? { base: opts.base } : {}),
|
|
@@ -15,6 +15,7 @@ import { ELANOUS_MARKET_URL } from '../plugins/install/market-fetch.js';
|
|
|
15
15
|
const MARKETPLACE = 'elanous';
|
|
16
16
|
const INDEX_URL = new URL('marketplace.json', ELANOUS_MARKET_URL).href; // one source of truth for the official market
|
|
17
17
|
const MAX_NEEDS = 5;
|
|
18
|
+
const MAX_VOCABULARY = 200;
|
|
18
19
|
|
|
19
20
|
export interface ResourcePlan {
|
|
20
21
|
needs: string[];
|
|
@@ -29,11 +30,13 @@ export interface MissionResources {
|
|
|
29
30
|
readonly backend?: string;
|
|
30
31
|
readonly plugin?: string;
|
|
31
32
|
readonly resources?: 'off' | 'on';
|
|
33
|
+
/** Total planner budget in milliseconds; discovery uses only the remaining time. */
|
|
34
|
+
readonly deadlineMs?: number;
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
export interface ResourceLadderDeps extends MissionResources {
|
|
35
38
|
/** One JSON-producing LLM call; the raw mission is never sent to logs. */
|
|
36
|
-
readonly inferNeeds?: (safeMission: string) => Promise<string>;
|
|
39
|
+
readonly inferNeeds?: (safeMission: string, vocabulary: readonly string[]) => Promise<string>;
|
|
37
40
|
readonly readers?: CapabilityReaders;
|
|
38
41
|
/** Read the official index and its detached signature as a pair; no other market is consulted. */
|
|
39
42
|
readonly readOfficialIndex?: () => Promise<{ marketplaceBytes: Uint8Array; signatureText: string }>;
|
|
@@ -60,22 +63,25 @@ function safeNeed(value: unknown): string | null {
|
|
|
60
63
|
return need;
|
|
61
64
|
}
|
|
62
65
|
|
|
63
|
-
function parseNeeds(raw: string): string[] {
|
|
66
|
+
function parseNeeds(raw: string, vocabulary: readonly string[]): string[] {
|
|
64
67
|
let parsed: unknown;
|
|
65
68
|
try { parsed = JSON.parse(raw); } catch { return []; }
|
|
66
69
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed) || !Array.isArray((parsed as { needs?: unknown }).needs)) return [];
|
|
67
70
|
const needs: string[] = [];
|
|
68
71
|
for (const item of (parsed as { needs: unknown[] }).needs) {
|
|
69
|
-
const
|
|
70
|
-
|
|
72
|
+
const label = typeof item === 'string' ? vocabulary.find(value => value === item) : undefined;
|
|
73
|
+
const need = label ?? safeNeed(item);
|
|
74
|
+
if (need && !needs.some(existing => normalizeServiceName(existing) === normalizeServiceName(need))) {
|
|
75
|
+
needs.push(label ?? vocabulary.find(value => normalizeServiceName(value) === normalizeServiceName(need)) ?? need);
|
|
76
|
+
}
|
|
71
77
|
if (needs.length === MAX_NEEDS) break;
|
|
72
78
|
}
|
|
73
79
|
return needs;
|
|
74
80
|
}
|
|
75
81
|
|
|
76
|
-
async function inferNeeds(mission: string): Promise<string> {
|
|
82
|
+
async function inferNeeds(mission: string, vocabulary: readonly string[]): Promise<string> {
|
|
77
83
|
return streamLLM([
|
|
78
|
-
{ role: 'system', content:
|
|
84
|
+
{ role: 'system', content: `Identify at most five concrete tool/service/skill capabilities required for this mission. Reply ONLY with JSON {"needs":["capability"]}. No credentials, tokens, URLs, or mission text in the output. If a relevant label appears in this verified official marketplace vocabulary, use that exact label; otherwise use a concrete capability. If uncertain, return an empty list.\nVerified labels: ${JSON.stringify(vocabulary)}` },
|
|
79
85
|
{ role: 'user', content: mission },
|
|
80
86
|
], () => {}, { maxTokens: 200, temperature: 0 });
|
|
81
87
|
}
|
|
@@ -104,20 +110,60 @@ function pluginLabels(plugin: MarketplaceIndex['plugins'][number]): string[] {
|
|
|
104
110
|
return [plugin.name, plugin.description ?? '', ...plugin['ai.elanous'].capabilities, ...(plugin['ai.elanous'].vocab ?? [])];
|
|
105
111
|
}
|
|
106
112
|
|
|
113
|
+
function officialVocabulary(index: MarketplaceIndex): string[] {
|
|
114
|
+
const labels: string[] = [];
|
|
115
|
+
for (const entry of index.plugins) {
|
|
116
|
+
for (const label of [entry.name, ...entry['ai.elanous'].capabilities, ...(entry['ai.elanous'].vocab ?? [])]) {
|
|
117
|
+
const safe = typeof label === 'string' && label === label.trim() && label.length <= 80
|
|
118
|
+
&& !/[\n\r{}<>@=/\\]/.test(label) && (safeMissionText(label) === label || /^secret:[a-zA-Z0-9._-]+$/.test(label)) ? label : null;
|
|
119
|
+
if (safe && !labels.includes(safe)) labels.push(safe);
|
|
120
|
+
if (labels.length === MAX_VOCABULARY) return labels;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return labels;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function mentionedPluginNames(mission: string, index: MarketplaceIndex): string[] {
|
|
127
|
+
return index.plugins.map(entry => entry.name).filter(name => {
|
|
128
|
+
if (!safeNeed(name) || safeMissionText(name) !== name || !/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(name)) return false;
|
|
129
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
130
|
+
return new RegExp(`(^|[^\\p{L}\\p{N}_.-])${escaped}(?=$|[^\\p{L}\\p{N}_.-])`, 'iu').test(mission);
|
|
131
|
+
}).slice(0, MAX_NEEDS);
|
|
132
|
+
}
|
|
133
|
+
|
|
107
134
|
/** Pre-mission resource judgment. Only the optional, injected installer can cause installation. */
|
|
108
135
|
export async function planMissionResources(mission: string, deps: ResourceLadderDeps = {}, options: MissionResources = {}): Promise<ResourcePlan> {
|
|
109
136
|
const plan: ResourcePlan = { needs: [], have: [], gaps: [], decisions: [] };
|
|
110
137
|
if (options.resources === 'off' || deps.resources === 'off') return plan;
|
|
111
138
|
const backend = options.backend ?? deps.backend;
|
|
112
139
|
const plugin = options.plugin ?? deps.plugin;
|
|
140
|
+
const budgetMs = options.deadlineMs ?? deps.deadlineMs;
|
|
141
|
+
const deadline = budgetMs === undefined ? undefined : Date.now() + budgetMs;
|
|
113
142
|
const decide = (step: string, kind: DecisionEvent['kind'], what: string, why: string) => {
|
|
114
143
|
plan.decisions.push(`${step}: ${what} — ${why}`);
|
|
115
144
|
(deps.decide ?? emitDecision)({ kind, what, reason: why, purpose: 'pre-mission resource selection', target: 'agent-mission', phase: 'dispatch' });
|
|
116
145
|
(deps.log ?? ((event, data) => debug.log('agent-mission.resources', event, data)))(step, { kind, what, why });
|
|
117
146
|
};
|
|
118
147
|
|
|
119
|
-
|
|
148
|
+
let index: MarketplaceIndex | undefined;
|
|
149
|
+
try {
|
|
150
|
+
const pair = await (deps.readOfficialIndex ?? readOfficialIndex)();
|
|
151
|
+
const checked = verifyIndex({ ...pair, trustedKeys: OFFICIAL_INDEX_KEYS });
|
|
152
|
+
if (checked.ok && checked.index.name === MARKETPLACE) index = checked.index;
|
|
153
|
+
else decide('official-index', 'ESCALATE', '공식 마켓 거부', '서명 또는 마켓 이름 검증 실패');
|
|
154
|
+
} catch { decide('official-index', 'ESCALATE', '공식 마켓 조회 실패', '서명 검증 가능한 원본 인덱스 없음'); }
|
|
155
|
+
const safeMission = safeMissionText(mission);
|
|
156
|
+
const vocabulary = index ? officialVocabulary(index) : [];
|
|
157
|
+
try { plan.needs = parseNeeds(await (deps.inferNeeds ?? inferNeeds)(safeMission, vocabulary), vocabulary); }
|
|
120
158
|
catch { decide('needs', 'ESCALATE', '능력 판정 불가', 'LLM 응답 실패 — 추측하지 않음'); }
|
|
159
|
+
const mentioned = index ? mentionedPluginNames(safeMission, index) : [];
|
|
160
|
+
for (const name of mentioned) {
|
|
161
|
+
if (!plan.needs.some(need => matches(need, [name]))) {
|
|
162
|
+
if (plan.needs.length === MAX_NEEDS) plan.needs.pop();
|
|
163
|
+
plan.needs.push(name);
|
|
164
|
+
}
|
|
165
|
+
decide('needs', 'VERIFY', name, '이름 언급');
|
|
166
|
+
}
|
|
121
167
|
decide('needs', 'VERIFY', '필요 능력 판정', `${plan.needs.length}개 확인`);
|
|
122
168
|
if (!plan.needs.length) return plan;
|
|
123
169
|
|
|
@@ -132,16 +178,7 @@ export async function planMissionResources(mission: string, deps: ResourceLadder
|
|
|
132
178
|
});
|
|
133
179
|
decide('have', 'VERIFY', 'ready 서비스 대조', `${plan.have.length}개 확인`);
|
|
134
180
|
|
|
135
|
-
|
|
136
|
-
if (remaining.length) {
|
|
137
|
-
try {
|
|
138
|
-
const pair = await (deps.readOfficialIndex ?? readOfficialIndex)();
|
|
139
|
-
const checked = verifyIndex({ ...pair, trustedKeys: OFFICIAL_INDEX_KEYS });
|
|
140
|
-
if (checked.ok && checked.index.name === MARKETPLACE) index = checked.index;
|
|
141
|
-
else decide('official-index', 'ESCALATE', '공식 마켓 거부', '서명 또는 마켓 이름 검증 실패');
|
|
142
|
-
} catch { decide('official-index', 'ESCALATE', '공식 마켓 조회 실패', '서명 검증 가능한 원본 인덱스 없음'); }
|
|
143
|
-
}
|
|
144
|
-
if (index) {
|
|
181
|
+
if (index && remaining.length) {
|
|
145
182
|
let installed: readonly { name: string; market: string }[] = [];
|
|
146
183
|
try { installed = (deps.readInstalledPlugins ?? installedPlugins)(); }
|
|
147
184
|
catch { decide('have', 'ESCALATE', '설치 원장 읽기 실패', '설치된 스킬을 보유로 간주하지 않음'); }
|
|
@@ -153,7 +190,8 @@ export async function planMissionResources(mission: string, deps: ResourceLadder
|
|
|
153
190
|
});
|
|
154
191
|
decide('have', 'VERIFY', '설치된 플러그인 스킬 대조', `${plan.have.length}개 확인`);
|
|
155
192
|
if (!plugin) {
|
|
156
|
-
const
|
|
193
|
+
const named = index.plugins.filter(entry => mentioned.includes(entry.name) && remaining.some(need => matches(need, [entry.name])));
|
|
194
|
+
const choices = named.length ? named : index.plugins.filter(entry => remaining.some(need => matches(need, pluginLabels(entry))));
|
|
157
195
|
if (choices.length === 1) {
|
|
158
196
|
plan.plugin = { plugin: choices[0]!.name, marketplace: MARKETPLACE, source: 'official-index' };
|
|
159
197
|
decide('official-index', 'ROUTE', '공식 플러그인 선택', '서명 검증된 인덱스에서 일치하는 플러그인 한 개');
|
|
@@ -169,13 +207,6 @@ export async function planMissionResources(mission: string, deps: ResourceLadder
|
|
|
169
207
|
} else decide('official-index', 'ROUTE', '명시 플러그인 보존', '호출자 선택을 덮지 않음');
|
|
170
208
|
}
|
|
171
209
|
|
|
172
|
-
for (const need of remaining) {
|
|
173
|
-
let candidates: CapabilityCandidate[] = [];
|
|
174
|
-
try { candidates = await (deps.discover ?? discoverCapability)(need, { community: false }); }
|
|
175
|
-
catch { decide('discover', 'ESCALATE', '발굴 실패', '후보 없음 — 자동 설치하지 않음'); }
|
|
176
|
-
plan.gaps.push({ need, candidates });
|
|
177
|
-
decide('discover', 'ESCALATE', '미충족 능력 후보', `${candidates.length}개 후보 제안만 함`);
|
|
178
|
-
}
|
|
179
210
|
if (!backend) {
|
|
180
211
|
const chosen = plan.needs.map(need => ({ need, name: pickBackend(matrix, need) })).find(row => row.name);
|
|
181
212
|
if (chosen?.name) {
|
|
@@ -183,5 +214,22 @@ export async function planMissionResources(mission: string, deps: ResourceLadder
|
|
|
183
214
|
decide('backend', 'ROUTE', 'ready 에이전트 선택', plan.backend.why);
|
|
184
215
|
} else decide('backend', 'ESCALATE', '에이전트 자동 선택 보류', '필요 서비스의 ready 관측 없음');
|
|
185
216
|
} else decide('backend', 'ROUTE', '명시 에이전트 보존', '호출자 backend 를 덮지 않음');
|
|
217
|
+
plan.gaps = await Promise.all(remaining.map(async need => {
|
|
218
|
+
let candidates: CapabilityCandidate[] = [];
|
|
219
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
220
|
+
const controller = new AbortController();
|
|
221
|
+
try {
|
|
222
|
+
const remainingMs = deadline === undefined ? undefined : Math.max(0, deadline - Date.now() - 50);
|
|
223
|
+
if (remainingMs === 0) throw new Error('discovery deadline');
|
|
224
|
+
const discovery = (deps.discover ?? discoverCapability)(need, { community: false, signal: controller.signal });
|
|
225
|
+
candidates = remainingMs === undefined ? await discovery : await Promise.race([
|
|
226
|
+
discovery,
|
|
227
|
+
new Promise<CapabilityCandidate[]>((_resolve, reject) => { timer = setTimeout(() => reject(new Error('discovery deadline')), remainingMs); }),
|
|
228
|
+
]);
|
|
229
|
+
} catch { controller.abort(); decide('discover', 'ESCALATE', '발굴 실패', '후보 없음 — 자동 설치하지 않음'); }
|
|
230
|
+
finally { if (timer) clearTimeout(timer); }
|
|
231
|
+
decide('discover', 'ESCALATE', '미충족 능력 후보', `${candidates.length}개 후보 제안만 함`);
|
|
232
|
+
return { need, candidates };
|
|
233
|
+
}));
|
|
186
234
|
return plan;
|
|
187
235
|
}
|