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
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import { spawnSync } from 'node:child_process';
|
|
3
3
|
import { closeSync, cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { basename, dirname, isAbsolute, join, resolve, sep } from 'node:path';
|
|
6
6
|
import { dlopen, FFIType } from 'bun:ffi';
|
|
7
7
|
import { loadPluginManifestFromDir, type PluginManifest } from '../core/manifest.js';
|
|
8
|
+
import { loadPluginNodes } from '../../graph-kinds/plugin-nodes.js';
|
|
9
|
+
import { getNodeKindRegistration, listNodeKinds, unregisterPluginNodeKind } from '../../graph-kinds/registry.js';
|
|
8
10
|
import { verifyIndex, type MarketplaceIndex, type VerifyIndexResult } from '../../market/signed-index.js';
|
|
9
11
|
import { elanousStateRoot } from '../../autopilot/state-paths.js';
|
|
12
|
+
import { ensureMarketIndex, MarketFetchError, type MarketFetchOptions } from './market-fetch.js';
|
|
10
13
|
|
|
11
14
|
const NAME = /^[a-z0-9][a-z0-9-]{1,39}$/;
|
|
12
15
|
const VERSION = /^[0-9]+\.[0-9]+\.[0-9]+(?:[-+][a-zA-Z0-9.-]+)?$/;
|
|
@@ -18,7 +21,7 @@ export type InstallEvent =
|
|
|
18
21
|
| { event: 'verify'; signature: 'ok' | 'missing' | 'bad'; scan: 'safe' | 'caution' | 'dangerous' }
|
|
19
22
|
| { event: 'consent'; capabilities: string[]; required: boolean }
|
|
20
23
|
| { event: 'credentials'; connectors: Array<{ id: string; fields: Array<{ name: string; secret: boolean }> }> }
|
|
21
|
-
| { event: 'registered'; kinds: string[]; graphs: string[]; skills: string[] }
|
|
24
|
+
| { event: 'registered'; /** Vocab file paths, not graph node kinds. */ kinds: string[]; nodes: string[]; nodeErrors: number; graphs: string[]; skills: string[] }
|
|
22
25
|
| { event: 'done'; plugin: string; version: string; path: string };
|
|
23
26
|
|
|
24
27
|
export interface InstalledPlugin {
|
|
@@ -27,6 +30,8 @@ export interface InstalledPlugin {
|
|
|
27
30
|
market: string;
|
|
28
31
|
path: string;
|
|
29
32
|
sha256: string | null;
|
|
33
|
+
/** Absent for installations recorded before timestamps were introduced. */
|
|
34
|
+
installedAt?: string;
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
export interface InstallOptions {
|
|
@@ -34,12 +39,15 @@ export interface InstallOptions {
|
|
|
34
39
|
marketDir?: string;
|
|
35
40
|
cwd?: string;
|
|
36
41
|
allowUnsigned?: boolean;
|
|
42
|
+
refresh?: boolean;
|
|
37
43
|
yes?: boolean;
|
|
38
44
|
trustedKeys?: ReadonlyArray<{ keyId: string; publicKey: string }>;
|
|
39
45
|
verifySignature?: typeof verifyIndex;
|
|
40
46
|
hashArtifact?: (bytes: Uint8Array) => string;
|
|
41
47
|
onEvent?: (event: InstallEvent) => void;
|
|
42
48
|
consent?: (capabilities: string[]) => boolean | Promise<boolean>;
|
|
49
|
+
fetcher?: MarketFetchOptions['fetcher'];
|
|
50
|
+
configPath?: string;
|
|
43
51
|
}
|
|
44
52
|
|
|
45
53
|
export class PluginInstallError extends Error {
|
|
@@ -115,28 +123,8 @@ function marketEntry(spec: string, opts: InstallOptions): { market: string; entr
|
|
|
115
123
|
return { market, entry: matches[0]!, signature };
|
|
116
124
|
}
|
|
117
125
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
writeFileSync(archive, bytes);
|
|
121
|
-
try {
|
|
122
|
-
const entries = child('tar', ['-tzf', archive]).split('\n');
|
|
123
|
-
for (const item of entries) {
|
|
124
|
-
if (item === '.' || item === './') continue;
|
|
125
|
-
inside(target, item.replace(/^\.\//, ''));
|
|
126
|
-
}
|
|
127
|
-
// Links and special entries can write outside the destination before a post-extract scan.
|
|
128
|
-
const types = child('tar', ['-tvzf', archive]).split('\n');
|
|
129
|
-
if (types.some(line => line && !['-', 'd'].includes(line[0]!))) fail('scan', 'artifact contains links or special files');
|
|
130
|
-
mkdirSync(target);
|
|
131
|
-
child('tar', ['-xzf', archive, '-C', target, '--no-same-owner', '--no-same-permissions']);
|
|
132
|
-
safeTree(target);
|
|
133
|
-
} finally {
|
|
134
|
-
rmSync(archive, { force: true });
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export function resolvePluginSource(spec: string, target: string, opts: InstallOptions = {}): { market: string; sha256: string | null; signature: 'ok' | 'missing'; expectedName?: string; expectedVersion?: string } {
|
|
139
|
-
if (/^[a-z0-9][a-z0-9-]{1,39}@[a-z0-9][a-z0-9-]{1,39}$/.test(spec) && !existsSync(resolve(opts.cwd ?? process.cwd(), spec))) {
|
|
126
|
+
/** Offline: a signed index placed in `<markets>/<name>/` without a configured URL (pre-fetch behaviour — kept). */
|
|
127
|
+
function localMarketSource(spec: string, target: string, opts: InstallOptions): ResolvedPluginSource {
|
|
140
128
|
const { market, entry, signature } = marketEntry(spec, opts);
|
|
141
129
|
const source = entry.source;
|
|
142
130
|
const sourceRoot = dirname(indexPath(market, opts));
|
|
@@ -159,7 +147,100 @@ export function resolvePluginSource(spec: string, target: string, opts: InstallO
|
|
|
159
147
|
fail('io', `market entry has no verifiable artifact: ${entry.name}`);
|
|
160
148
|
}
|
|
161
149
|
return { market, sha256: entry.artifact?.sha256 ?? null, signature, expectedName: entry.name, expectedVersion: entry.version };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function hasLocalMarketIndex(market: string, opts: InstallOptions): boolean {
|
|
153
|
+
const base = resolve(opts.marketDir ?? join(opts.root ?? elanousStateRoot(), 'markets'));
|
|
154
|
+
return [join(base, market, 'marketplace.json'), join(base, market, '.agents', 'plugins', 'marketplace.json')].some(existsSync);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function unpackArchive(bytes: Uint8Array, target: string): void {
|
|
158
|
+
const archive = join(dirname(target), 'artifact.tgz');
|
|
159
|
+
writeFileSync(archive, bytes);
|
|
160
|
+
try {
|
|
161
|
+
const entries = child('tar', ['-tzf', archive]).split('\n');
|
|
162
|
+
for (const item of entries) {
|
|
163
|
+
if (item === '.' || item === './') continue;
|
|
164
|
+
inside(target, item.replace(/^\.\//, ''));
|
|
165
|
+
}
|
|
166
|
+
// Links and special entries can write outside the destination before a post-extract scan.
|
|
167
|
+
const types = child('tar', ['-tvzf', archive]).split('\n');
|
|
168
|
+
if (types.some(line => line && !['-', 'd'].includes(line[0]!))) fail('scan', 'artifact contains links or special files');
|
|
169
|
+
mkdirSync(target);
|
|
170
|
+
child('tar', ['-xzf', archive, '-C', target, '--no-same-owner', '--no-same-permissions']);
|
|
171
|
+
safeTree(target);
|
|
172
|
+
} finally {
|
|
173
|
+
rmSync(archive, { force: true });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
type ResolvedPluginSource = { market: string; sha256: string | null; signature: 'ok' | 'missing'; expectedName?: string; expectedVersion?: string };
|
|
178
|
+
|
|
179
|
+
function marketSpec(spec: string, opts: InstallOptions): boolean {
|
|
180
|
+
return /^[a-z0-9][a-z0-9-]{1,39}@[a-z0-9][a-z0-9-]{1,39}$/.test(spec) && !existsSync(resolve(opts.cwd ?? process.cwd(), spec));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function fetchedMarketSource(spec: string, target: string, opts: InstallOptions): Promise<ResolvedPluginSource> {
|
|
184
|
+
const [name, marketName] = spec.split('@') as [string, string];
|
|
185
|
+
let marketIndex: Awaited<ReturnType<typeof ensureMarketIndex>>;
|
|
186
|
+
try {
|
|
187
|
+
marketIndex = await ensureMarketIndex(marketName, {
|
|
188
|
+
root: opts.root, marketDir: opts.marketDir, configPath: opts.configPath, fetcher: opts.fetcher,
|
|
189
|
+
refresh: opts.refresh, verifySignature: opts.verifySignature, trustedKeys: opts.trustedKeys,
|
|
190
|
+
});
|
|
191
|
+
} catch (error) {
|
|
192
|
+
if (error instanceof MarketFetchError && /^market not configured: /.test(error.message) && hasLocalMarketIndex(marketName, opts)) {
|
|
193
|
+
return localMarketSource(spec, target, opts);
|
|
194
|
+
}
|
|
195
|
+
if (error instanceof MarketFetchError) throw new PluginInstallError(error.reason, error.message);
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
const matches = marketIndex.index.plugins.filter(entry => entry.name === name);
|
|
199
|
+
if (matches.length !== 1) fail('io', `plugin not found or ambiguous in ${marketName}: ${name}`);
|
|
200
|
+
const entry = matches[0]!;
|
|
201
|
+
const artifact = entry.artifact;
|
|
202
|
+
if (!artifact || typeof artifact.key !== 'string' || !artifact.key || !HASH.test(artifact.sha256)
|
|
203
|
+
|| !Number.isSafeInteger(artifact.bytes) || artifact.bytes < 0) fail('io', 'invalid artifact key, hash or size');
|
|
204
|
+
// Check the key as written, before URL resolution folds `..`/`.` or percent-encoded separators away.
|
|
205
|
+
if (!/^[A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)*$/.test(artifact.key) || artifact.key.split('/').some(part => part === '.' || part === '..')) fail('io', 'unsafe artifact key');
|
|
206
|
+
const base = new URL(marketIndex.market.url);
|
|
207
|
+
const url = new URL(artifact.key, base);
|
|
208
|
+
if (url.protocol !== 'https:' || url.origin !== base.origin || !url.pathname.startsWith(base.pathname)
|
|
209
|
+
|| url.username || url.password || url.search || url.hash || /[\\?#]/.test(artifact.key)) fail('io', 'unsafe artifact key');
|
|
210
|
+
let response: Response;
|
|
211
|
+
// The URL checks above hold only if no redirect moves the download elsewhere.
|
|
212
|
+
try { response = await (opts.fetcher ?? fetch)(url.href, { redirect: 'error' }); }
|
|
213
|
+
catch { fail('io', 'artifact fetch failed: network error'); }
|
|
214
|
+
if (!response.ok || !response.body) fail('io', `artifact fetch failed: HTTP ${response.status}`);
|
|
215
|
+
if (response.redirected || (response.url && response.url !== url.href)) fail('io', 'artifact fetch was redirected');
|
|
216
|
+
const reader = response.body.getReader();
|
|
217
|
+
const chunks: Uint8Array[] = [];
|
|
218
|
+
let length = 0;
|
|
219
|
+
try {
|
|
220
|
+
for (;;) {
|
|
221
|
+
const { done, value } = await reader.read();
|
|
222
|
+
if (done) break;
|
|
223
|
+
length += value.byteLength;
|
|
224
|
+
if (length > artifact.bytes) fail('scan', 'artifact sha256 or size mismatch');
|
|
225
|
+
chunks.push(value);
|
|
226
|
+
}
|
|
227
|
+
} finally {
|
|
228
|
+
void reader.cancel().catch(() => {});
|
|
229
|
+
reader.releaseLock();
|
|
230
|
+
}
|
|
231
|
+
const bytes = new Uint8Array(length);
|
|
232
|
+
let offset = 0;
|
|
233
|
+
for (const chunk of chunks) { bytes.set(chunk, offset); offset += chunk.byteLength; }
|
|
234
|
+
if (length !== artifact.bytes || (opts.hashArtifact ?? (data => createHash('sha256').update(data).digest('hex')))(bytes).toLowerCase() !== artifact.sha256.toLowerCase()) {
|
|
235
|
+
fail('scan', 'artifact sha256 or size mismatch');
|
|
162
236
|
}
|
|
237
|
+
unpackArchive(bytes, target);
|
|
238
|
+
return { market: marketName, sha256: artifact.sha256, signature: marketIndex.signature, expectedName: name, expectedVersion: entry.version };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function resolvePluginSource(spec: string, target: string, opts: InstallOptions = {}): ResolvedPluginSource {
|
|
242
|
+
// Synchronous callers keep the offline contract: a signed index already placed under `<markets>/<name>/`.
|
|
243
|
+
if (marketSpec(spec, opts)) return localMarketSource(spec, target, opts);
|
|
163
244
|
const local = resolve(opts.cwd ?? process.cwd(), spec);
|
|
164
245
|
if (existsSync(local)) {
|
|
165
246
|
copyPlugin(realpathSync(local), target);
|
|
@@ -187,19 +268,63 @@ function gitSource(url: string, destination: string, path?: unknown, sha?: unkno
|
|
|
187
268
|
}
|
|
188
269
|
}
|
|
189
270
|
|
|
271
|
+
function hasSkill(dir: string): boolean {
|
|
272
|
+
if (!existsSync(dir)) return false;
|
|
273
|
+
return readdirSync(dir, { withFileTypes: true }).some(entry => entry.isDirectory() && existsSync(join(dir, entry.name, 'SKILL.md')));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function inspectPluginNodes(packageRoot: string, manifest: PluginManifest): { nodes: string[]; nodeErrors: number } {
|
|
277
|
+
if (!manifest.contributes.nodes?.length) return { nodes: [], nodeErrors: 0 };
|
|
278
|
+
// Registration validates each staged definition. Use a private identity so existing installations
|
|
279
|
+
// cannot reject an otherwise valid kind as a duplicate; only this inspection's entries are removed.
|
|
280
|
+
const inspectionId = `inspect-${randomUUID().replaceAll('-', '')}`;
|
|
281
|
+
let nodeErrors = 0;
|
|
282
|
+
const nodes: string[] = [];
|
|
283
|
+
try {
|
|
284
|
+
nodeErrors = loadPluginNodes(packageRoot, { ...manifest, id: inspectionId }).errors.length;
|
|
285
|
+
} finally {
|
|
286
|
+
for (const entry of listNodeKinds()) {
|
|
287
|
+
if (entry.plugin !== inspectionId) continue;
|
|
288
|
+
nodes.push(`${manifest.id}:${entry.kind.slice(inspectionId.length + 1)}`);
|
|
289
|
+
const registered = getNodeKindRegistration(entry.graph, entry.kind);
|
|
290
|
+
if (registered) unregisterPluginNodeKind(entry.graph, entry.kind, inspectionId, registered);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return { nodes, nodeErrors };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function declaresMain(packageRoot: string): boolean {
|
|
297
|
+
for (const file of ['plugin.json', join('.codex-plugin', 'plugin.json')]) {
|
|
298
|
+
const path = join(packageRoot, file);
|
|
299
|
+
if (!existsSync(path)) continue;
|
|
300
|
+
try {
|
|
301
|
+
const raw: unknown = JSON.parse(readFileSync(path, 'utf8'));
|
|
302
|
+
if (raw && typeof raw === 'object' && 'main' in raw) return true;
|
|
303
|
+
} catch { return true; }
|
|
304
|
+
}
|
|
305
|
+
return !existsSync(join(packageRoot, 'plugin.json')) && !existsSync(join(packageRoot, '.codex-plugin', 'plugin.json'));
|
|
306
|
+
}
|
|
307
|
+
|
|
190
308
|
export async function installPlugin(spec: string, opts: InstallOptions = {}): Promise<InstalledPlugin> {
|
|
191
309
|
const root = resolve(opts.root ?? elanousStateRoot());
|
|
192
310
|
const stageRoot = join(root, 'plugins', '.staging');
|
|
193
311
|
mkdirSync(stageRoot, { recursive: true });
|
|
194
312
|
const stage = mkdtempSync(join(stageRoot, 'plugin-'));
|
|
195
313
|
try {
|
|
196
|
-
const source =
|
|
314
|
+
const source = marketSpec(spec, opts)
|
|
315
|
+
? await fetchedMarketSource(spec, join(stage, 'package'), opts)
|
|
316
|
+
: resolvePluginSource(spec, join(stage, 'package'), opts);
|
|
197
317
|
const manifest: PluginManifest = loadPluginManifestFromDir(join(stage, 'package'), { id: source.expectedName ?? basename(spec) }).manifest;
|
|
198
318
|
if (!NAME.test(manifest.id) || !VERSION.test(manifest.version)) fail('io', 'invalid plugin name or version');
|
|
199
319
|
if (source.expectedName && (manifest.id !== source.expectedName || manifest.version !== source.expectedVersion)) fail('conflict', 'market name or version differs from manifest');
|
|
200
320
|
const packageRoot = join(stage, 'package');
|
|
201
321
|
const mainPath = inside(packageRoot, manifest.main);
|
|
202
|
-
|
|
322
|
+
// A skills/graphs-only pack (official `elanous-basics`) declares no `main` — the default `./plugin.ts` is not a promise.
|
|
323
|
+
// …but only a pack that ships something to install (skills or graphs) may omit it.
|
|
324
|
+
const shipsAssets = hasSkill(join(packageRoot, 'skills')) || (manifest.contributes.graphs?.length ?? 0) > 0;
|
|
325
|
+
if (existsSync(mainPath) || declaresMain(packageRoot) || !shipsAssets) {
|
|
326
|
+
if (!existsSync(mainPath) || !realpathSync(mainPath).startsWith(`${realpathSync(packageRoot)}${sep}`)) fail('io', `plugin main not found or unsafe: ${manifest.main}`);
|
|
327
|
+
}
|
|
203
328
|
const name = manifest.id;
|
|
204
329
|
const version = manifest.version;
|
|
205
330
|
for (const asset of [...(manifest.contributes.graphs ?? []), ...(manifest.contributes.vocab ?? [])]) {
|
|
@@ -228,12 +353,13 @@ export async function installPlugin(spec: string, opts: InstallOptions = {}): Pr
|
|
|
228
353
|
.map(entry => entry.name).sort()
|
|
229
354
|
: []
|
|
230
355
|
: manifest.contributes.skills.map(skill => skill.name ?? skill.id ?? '').filter(Boolean);
|
|
231
|
-
|
|
356
|
+
const { nodes, nodeErrors } = inspectPluginNodes(packageRoot, manifest);
|
|
357
|
+
emit({ event: 'registered', kinds, nodes, nodeErrors, graphs, skills });
|
|
232
358
|
const installed = withLedgerLock(root, () => {
|
|
233
359
|
if (existsSync(destination)) fail('conflict', `plugin already installed: ${name}@${version}`);
|
|
234
360
|
mkdirSync(dirname(destination), { recursive: true });
|
|
235
361
|
renameSync(join(stage, 'package'), destination);
|
|
236
|
-
const item = { name, version, market: source.market, path: destination, sha256: source.sha256 };
|
|
362
|
+
const item = { name, version, market: source.market, path: destination, sha256: source.sha256, installedAt: new Date().toISOString() };
|
|
237
363
|
try {
|
|
238
364
|
writeLedger(root, [...listInstalledPlugins(root), item]);
|
|
239
365
|
} catch (error) {
|
|
@@ -323,7 +449,8 @@ function writeLedger(root: string, entries: InstalledPlugin[]): void {
|
|
|
323
449
|
const unique = new Map(entries.map(entry => [`${entry.market}/${entry.name}/${entry.version}`, entry]));
|
|
324
450
|
const temp = `${ledger}.${process.pid}.tmp`;
|
|
325
451
|
try {
|
|
326
|
-
writeFileSync(temp, JSON.stringify([...unique.values()].map(({ name, version, market, sha256
|
|
452
|
+
writeFileSync(temp, JSON.stringify([...unique.values()].map(({ name, version, market, sha256, installedAt }) =>
|
|
453
|
+
({ name, version, market, sha256, ...(installedAt === undefined ? {} : { installedAt }) })), null, 2));
|
|
327
454
|
renameSync(temp, ledger);
|
|
328
455
|
} finally {
|
|
329
456
|
rmSync(temp, { force: true });
|
|
@@ -334,7 +461,7 @@ export function listInstalledPlugins(root = elanousStateRoot()): InstalledPlugin
|
|
|
334
461
|
const base = join(root, 'plugins');
|
|
335
462
|
if (!existsSync(base)) return [];
|
|
336
463
|
const result: InstalledPlugin[] = [];
|
|
337
|
-
let ledger: Array<Pick<InstalledPlugin, 'name' | 'version' | 'market' | 'sha256'>> = [];
|
|
464
|
+
let ledger: Array<Pick<InstalledPlugin, 'name' | 'version' | 'market' | 'sha256' | 'installedAt'>> = [];
|
|
338
465
|
try {
|
|
339
466
|
ledger = JSON.parse(readFileSync(join(base, 'installed.json'), 'utf8'));
|
|
340
467
|
if (!Array.isArray(ledger)) ledger = [];
|
|
@@ -348,8 +475,12 @@ export function listInstalledPlugins(root = elanousStateRoot()): InstalledPlugin
|
|
|
348
475
|
const path = join(base, market.name, name.name, version.name);
|
|
349
476
|
try {
|
|
350
477
|
const manifest = loadPluginManifestFromDir(path, { id: name.name }).manifest;
|
|
351
|
-
if (manifest.id === name.name && manifest.version === version.name)
|
|
352
|
-
|
|
478
|
+
if (manifest.id === name.name && manifest.version === version.name) {
|
|
479
|
+
const recorded = ledger.find(entry => entry.name === name.name && entry.version === version.name && entry.market === market.name);
|
|
480
|
+
result.push({ name: name.name, version: version.name, market: market.name, path,
|
|
481
|
+
sha256: recorded?.sha256 ?? null,
|
|
482
|
+
...(typeof recorded?.installedAt === 'string' ? { installedAt: recorded.installedAt } : {}) });
|
|
483
|
+
}
|
|
353
484
|
} catch { /* malformed packages are not installed plugins */ }
|
|
354
485
|
}
|
|
355
486
|
}
|
|
@@ -59,6 +59,10 @@ export const PROJECT_ANCHOR_NON_CANDIDATE_FILENAMES = ['CLAUDE.md'] as const;
|
|
|
59
59
|
export const PROJECT_TREE_MAX_CHARS = 8 * 1024;
|
|
60
60
|
export const PROJECT_TREE_DIR_ENTRY_LIMIT = 20;
|
|
61
61
|
|
|
62
|
+
const NO_TOOLS_VERIFICATION_GUIDANCE = `## Chat without tools — verify rather than guess
|
|
63
|
+
This conversation has tools disabled. Do not guess repository or real-time facts that require verification, such as file counts, file contents, commits, or the current time. Say you cannot check them here. The project tree above is for navigation, not evidence for counts.
|
|
64
|
+
To check these facts, enable tools with \`elanous agent …\` or \`elanous chat --tools …\`.`;
|
|
65
|
+
|
|
62
66
|
// P4 (2026-05-03) — Family-agnostic coding-agent lifecycle. Maps the
|
|
63
67
|
// three user-facing pipelines to an explicit tool-batch sequence so every
|
|
64
68
|
// model (not just codex) follows the same shape. Pattern source:
|
|
@@ -673,8 +677,7 @@ export function loadProjectAnchor(cwd: string): ProjectAnchorResult {
|
|
|
673
677
|
return result;
|
|
674
678
|
}
|
|
675
679
|
|
|
676
|
-
/** Build the surface-agnostic preamble.
|
|
677
|
-
* one system message (project anchor). Surfaces spread the result
|
|
680
|
+
/** Build the surface-agnostic preamble. Surfaces spread the result
|
|
678
681
|
* into their own preamble; e.g.
|
|
679
682
|
*
|
|
680
683
|
* ```ts
|
|
@@ -729,6 +732,9 @@ export function buildUniversalPreamble(ctx: UniversalPreambleContext): LLMMessag
|
|
|
729
732
|
content: `## Project Layout (top-level + 1 depth)\n\n\`\`\`\n${tree.content}\n\`\`\`\n\nUse this layout to pick directories directly instead of issuing broad project-root Grep/Glob calls. When AGENTS.md / CLAUDE.md describe a track or subsystem, this layout shows where its files actually live.`,
|
|
730
733
|
});
|
|
731
734
|
}
|
|
735
|
+
if (!ctx.enabledTools?.length) {
|
|
736
|
+
out.push({ role: 'system', content: NO_TOOLS_VERIFICATION_GUIDANCE });
|
|
737
|
+
}
|
|
732
738
|
// P4 (2026-05-03) — Family-agnostic coding-agent lifecycle. Append
|
|
733
739
|
// before family addendum so codex (which has its own discipline
|
|
734
740
|
// overlay) sees both: lifecycle (what shape) + addendum (how to behave
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { Database } from 'bun:sqlite';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { debug } from '../debug/log.js';
|
|
6
|
+
import { releaseLedgerRoot } from '../instance/resolve.js';
|
|
7
|
+
|
|
8
|
+
export type ChecklistStatus = 'green' | 'yellow' | 'red' | 'done';
|
|
9
|
+
export interface ChecklistItem {
|
|
10
|
+
id: string;
|
|
11
|
+
title: string;
|
|
12
|
+
status: ChecklistStatus;
|
|
13
|
+
owner?: string;
|
|
14
|
+
evidence?: string;
|
|
15
|
+
updatedAt: string;
|
|
16
|
+
updatedBy: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ChecklistHistory {
|
|
19
|
+
at: string;
|
|
20
|
+
by: string;
|
|
21
|
+
id: string;
|
|
22
|
+
field: string;
|
|
23
|
+
from: unknown;
|
|
24
|
+
to: unknown;
|
|
25
|
+
released: string;
|
|
26
|
+
dev: string;
|
|
27
|
+
}
|
|
28
|
+
export interface Checklist {
|
|
29
|
+
version: string;
|
|
30
|
+
released: string;
|
|
31
|
+
dev: string;
|
|
32
|
+
items: ChecklistItem[];
|
|
33
|
+
history: ChecklistHistory[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function devVersion(): string {
|
|
37
|
+
return (JSON.parse(readFileSync(join(import.meta.dir, '..', '..', 'package.json'), 'utf8')) as { version: string }).version;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function releasedVersion(): string {
|
|
41
|
+
const dir = join(releaseLedgerRoot(), 'release');
|
|
42
|
+
if (!existsSync(dir)) return '';
|
|
43
|
+
return readdirSync(dir).filter((v) => /^\d+\.\d+\.\d+$/.test(v) && existsSync(join(dir, v, 'release.json')))
|
|
44
|
+
.filter((v) => {
|
|
45
|
+
try { const record = JSON.parse(readFileSync(join(dir, v, 'release.json'), 'utf8')) as { version?: string; publishedAt?: string }; return record.version === v && typeof record.publishedAt === 'string'; }
|
|
46
|
+
catch { return false; }
|
|
47
|
+
})
|
|
48
|
+
.sort((a, b) => {
|
|
49
|
+
const aa = a.split('.').map(Number), bb = b.split('.').map(Number);
|
|
50
|
+
return (bb[0]! - aa[0]!) || (bb[1]! - aa[1]!) || (bb[2]! - aa[2]!);
|
|
51
|
+
})[0] ?? '';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function pathFor(v: string): string {
|
|
55
|
+
if (!/^\d+\.\d+\.\d+(?:-(?:rc|alpha|beta)\.\d+)?$/.test(v)) throw new Error(`체크리스트 판이 아니다: ${v}`);
|
|
56
|
+
return join(releaseLedgerRoot(), 'release', v, 'checklist.json');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function listChecklist(v: string): Checklist {
|
|
60
|
+
const path = pathFor(v);
|
|
61
|
+
if (existsSync(path)) {
|
|
62
|
+
const data = JSON.parse(readFileSync(path, 'utf8')) as Checklist;
|
|
63
|
+
refresh(data);
|
|
64
|
+
return data;
|
|
65
|
+
}
|
|
66
|
+
return { version: v, released: releasedVersion(), dev: devVersion(), items: [], history: [] };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// SQLite's OS-backed write lock is released when the owning process exits, even without a finally block.
|
|
70
|
+
// Hold it across the JSON read/modify/atomic-rename sequence so writers never use stale snapshots.
|
|
71
|
+
function mutate(v: string, apply: (data: Checklist) => boolean): Checklist {
|
|
72
|
+
const path = pathFor(v);
|
|
73
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
74
|
+
const lock = `${path}.mutex.sqlite`;
|
|
75
|
+
const db = new Database(lock, { create: true, strict: true });
|
|
76
|
+
try {
|
|
77
|
+
chmodSync(lock, 0o600);
|
|
78
|
+
db.exec('PRAGMA busy_timeout = 10000');
|
|
79
|
+
db.exec('BEGIN IMMEDIATE');
|
|
80
|
+
try {
|
|
81
|
+
const data = listChecklist(v);
|
|
82
|
+
if (apply(data)) save(v, data);
|
|
83
|
+
db.exec('COMMIT');
|
|
84
|
+
return data;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
db.exec('ROLLBACK');
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
} finally { db.close(); }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function save(v: string, data: Checklist): void {
|
|
93
|
+
const path = pathFor(v);
|
|
94
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
95
|
+
const temp = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
96
|
+
try {
|
|
97
|
+
writeFileSync(temp, `${JSON.stringify(data, null, 2)}\n`, { mode: 0o600 });
|
|
98
|
+
chmodSync(temp, 0o600);
|
|
99
|
+
renameSync(temp, path);
|
|
100
|
+
} finally { if (existsSync(temp)) rmSync(temp); }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function change(data: Checklist, id: string, field: string, from: unknown, to: unknown, by: string, at: string): void {
|
|
104
|
+
data.history.push({ at, by, id, field, from: from ?? null, to: to ?? null, released: data.released, dev: data.dev });
|
|
105
|
+
debug.log('release-loop.checklist', 'change', { version: data.version, id, field, from: from ?? null, to: to ?? null, by });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function refresh(data: Checklist): void { data.released = releasedVersion(); data.dev = devVersion(); }
|
|
109
|
+
|
|
110
|
+
export function addItem(v: string, input: { id: string; title: string; owner?: string }): Checklist {
|
|
111
|
+
if (!input.id.trim()) throw new Error('칸 id 가 비었다');
|
|
112
|
+
if (!input.title.trim()) throw new Error('칸 제목이 비었다');
|
|
113
|
+
return mutate(v, (data) => {
|
|
114
|
+
if (data.items.some((item) => item.id === input.id)) throw new Error(`이미 있는 칸: ${input.id}`);
|
|
115
|
+
const by = process.env.ELANOUS_TRACK || 'cli';
|
|
116
|
+
const at = new Date().toISOString();
|
|
117
|
+
const item: ChecklistItem = { id: input.id, title: input.title, status: 'yellow', ...(input.owner !== undefined ? { owner: input.owner } : {}), updatedAt: at, updatedBy: by };
|
|
118
|
+
data.items.push(item);
|
|
119
|
+
change(data, item.id, 'add', null, item, by, at);
|
|
120
|
+
return true;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function setItem(v: string, id: string, patch: { status?: ChecklistStatus; evidence?: string; owner?: string }, by: string): Checklist {
|
|
125
|
+
return mutate(v, (data) => {
|
|
126
|
+
const item = data.items.find((i) => i.id === id);
|
|
127
|
+
if (!item) throw new Error(`없는 칸: ${id}`);
|
|
128
|
+
if (patch.status !== undefined && !['green', 'yellow', 'red', 'done'].includes(patch.status)) throw new Error(`잘못된 상태: ${patch.status}`);
|
|
129
|
+
const fields = (['evidence', 'owner', 'status'] as const).filter((field) => patch[field] !== undefined && patch[field] !== item[field]);
|
|
130
|
+
if (fields.length === 0) return false;
|
|
131
|
+
const at = new Date().toISOString();
|
|
132
|
+
for (const field of fields) {
|
|
133
|
+
const from = item[field];
|
|
134
|
+
const to = patch[field]!;
|
|
135
|
+
(item as unknown as Record<string, unknown>)[field] = to;
|
|
136
|
+
item.updatedAt = at;
|
|
137
|
+
item.updatedBy = by;
|
|
138
|
+
change(data, id, field, from, to, by, at);
|
|
139
|
+
}
|
|
140
|
+
return true;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function removeItem(v: string, id: string, by: string): Checklist {
|
|
145
|
+
return mutate(v, (data) => {
|
|
146
|
+
const index = data.items.findIndex((i) => i.id === id);
|
|
147
|
+
if (index < 0) throw new Error(`없는 칸: ${id}`);
|
|
148
|
+
const at = new Date().toISOString();
|
|
149
|
+
const [item] = data.items.splice(index, 1);
|
|
150
|
+
change(data, id, 'remove', item, null, by, at);
|
|
151
|
+
return true;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function summarize(v: string): ChecklistSummary {
|
|
156
|
+
return summarizeChecklist(listChecklist(v));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface ChecklistSummary { green: number; yellow: number; red: number; done: number; blocked: string[]; byOwner: Record<string, number> }
|
|
160
|
+
|
|
161
|
+
export function summarizeChecklist(data: Checklist): ChecklistSummary {
|
|
162
|
+
const summary = { green: 0, yellow: 0, red: 0, done: 0, blocked: [] as string[], byOwner: Object.create(null) as Record<string, number> };
|
|
163
|
+
for (const item of data.items) {
|
|
164
|
+
summary[item.status]++;
|
|
165
|
+
if (item.status === 'red') summary.blocked.push(item.id);
|
|
166
|
+
const owner = item.owner ?? 'unassigned';
|
|
167
|
+
summary.byOwner[owner] = (summary.byOwner[owner] ?? 0) + 1;
|
|
168
|
+
}
|
|
169
|
+
return summary;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function seedFromRoadmap(v: string, markdown: string): Checklist {
|
|
173
|
+
return mutate(v, (data) => {
|
|
174
|
+
let added = false;
|
|
175
|
+
for (const line of markdown.split('\n')) {
|
|
176
|
+
const match = /^\|\s*(K\d+[a-z]?[′']?)\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|/.exec(line);
|
|
177
|
+
if (!match || data.items.some((i) => i.id === match[1])) continue;
|
|
178
|
+
const id = match[1]!;
|
|
179
|
+
const title = match[2]!;
|
|
180
|
+
const icon = match[3]!.trim();
|
|
181
|
+
const status: ChecklistStatus = icon.startsWith('🟢') ? 'green' : icon.startsWith('🔴') ? 'red' : icon.startsWith('✅') ? 'done' : 'yellow';
|
|
182
|
+
const at = new Date().toISOString();
|
|
183
|
+
const by = process.env.ELANOUS_TRACK || 'cli';
|
|
184
|
+
const item: ChecklistItem = { id, title, status, updatedAt: at, updatedBy: by };
|
|
185
|
+
data.items.push(item);
|
|
186
|
+
change(data, id, 'add', null, item, by, at);
|
|
187
|
+
added = true;
|
|
188
|
+
}
|
|
189
|
+
return added;
|
|
190
|
+
});
|
|
191
|
+
}
|
|
@@ -30,9 +30,10 @@ function blankFencedCode(text: string): string {
|
|
|
30
30
|
|
|
31
31
|
export function parseReleaseNoteSection(rawPrBody: string): { fragment?: Section; problems: string[] } {
|
|
32
32
|
const prBody = blankFencedCode(rawPrBody);
|
|
33
|
-
const
|
|
33
|
+
const sections = [...prBody.matchAll(/^## 릴리스 노트[ \t]*\r?$/gm)];
|
|
34
|
+
const start = sections.at(-1);
|
|
34
35
|
if (!start) return { problems: [] };
|
|
35
|
-
const rest = prBody.slice(start.index + start[0].length).replace(/^\r?\n/, '');
|
|
36
|
+
const rest = prBody.slice(start.index! + start[0].length).replace(/^\r?\n/, '');
|
|
36
37
|
const nextHeading = /^ {0,3}#{1,6}(?:[ \t]+|$)/m.exec(rest);
|
|
37
38
|
const body = nextHeading ? rest.slice(0, nextHeading.index) : rest;
|
|
38
39
|
const fields = new Map<string, string>();
|
|
@@ -51,7 +52,9 @@ export function parseReleaseNoteSection(rawPrBody: string): { fragment?: Section
|
|
|
51
52
|
if (duplicates.has('종류') || !kind || !['fix', 'feat', 'security', 'internal'].includes(kind)) problems.push('종류');
|
|
52
53
|
if (duplicates.has('문서') || !doc || (doc.startsWith('없음') ? !/^없음\([^\s()]+(?:[^()]*)\)$/.test(doc) : !/^\S+$/.test(doc))) problems.push('문서');
|
|
53
54
|
if (duplicates.has('대상') || (target !== 'next' && target !== 'later')) problems.push('대상');
|
|
54
|
-
|
|
55
|
+
const invalid = problems.length > 0;
|
|
56
|
+
if (sections.length > 1) problems.push(`multiple release-note sections (${sections.length}); using the last`);
|
|
57
|
+
if (invalid) return { problems };
|
|
55
58
|
return {
|
|
56
59
|
fragment: {
|
|
57
60
|
line: line!, kind: kind as Section['kind'],
|
package/src/self-dev/dev-cli.ts
CHANGED
|
@@ -45,6 +45,7 @@ import { getUserConfig, type UserConfig } from '../user-config.js';
|
|
|
45
45
|
import { resolveChildLlmPreference, type ChildLlmPreferenceInput, type ResolvedChildLlmPreference } from '../self-implement/child-llm-preference.js';
|
|
46
46
|
import { readCachedGrokQuota } from '../oauth/codex-account-store.js';
|
|
47
47
|
import { queryAbandonedDraftPrs, type AbandonedDraftPr } from '../cli/logs-abandoned-draft-prs.js';
|
|
48
|
+
import { branchLineageSlug } from '../cli/pr-lineage.js';
|
|
48
49
|
import { decideDraft, type DraftTriagePr } from './draft-triage-rules.js';
|
|
49
50
|
import { resolveLogTargets } from '../cli/logs-cli.js';
|
|
50
51
|
import { LogStore } from '../mss/logging/log-store.js';
|
|
@@ -196,12 +197,12 @@ export interface DevSelfRunExecution {
|
|
|
196
197
|
|
|
197
198
|
interface DraftTriage {
|
|
198
199
|
closed: number[];
|
|
199
|
-
kept: number | null;
|
|
200
|
+
kept: number[] | null;
|
|
200
201
|
closeFailed: number[];
|
|
201
202
|
skippedNotDraft: number[];
|
|
202
203
|
}
|
|
203
204
|
|
|
204
|
-
export type DraftPrViewState = { isDraft: boolean; state: string } | null;
|
|
205
|
+
export type DraftPrViewState = { isDraft: boolean; state: string; headRefName?: string } | null;
|
|
205
206
|
|
|
206
207
|
export interface DraftPrInventory {
|
|
207
208
|
readonly open: readonly (DraftTriagePr & { url: string; openedAtMs: number; runId?: string | null })[];
|
|
@@ -294,11 +295,13 @@ function defaultViewDraftPr(prUrl: string, timeoutMs?: number): DraftPrViewState
|
|
|
294
295
|
if (IN_TEST_PROCESS()) return null;
|
|
295
296
|
const target = draftPrTargetFromUrl(prUrl);
|
|
296
297
|
if (!target) return null;
|
|
297
|
-
const view = spawnSync('gh', ['pr', 'view', String(target.number), '--repo', target.repository, '--json', 'isDraft,state'], { encoding: 'utf8', ...(timeoutMs === undefined ? {} : { timeout: timeoutMs }) });
|
|
298
|
+
const view = spawnSync('gh', ['pr', 'view', String(target.number), '--repo', target.repository, '--json', 'isDraft,state,headRefName'], { encoding: 'utf8', ...(timeoutMs === undefined ? {} : { timeout: timeoutMs }) });
|
|
298
299
|
if (view.status !== 0) return null;
|
|
299
300
|
try {
|
|
300
|
-
const parsed = JSON.parse(view.stdout) as { isDraft?: unknown; state?: unknown };
|
|
301
|
-
return typeof parsed.isDraft === 'boolean' && typeof parsed.state === 'string'
|
|
301
|
+
const parsed = JSON.parse(view.stdout) as { isDraft?: unknown; state?: unknown; headRefName?: unknown };
|
|
302
|
+
return typeof parsed.isDraft === 'boolean' && typeof parsed.state === 'string'
|
|
303
|
+
? { isDraft: parsed.isDraft, state: parsed.state, ...(typeof parsed.headRefName === 'string' ? { headRefName: parsed.headRefName } : {}) }
|
|
304
|
+
: null;
|
|
302
305
|
} catch {
|
|
303
306
|
return null;
|
|
304
307
|
}
|
|
@@ -946,41 +949,58 @@ export async function executeDevSelfRun(
|
|
|
946
949
|
const view = supervise?.viewDraftPr ?? defaultViewDraftPr;
|
|
947
950
|
const closeDraft = supervise?.closeDraftPr ?? defaultCloseDraftPr;
|
|
948
951
|
const commentDraft = supervise?.commentDraftPr ?? defaultCommentDraftPr;
|
|
949
|
-
const openDrafts: string
|
|
952
|
+
const openDrafts: Array<{ url: string; lineage: string | null }> = [];
|
|
950
953
|
const skippedNotDraft: number[] = [];
|
|
951
954
|
for (const prUrl of drafts) {
|
|
952
955
|
let live: DraftPrViewState = null;
|
|
953
956
|
try { live = view(prUrl); } catch { live = null; }
|
|
954
|
-
if (live?.isDraft === true && live.state === 'OPEN')
|
|
955
|
-
|
|
957
|
+
if (live?.isDraft === true && live.state === 'OPEN') {
|
|
958
|
+
openDrafts.push({ url: prUrl, lineage: live.headRefName ? branchLineageSlug(live.headRefName) : null });
|
|
959
|
+
} else {
|
|
956
960
|
const number = prNumberFromUrl(prUrl);
|
|
957
961
|
if (number !== null) skippedNotDraft.push(number);
|
|
958
962
|
}
|
|
959
963
|
}
|
|
960
|
-
const
|
|
961
|
-
|
|
964
|
+
const keptByLineage = new Map<string, string>();
|
|
965
|
+
if (!converged) {
|
|
966
|
+
for (const draft of openDrafts) {
|
|
967
|
+
if (draft.lineage !== null) keptByLineage.set(draft.lineage, draft.url);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
const keptDrafts = converged ? [] : openDrafts.filter(({ url, lineage }) => lineage === null || keptByLineage.get(lineage) === url);
|
|
971
|
+
const kept = keptDrafts.length === 0 ? null : keptDrafts.flatMap(({ url }) => {
|
|
972
|
+
const number = prNumberFromUrl(url);
|
|
973
|
+
return number === null ? [] : [number];
|
|
974
|
+
});
|
|
962
975
|
const closed: number[] = [];
|
|
963
976
|
const closeFailed: number[] = [];
|
|
964
977
|
const landed = mergedNumbers.map((n) => `#${n}`).join(' ') || '없음';
|
|
965
|
-
for (const
|
|
966
|
-
if (
|
|
967
|
-
const number = prNumberFromUrl(
|
|
978
|
+
for (const { url, lineage } of openDrafts) {
|
|
979
|
+
if (!converged && (lineage === null || keptByLineage.get(lineage) === url)) continue;
|
|
980
|
+
const number = prNumberFromUrl(url);
|
|
981
|
+
const keptInLineage = lineage === null ? null : prNumberFromUrl(keptByLineage.get(lineage)!);
|
|
968
982
|
const comment = converged
|
|
969
983
|
? `하니스 종결 트리아지: 이 런이 수렴해 대체됨 — 착지 ${landed}`
|
|
970
|
-
: `하니스 종결 트리아지: 최신 산출 #${
|
|
984
|
+
: `하니스 종결 트리아지: 최신 산출 #${keptInLineage ?? 'unknown'} 로 대체됨`;
|
|
971
985
|
let ok = false;
|
|
972
|
-
try { ok = closeDraft(
|
|
986
|
+
try { ok = closeDraft(url, comment); } catch { ok = false; }
|
|
973
987
|
if (number === null) continue;
|
|
974
988
|
if (ok) closed.push(number);
|
|
975
989
|
else closeFailed.push(number);
|
|
976
990
|
}
|
|
977
|
-
|
|
991
|
+
for (const { url, lineage } of keptDrafts) {
|
|
992
|
+
const keptLineage = [...keptByLineage.keys()].find((other) => other !== lineage);
|
|
993
|
+
if (lineage !== null && keptLineage !== undefined) {
|
|
994
|
+
try { debug.log('self-dev.triage', 'kept-distinct-piece', { pr: prNumberFromUrl(url), lineage, keptLineage }); } catch { /* fail-open */ }
|
|
995
|
+
}
|
|
978
996
|
const rounds = Math.max(1, executedRounds);
|
|
979
|
-
const handoff = closeFailed.length
|
|
980
|
-
?
|
|
981
|
-
:
|
|
997
|
+
const handoff = closeFailed.length > 0
|
|
998
|
+
? `닫지 못한 draft 도 남아 있다: ${closeFailed.map((n) => `#${n}`).join(' ')}`
|
|
999
|
+
: lineage === null ? '브랜치 계보 미확인 — 사람 판단 대상'
|
|
1000
|
+
: keptDrafts.length === 1 ? '이 draft 가 이 골의 유일한 사람 판단 대상'
|
|
1001
|
+
: '이 draft 가 이 계보의 사람 판단 대상';
|
|
982
1002
|
const comment = `하니스 종결 트리아지: 멈춘 사유 ${supervisorStopReason ?? 'unsupervised'} · 라운드 ${rounds} · 착지한 조각 PR ${landed} · ${handoff}`;
|
|
983
|
-
try { commentDraft(
|
|
1003
|
+
try { commentDraft(url, comment); } catch { /* fail-open */ }
|
|
984
1004
|
}
|
|
985
1005
|
draftTriage = { closed, kept, closeFailed, skippedNotDraft };
|
|
986
1006
|
};
|