elanous 0.2.5 → 0.2.7
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/.env.example +10 -7
- package/README.md +9 -4
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5391-8793f88b03b30161.js → 2375-cb6debea56cce4fb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{1751.50b96ea9c37b49b6.js → 2415.ed6428ff334c7ce2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +25 -0
- package/apps/pwa/out/_next/static/chunks/{4daa2798-1363d725c79f3593.js → 2ea96958-e8c7e112de50b067.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +5 -0
- package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +1 -0
- package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8617.465ca22a319c95b3.js → 4323.c5dfb6f503304775.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4836-2eba6079f31b88d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8918-482b30ce2a8ebf4c.js → 5080-1613752891fcadb4.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5348-bbf838e119661eee.js → 5791-bb82743fa6299562.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{9704-3221c102623c8d73.js → 6062-73e68343a9814f6a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{4440-18bb160d95819f4e.js → 6090-85a3f05f954e5697.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{f6f3850d-7f324c6566002113.js → 8046cadf-211ca3c0e00f2b72.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8067-f74bf14d67ad98f1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8519-f6b5d8a5833a7603.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8894-e92491a164192cc1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9454-a3e0953307216c58.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9740-67051141115ce775.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9804-64c02c3c127d5014.js +1 -0
- package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-d5ebde04844689e6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-f89e9a303515a033.js → page-10ed24f413ed66e9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/{page-4f403cd060442c95.js → page-211810b6c93ab66a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-0ac5d053f0444d21.js → page-624f7a2f89b60c8f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-5a5fb385a4df0ffe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-80497e93a878ddc5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-7457f52683562161.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-cee86394de407aa6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-4ff8edab44196b8c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-92e4f44428d5e290.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-b7dcf5d48a854b76.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-51480757e7d68b92.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-2a05dfe80c8bdf16.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-d9979633aa82f8e6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-30533117beb53625.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-f7857826c2f97ad7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-07267f1391b96aba.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-bccce0babceae30f.js → page-55d50e9b0006a91d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-9699b1786be6171d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-43dd772aee8af75f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-a7e37af7d0399cb8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-973ab7af59c718b7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a9f8d3865b91e436.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-15e5a15db84c56aa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a2e572e4511f2a16.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-dd107bf1c7d52a90.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-d42b99cdfb9f27c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-2764ff40dad5a995.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-c5f1152db116ff96.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-f933bca81ea38a6f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-c1c95739c0bff3b8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-a7aaffcf771662d8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-71d18eec33b9c667.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-56adbe1a1f1379bd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-2c4e59079ee357ac.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-a81f113f3ef89d72.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-5d422f38c052967e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-fc27b00e20733146.js +1 -0
- package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{webpack-2bf59c720fe9542c.js → webpack-e5bea36f183b25e6.js} +1 -1
- package/apps/pwa/out/_next/static/css/f537bdb19c69306a.css +1 -0
- package/apps/pwa/out/_next/static/keajW7DDyghEs1y8oJe6T/_buildManifest.js +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +22 -22
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +22 -22
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +22 -22
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +21 -21
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +21 -21
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +21 -21
- package/apps/pwa/out/consult/index.html +1 -0
- package/apps/pwa/out/consult/index.txt +32 -0
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +22 -22
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +22 -22
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +22 -22
- package/apps/pwa/out/exec/index.html +1 -0
- package/apps/pwa/out/exec/index.txt +32 -0
- package/apps/pwa/out/field/index.html +1 -0
- package/apps/pwa/out/field/index.txt +32 -0
- 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 +21 -21
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +22 -22
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +22 -22
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +21 -21
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +21 -21
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +22 -22
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +22 -22
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +22 -22
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +22 -22
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +21 -21
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +22 -22
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +22 -22
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +22 -22
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +22 -22
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +22 -22
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +21 -21
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +22 -22
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +22 -22
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +22 -22
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +22 -22
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +22 -22
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +22 -22
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +22 -22
- package/bin/elanous.cjs +107 -0
- package/graphs/field/field-feed.yaml +24 -0
- package/graphs/field/recipes.yaml +12 -0
- package/graphs/landing/landing-heal.yaml +3 -1
- package/graphs/landing/recipes.yaml +3 -0
- package/graphs/release/recipes.yaml +13 -0
- package/graphs/release/release-loop.yaml +13 -4
- package/package.json +5 -5
- package/plugins/card-followup/README.md +42 -0
- package/plugins/card-followup/examples/card-sample.json +7 -0
- package/plugins/card-followup/graphs/card-followup.yaml +18 -0
- package/plugins/card-followup/graphs/recipes.yaml +16 -0
- package/plugins/card-followup/graphs/run-step.ts +276 -0
- package/plugins/card-followup/graphs/sales-strategy.ts +173 -0
- package/plugins/card-followup/plugin.json +25 -0
- package/plugins/doc-draft/README.md +13 -0
- package/plugins/doc-draft/examples/brief-sample.json +8 -0
- package/plugins/doc-draft/graphs/doc-draft.yaml +14 -0
- package/plugins/doc-draft/graphs/recipes.yaml +9 -0
- package/plugins/doc-draft/graphs/run-step.ts +113 -0
- package/plugins/doc-draft/plugin.json +24 -0
- package/plugins/geo-check/README.md +15 -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 +198 -0
- package/plugins/geo-check/plugin.json +24 -0
- package/plugins/job-coach/.codex-plugin/plugin.json +1 -1
- package/plugins/job-coach/README.md +31 -1
- package/plugins/job-coach/graphs/run-step.ts +2 -2
- package/plugins/job-coach/plugin.json +1 -1
- package/plugins/job-coach/plugin.ts +1 -1
- package/scripts/affinity/endcard.sh +3 -2
- package/scripts/affinity/reframe.sh +3 -1
- package/scripts/ci-public-export-leak-gate.ts +48 -0
- package/scripts/codex-quota-alert.ts +122 -258
- package/scripts/coord-tracks.json +4 -4
- package/scripts/daemon-port-baseline.txt +1 -2
- package/scripts/field-feed/deliver-node.ts +52 -0
- package/scripts/field-feed/feed-node.ts +67 -0
- package/scripts/field-feed/lib.ts +162 -0
- package/scripts/field-feed/reel-node.ts +24 -0
- package/scripts/field-feed/start.ts +62 -0
- package/scripts/fixtures/vflow-sample.ndjson +6 -0
- package/scripts/install.sh +92 -13
- package/scripts/landing-heal/clean-checkout.ts +114 -0
- package/scripts/landing-heal/run-mission.ts +38 -3
- package/scripts/lib/android-test-device.ts +32 -0
- package/scripts/lib/pwa-scenarios/scenarios.ts +2 -1
- package/scripts/public-export.ts +8 -4
- package/scripts/release-loop/baseline-from-logs.ts +92 -0
- package/scripts/release-loop/checklist-gate-node.ts +35 -0
- package/scripts/release-loop/env-known-failures.ts +31 -0
- package/scripts/release-loop/gate-diff.ts +29 -0
- package/scripts/release-loop/gate-node.ts +124 -29
- package/scripts/release-loop/known-issues-node.ts +159 -0
- package/scripts/release-loop/npm-publish-node.ts +118 -0
- package/scripts/release-loop/ops-upgrade-node.ts +87 -0
- package/scripts/release-loop/release-readiness.ts +58 -0
- package/scripts/release-loop/shard-plan.ts +47 -0
- package/scripts/release-loop/unattended-release.ts +93 -0
- package/scripts/ux/measure-first-text.ts +102 -0
- package/scripts/ux/record-page.ts +4 -1
- package/scripts/vflow-collect.ts +20 -7
- package/scripts/vflow-search.ts +73 -0
- package/scripts/video-free-line-test-walker.ts +36 -0
- package/scripts/video-pipeline.ts +2 -2
- package/skills/explainer-video/SKILL.md +24 -3
- package/skills/explainer-video/engine/brand/elanous-mark-on-dark.svg +1 -1
- package/skills/explainer-video/engine/build.mjs +2 -2
- package/skills/explainer-video/engine/reel.mjs +235 -0
- package/skills/explainer-video/engine/reel.sh +9 -0
- package/skills/explainer-video/engine/vo.ts +58 -16
- package/skills/explainer-video/examples/elanous-0.2.5/script.json +1 -1
- package/skills/grill-me/SKILL.md +1 -1
- package/skills/omni-digest/docs/diagrams/noncode-terminal-promotion-path-2026-08-13.mmd +1 -1
- package/src/a2a/server.ts +2 -0
- package/src/acp/elanous-aware-peers.ts +30 -0
- package/src/acp/server.ts +91 -15
- package/src/agent/monad-agent-turn.ts +25 -6
- package/src/agent/shared-app-tools.ts +5 -3
- package/src/agent-substrate/review-intent.ts +6 -2
- package/src/agent-substrate/shard-boundary-intent.ts +70 -0
- package/src/auth/github-app-token.ts +4 -1
- package/src/autopilot/mission-origin.ts +20 -4
- package/src/autopilot/mission-phase-track.ts +11 -1
- package/src/autopilot/track-agent-shadow.ts +151 -0
- package/src/autopilot/track-agent.ts +26 -6
- package/src/autopilot/track-courier.ts +2 -2
- package/src/boot/acp-server-help.ts +16 -0
- package/src/boot/daemon-prompt-turn.ts +31 -10
- package/src/boot/daemon-tools/index.ts +7 -1
- package/src/budget/codex-credit-pace.ts +84 -0
- package/src/budget/codex-credit-plan.ts +154 -0
- package/src/budget/codex-reset-credit-state.ts +2 -0
- package/src/chat/chat-cli.ts +4 -1
- package/src/chat/short-question.ts +40 -0
- package/src/cli/agent-cli.ts +24 -15
- package/src/cli/ask-bare.ts +42 -0
- package/src/cli/cli-user-error.ts +25 -0
- package/src/cli/codex-credit-plan-view.ts +25 -0
- package/src/cli/dependence-gauge.ts +237 -0
- package/src/cli/doctor-cli.ts +5 -0
- package/src/cli/doctor-modes.ts +48 -0
- package/src/cli/intake-cli.ts +12 -2
- package/src/cli/intake-source-cli.ts +58 -0
- package/src/cli/machine-cli.ts +23 -0
- package/src/cli/msg-cli.ts +137 -0
- package/src/cli/nexus-ios-bind.ts +25 -8
- package/src/cli/phone-link.ts +117 -0
- package/src/cli/plugin-cli.ts +52 -0
- package/src/cli/pr-cli.ts +36 -0
- package/src/cli/private-ledger-commands.ts +31 -0
- package/src/cli/provider-cli.ts +5 -0
- package/src/cli/release-cli.ts +56 -12
- package/src/cli/release-run-if-ready.ts +48 -0
- package/src/cli/setup-cli.ts +27 -6
- package/src/cli/setup-recommend.ts +8 -0
- package/src/cli/usage-cli.ts +11 -1
- package/src/codex/setup.ts +27 -8
- package/src/dashboard/index.ts +1 -1
- package/src/discord.ts +56 -6
- package/src/domains/core-tools.ts +37 -14
- package/src/domains/outbound-alert.ts +55 -16
- package/src/exec-requests/planner.ts +140 -0
- package/src/exec-requests/runner.ts +338 -0
- package/src/exec-requests/store.ts +76 -0
- package/src/field/field-media.ts +250 -0
- package/src/field/field-reel.ts +196 -0
- package/src/grok/credential.ts +7 -5
- package/src/harness/aside-run-status.ts +33 -0
- package/src/harness/browser-verify.ts +48 -17
- package/src/harness/harness-cli-command.ts +25 -3
- package/src/harness/image-load-state.ts +21 -0
- package/src/harness/process-garbage.ts +45 -0
- package/src/index.ts +104 -38
- package/src/instance/leader.ts +13 -1
- package/src/intake-plane/collect-github.ts +5 -3
- package/src/intake-plane/collect-telegram-saved.ts +15 -3
- package/src/intake-plane/discord-seat-work.ts +56 -0
- package/src/intake-plane/intake-sources.ts +202 -0
- package/src/intake-plane/items.ts +28 -12
- package/src/intake-plane/seat-doc-route.ts +207 -0
- package/src/intake-plane/submit-intake-work.ts +9 -1
- package/src/intake-plane/telegram-seat-work.ts +72 -0
- package/src/llm/model-defaults.ts +24 -1
- package/src/llm.ts +5 -4
- package/src/machines/machine-ledger.ts +127 -0
- package/src/market/pack-internal-refs.ts +28 -0
- package/src/market/publish.ts +31 -4
- package/src/maturity/feature-maturity.ts +65 -0
- package/src/model-tier/turn-tier.ts +24 -0
- package/src/msg/msg-store.ts +149 -0
- package/src/native-tool-catalog.ts +2 -1
- package/src/nexus/api/chat-fast-path-config.ts +28 -0
- package/src/nexus/api/consult-requests.ts +117 -0
- package/src/nexus/api/discord-trigger-bot.ts +3 -0
- package/src/nexus/api/exec-requests.ts +51 -0
- package/src/nexus/api/field-uploads.ts +180 -0
- package/src/nexus/api/graph-approvals-feed.ts +140 -0
- package/src/nexus/api/graph-approvals.ts +120 -4
- package/src/nexus/api/harness-api.ts +219 -5
- package/src/nexus/api/http-server.ts +68 -5
- package/src/nexus/api/seat-requests.ts +254 -0
- package/src/nexus/api/trace-run-scan.ts +37 -0
- package/src/nexus/api/trace.ts +19 -3
- package/src/nexus/index.ts +3 -0
- package/src/nexus/setup-status.ts +4 -5
- package/src/notify/llm-budget-alert.ts +125 -0
- package/src/oauth/codex-account-rotation.ts +26 -4
- package/src/oauth/codex-account-store.ts +20 -2
- package/src/oauth/codex-browser-login.ts +173 -0
- package/src/oauth/codex.ts +4 -1
- package/src/oauth/fallback-chain.ts +5 -5
- package/src/oauth/store.ts +42 -5
- package/src/onboarding/continue-here.ts +29 -0
- package/src/onboarding/entry-hints.ts +8 -0
- package/src/onboarding/full-screen-io.ts +35 -3
- package/src/onboarding/help/skills.md +1 -1
- package/src/onboarding/non-interactive.ts +1 -0
- package/src/onboarding/subscription-hint.ts +27 -0
- package/src/onboarding/web-first.ts +83 -0
- package/src/onboarding.ts +230 -23
- package/src/plugins/maker/node-install.ts +92 -0
- package/src/plugins/maker/node-maker.ts +139 -0
- package/src/plugins/maker/plugin-maker.ts +220 -0
- package/src/policy/llm-policy.ts +268 -0
- package/src/prompt-library/session-guidance.ts +7 -1
- package/src/prompt-library/universal-preamble.ts +2 -2
- package/src/relay/channel.ts +184 -0
- package/src/relay/mux.ts +182 -0
- package/src/relay/relay-cli.ts +20 -0
- package/src/relay/relay-server.ts +220 -0
- package/src/relay/test-vectors.json +29 -0
- package/src/release-loop/checklist.ts +36 -9
- package/src/seat-address/seat-address.ts +34 -0
- package/src/self-dev/decompose.ts +1 -0
- package/src/self-dev/dev-pipeline.ts +6 -3
- package/src/self-dev/run-record-mirror.ts +44 -0
- package/src/self-dev/run-store.ts +12 -3
- package/src/self-implement/harness-policy-gates.ts +4 -1
- package/src/self-implement/report-origin.ts +12 -1
- package/src/self-implement/run-ledger-compact.ts +1 -1
- package/src/session/chat.ts +17 -5
- package/src/session/index.ts +23 -10
- package/src/skills/shared-agent-skill-roots.ts +25 -0
- package/src/steward/failure-streak.ts +78 -0
- package/src/steward/steward-cards.ts +24 -0
- package/src/steward/triage-budget.ts +127 -0
- package/src/steward/triage-plan.ts +75 -0
- package/src/steward/triage.ts +244 -42
- package/src/task-cards/card-store.ts +1 -1
- package/src/task-orchestrator/surfaces/pod-bun-cache.ts +18 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +12 -3
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +11 -5
- package/src/task-orchestrator/surfaces/pod-ledger-events.ts +68 -0
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +78 -4
- package/src/telegram.ts +145 -2
- package/src/tool-runtime/elanous-skills-list-runtime.ts +3 -3
- package/src/tool-runtime/self-cognition-runtimes.ts +160 -2
- package/src/user-config.ts +84 -4
- package/src/version/packed-revision.json +1 -1
- package/src/video-pipeline/recipes/character.ts +42 -5
- package/src/video-pipeline/recipes/explainer.ts +213 -0
- package/src/video-pipeline/recipes/ffmpeg.ts +1 -1
- package/src/video-pipeline/recipes/free-line.ts +16 -3
- package/src/video-pipeline/recipes/remote.ts +7 -1
- package/src/video-pipeline/vflow-reference.ts +109 -0
- package/src/video-pipeline/walk-line.ts +3 -2
- package/apps/pwa/out/_next/static/chunks/06ef2eba-d1b99ac6d2ea663c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1413-7179a49332db5a80.js +0 -1
- package/apps/pwa/out/_next/static/chunks/150-25c277137a9638ab.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1640-bc99ec23bbe29d12.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2300-de059d0ab2a16630.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2684-05b0a81e75298c16.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3026-2ed7e317d9b02102.js +0 -25
- package/apps/pwa/out/_next/static/chunks/3611-77496ada38cd6358.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3717-68957c4dc9ceec90.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3929-46ba9af6f9d895cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4015-62c5a3744698a8da.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4217-3c4317a7905f9eeb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4240-3230f230fae2d7d5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4271-e53e075ebd48593b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4377.3b125fef79576dfd.js +0 -5
- package/apps/pwa/out/_next/static/chunks/4853-35fdbe6bdbcdc0fc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5007-6a3b30e23ff68a6e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5244-12b424c42070f5ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5611-df00af3e4e4a61bb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5890-dca2321773be829a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6123-37c9dc57ddaa7ab0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6588-b7975304a302ab91.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6604-c03961962b6273b6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7109-cf8c87e968e83a65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7178-30f77877525a2efb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7262-871563fc9663730b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7294-f0eeebb92aa93459.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7429-881af44deed6958f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7527.943753b6ed829f09.js +0 -1
- package/apps/pwa/out/_next/static/chunks/764-a01e858525a190c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7868-e35d5f7985ecd3f1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8320-51484af7b01fdfa5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9131-52f5fad51cceca04.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9238.2cd5f47f4bf7a2c3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-70c404c7bb6d7f0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-3ffd80166b1050aa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-4bcc63e272ae8205.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-880734bd07d62882.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-72bd384898d88955.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-1018bbc9586ecae5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-de86f8c38076dd7b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-70c404c7bb6d7f0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-cd6447196d8bafe6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-e733630c47fa102b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-d1c1a8357b161875.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-60c514128e81d787.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8b637b7e325e13c1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-63301d11dbe73836.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-a8cf0dc472060db2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-02fba54881ccfa49.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-d5e8e90f0038f8de.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-b1ef6824f494466a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-d589bb0f233b1009.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-c9bbaebef4cda059.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-3639ce7916b92fed.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-7450ae7478955653.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-70c404c7bb6d7f0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-b41ca4abfd618452.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-b333dd79ebee3fa9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-8be1227492793655.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-6e03e9371b552e0d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-911411e9fca18d03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-792170e649b37088.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-c40463466937bc91.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-509c798b80384536.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-1cac2aa21d77c417.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-9626799d9b4a2cd1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-84cd419494e16b0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-361487d2da603917.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-607f5beb94cb2e07.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-d0222d219e29dddf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-a26980a31126decc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-edb2b37decf23e4d.js +0 -1
- package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +0 -1
- package/apps/pwa/out/_next/static/ktMqfrBbf8BMGac0jqLrb/_buildManifest.js +0 -1
- /package/apps/pwa/out/_next/static/{ktMqfrBbf8BMGac0jqLrb → keajW7DDyghEs1y8oJe6T}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
4
|
+
import { parse as parseYaml } from 'yaml';
|
|
5
|
+
import { elanousStateRoot } from '../../autopilot/state-paths.js';
|
|
6
|
+
import { debug } from '../../debug/log.js';
|
|
7
|
+
import { runGraph } from '../../graph-runner/runner.js';
|
|
8
|
+
import { parseGraphTemplateYaml } from '../../self-implement/graph-yaml.js';
|
|
9
|
+
import { installPlugin } from '../install/plugin-install.js';
|
|
10
|
+
|
|
11
|
+
const CAPABILITIES = ['fs:workdir', 'proc:bun', 'proc:elanous'];
|
|
12
|
+
const isRecord = (value: unknown): value is Record<string, unknown> => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
13
|
+
|
|
14
|
+
export interface MakePluginDeps {
|
|
15
|
+
codex: (dir: string, prompt: string) => Promise<void>;
|
|
16
|
+
install?: typeof installPlugin;
|
|
17
|
+
runGraph?: typeof runGraph;
|
|
18
|
+
}
|
|
19
|
+
export interface MakePluginOptions {
|
|
20
|
+
request: string;
|
|
21
|
+
name?: string;
|
|
22
|
+
parentDir?: string;
|
|
23
|
+
run?: boolean;
|
|
24
|
+
input?: unknown;
|
|
25
|
+
deps?: MakePluginDeps;
|
|
26
|
+
}
|
|
27
|
+
export interface MakePluginResult {
|
|
28
|
+
status: 'installed' | 'ran' | 'failed';
|
|
29
|
+
dir: string;
|
|
30
|
+
plugin: string;
|
|
31
|
+
graph: string;
|
|
32
|
+
errors: string[];
|
|
33
|
+
runStatus?: Awaited<ReturnType<typeof runGraph>>['status'];
|
|
34
|
+
timings: { scaffold: number; write: number; validate: number; repair?: number; install: number; run?: number };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Run codex in the plugin folder without leaving run debris there: the last message goes to a temp file, and a
|
|
38
|
+
* `.elanous/` that the run created (debug logs and a `latest` symlink from elanous processes codex starts in that
|
|
39
|
+
* folder) is removed — the installer refuses symlinks, so the debris made every freshly written plugin uninstallable
|
|
40
|
+
* (10-01 live `plugin node add`). A `.elanous/` that existed before the run is left alone. */
|
|
41
|
+
export async function codexWrite(dir: string, prompt: string, bin = 'codex'): Promise<void> {
|
|
42
|
+
const scratch = mkdtempSync(join(tmpdir(), 'elanous-codex-'));
|
|
43
|
+
const stateDirExisted = existsSync(join(dir, '.elanous'));
|
|
44
|
+
try {
|
|
45
|
+
const child = Bun.spawn([bin, 'exec', '-C', dir, '-s', 'workspace-write', '--skip-git-repo-check', '-o', join(scratch, 'last-message.txt'), prompt], {
|
|
46
|
+
cwd: dir, stdin: 'ignore', stdout: 'ignore', stderr: 'pipe',
|
|
47
|
+
});
|
|
48
|
+
const [code, stderr] = await Promise.all([child.exited, new Response(child.stderr).text()]);
|
|
49
|
+
if (code !== 0) throw new Error(`codex exec failed (${code}): ${stderr.trim().slice(0, 500)}`);
|
|
50
|
+
} finally {
|
|
51
|
+
if (!stateDirExisted) rmSync(join(dir, '.elanous'), { recursive: true, force: true });
|
|
52
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function scaffold(dir: string, slug: string, request: string): void {
|
|
57
|
+
mkdirSync(join(dir, 'graphs'));
|
|
58
|
+
mkdirSync(join(dir, 'examples'));
|
|
59
|
+
writeFileSync(join(dir, 'plugin.json'), JSON.stringify({ name: slug, version: '0.1.0', description: request,
|
|
60
|
+
extensions: { 'ai.elanous': { graphs: [`./graphs/${slug}.yaml`], capabilities: CAPABILITIES } } }, null, 2) + '\n');
|
|
61
|
+
writeFileSync(join(dir, 'graphs', `${slug}.yaml`), `graph_id: ${slug}\nversion: 1\nentry_node: main\nterminal_nodes: [done, failed]\nnodes:\n - { node_id: main, kind: agent, recipe: 'cmd:main', max_visits: 1 }\n - { node_id: done, kind: gate, recipe: none, max_visits: 1 }\n - { node_id: failed, kind: gate, recipe: none, max_visits: 1 }\nedges:\n - { from: main, on: outcome, map: { ok: done, fail: failed } }\n`);
|
|
62
|
+
writeFileSync(join(dir, 'graphs', 'recipes.yaml'), `main:\n command: 'bun "$ELANOUS_GRAPH_DIR/run-step.ts" main'\n timeout_ms: 120000\n`);
|
|
63
|
+
writeFileSync(join(dir, 'graphs', 'run-step.ts'), `import { readFileSync } from 'node:fs';\nconst context = JSON.parse(readFileSync(process.env.ELANOUS_GRAPH_CONTEXT!, 'utf8'));\nconsole.log(JSON.stringify({ outcome: 'ok', input: context.input }));\n`);
|
|
64
|
+
writeFileSync(join(dir, 'examples', 'input.json'), '{}\n');
|
|
65
|
+
writeFileSync(join(dir, 'README.md'), `# ${slug}\n\n${request}\n\nRun the installed graph with examples/input.json as input. No messages are sent.\n`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function inside(dir: string, path: string): string | undefined {
|
|
69
|
+
const full = resolve(dir, path);
|
|
70
|
+
return full.startsWith(`${dir}${sep}`) ? full : undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function realTarget(path: string): string {
|
|
74
|
+
let ancestor = path;
|
|
75
|
+
while (!existsSync(ancestor)) {
|
|
76
|
+
const next = dirname(ancestor);
|
|
77
|
+
if (next === ancestor) throw new Error(`cannot resolve plugin directory: ${path}`);
|
|
78
|
+
ancestor = next;
|
|
79
|
+
}
|
|
80
|
+
return resolve(realpathSync(ancestor), relative(ancestor, path));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function assertLocalParent(parent: string): void {
|
|
84
|
+
const repoPlugins = realpathSync(resolve(import.meta.dir, '../../../plugins'));
|
|
85
|
+
const target = realTarget(parent);
|
|
86
|
+
if (target === repoPlugins || target.startsWith(`${repoPlugins}${sep}`)) {
|
|
87
|
+
throw new Error('repository plugins/ is not a local plugin directory');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function validatePluginDir(dir: string): Promise<string[]> {
|
|
92
|
+
const errors: string[] = [];
|
|
93
|
+
let manifest: unknown;
|
|
94
|
+
try {
|
|
95
|
+
const file = join(dir, 'plugin.json');
|
|
96
|
+
if (lstatSync(file).isSymbolicLink()) errors.push('안전하지 않은 plugin.json 링크');
|
|
97
|
+
else manifest = JSON.parse(readFileSync(file, 'utf8'));
|
|
98
|
+
}
|
|
99
|
+
catch (error) { errors.push(`plugin.json 파싱 실패: ${String(error)}`); }
|
|
100
|
+
const extension = isRecord(manifest) && isRecord(manifest.extensions) ? manifest.extensions['ai.elanous'] : undefined;
|
|
101
|
+
const graphs = isRecord(extension) ? extension.graphs : undefined;
|
|
102
|
+
if (isRecord(extension) && extension.contributes !== undefined) errors.push('extension contributes 덮어쓰기 금지');
|
|
103
|
+
if (!isRecord(manifest) || typeof manifest.name !== 'string' || !/^[a-z0-9][a-z0-9-]{1,39}$/.test(manifest.name) || typeof manifest.version !== 'string') errors.push('plugin.json 이름/버전 오류');
|
|
104
|
+
if (isRecord(manifest) && (manifest.capabilities !== undefined || manifest.contributes !== undefined || manifest.main !== undefined)) errors.push('plugin.json 최상위 권한/기여/main 덮어쓰기 금지');
|
|
105
|
+
if (!isRecord(extension) || !Array.isArray(extension.capabilities) || JSON.stringify(extension.capabilities) !== JSON.stringify(CAPABILITIES)) {
|
|
106
|
+
errors.push('capabilities 는 ["fs:workdir","proc:bun","proc:elanous"] 이어야 한다');
|
|
107
|
+
}
|
|
108
|
+
if (!Array.isArray(graphs) || graphs.length === 0 || !graphs.every(g => typeof g === 'string')) errors.push('plugin.json graphs 경로 없음');
|
|
109
|
+
let recipes: unknown;
|
|
110
|
+
try {
|
|
111
|
+
const file = join(dir, 'graphs', 'recipes.yaml');
|
|
112
|
+
if (lstatSync(file).isSymbolicLink()) errors.push('안전하지 않은 recipes.yaml 링크');
|
|
113
|
+
else recipes = parseYaml(readFileSync(file, 'utf8'));
|
|
114
|
+
}
|
|
115
|
+
catch (error) { errors.push(`recipes.yaml 파싱 실패: ${String(error)}`); }
|
|
116
|
+
if (!isRecord(recipes)) errors.push('recipes.yaml 은 맵이어야 한다');
|
|
117
|
+
else for (const [id, recipe] of Object.entries(recipes)) {
|
|
118
|
+
if (!isRecord(recipe) || typeof recipe.command !== 'string' || !recipe.command.trim() ||
|
|
119
|
+
(recipe.timeout_ms !== undefined && (!Number.isSafeInteger(recipe.timeout_ms) || Number(recipe.timeout_ms) <= 0))) errors.push(`잘못된 recipe: ${id}`);
|
|
120
|
+
}
|
|
121
|
+
for (const graph of Array.isArray(graphs) ? graphs : []) {
|
|
122
|
+
if (typeof graph !== 'string') continue;
|
|
123
|
+
const file = inside(dir, graph);
|
|
124
|
+
if (!file || !/^\.\/graphs\/[a-zA-Z0-9_-]+\.ya?ml$/.test(graph)) { errors.push(`안전하지 않은 graph 경로: ${graph}`); continue; }
|
|
125
|
+
try {
|
|
126
|
+
if (lstatSync(file).isSymbolicLink()) { errors.push(`안전하지 않은 graph 링크: ${graph}`); continue; }
|
|
127
|
+
const parsed = parseGraphTemplateYaml(readFileSync(file, 'utf8'), file);
|
|
128
|
+
errors.push(...parsed.errors.map(issue => `${issue.path}: ${issue.message}`));
|
|
129
|
+
const template = parsed.template;
|
|
130
|
+
if (!template) continue;
|
|
131
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(template.graphId)) errors.push(`잘못된 graph_id: ${template.graphId}`);
|
|
132
|
+
if (template.terminalNodes.length !== 2 || !template.terminalNodes.includes('done') || !template.terminalNodes.includes('failed')) errors.push('terminal_nodes 는 done, failed 이어야 한다');
|
|
133
|
+
const executionNodes = template.nodes.filter(node => !template.terminalNodes.includes(node.nodeId));
|
|
134
|
+
if (executionNodes.length < 2 || executionNodes.length > 6) errors.push(`실행 노드는 2~6개여야 한다: ${executionNodes.length}개`);
|
|
135
|
+
for (const node of template.nodes) {
|
|
136
|
+
if (node.recipe.startsWith('cmd:')) {
|
|
137
|
+
const id = node.recipe.slice(4);
|
|
138
|
+
if (!isRecord(recipes) || !isRecord(recipes[id]) || typeof recipes[id].command !== 'string' || !recipes[id].command) errors.push(`recipe ${id} 없음`);
|
|
139
|
+
} else if (node.recipe !== 'none') errors.push(`지원하지 않는 recipe: ${node.recipe}`);
|
|
140
|
+
}
|
|
141
|
+
} catch (error) { errors.push(`${graph} 파싱 실패: ${String(error)}`); }
|
|
142
|
+
}
|
|
143
|
+
const visit = (path: string): void => {
|
|
144
|
+
for (const entry of readdirSync(path, { withFileTypes: true })) {
|
|
145
|
+
const file = join(path, entry.name);
|
|
146
|
+
if (entry.isSymbolicLink() || (!entry.isDirectory() && !entry.isFile())) { errors.push(`안전하지 않은 파일: ${file}`); continue; }
|
|
147
|
+
if (entry.isDirectory()) visit(file);
|
|
148
|
+
else if (/\.[cm]?[jt]sx?$/.test(entry.name)) {
|
|
149
|
+
const source = readFileSync(file, 'utf8');
|
|
150
|
+
for (const match of source.matchAll(/\b(?:import|export)\s*(?:[\s\S]*?\s+from\s*)?["']([^"']+)["']|\b(?:import|require)\s*\(\s*["']([^"']+)["']/g)) {
|
|
151
|
+
const target = match[1] ?? match[2]!;
|
|
152
|
+
if (/(?:^|\/)src\//.test(target) && (target.startsWith('/') || target.startsWith('.'))) errors.push(`src import 금지: ${file}: ${target}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
try { visit(dir); } catch (error) { errors.push(`파일 검사 실패: ${String(error)}`); }
|
|
158
|
+
try {
|
|
159
|
+
const example = join(dir, 'examples', 'input.json');
|
|
160
|
+
if (lstatSync(example).isSymbolicLink()) errors.push('안전하지 않은 examples/input.json 링크');
|
|
161
|
+
else JSON.parse(readFileSync(example, 'utf8'));
|
|
162
|
+
} catch (error) { errors.push(`examples/input.json 파싱 실패: ${String(error)}`); }
|
|
163
|
+
const step = join(dir, 'graphs', 'run-step.ts');
|
|
164
|
+
try {
|
|
165
|
+
if (!existsSync(step)) errors.push('run-step.ts 없음');
|
|
166
|
+
else if (errors.some(error => error.startsWith('안전하지 않은 파일:'))) return errors;
|
|
167
|
+
else {
|
|
168
|
+
const build = await Bun.build({ entrypoints: [step], target: 'bun' });
|
|
169
|
+
if (!build.success) errors.push(...build.logs.map(log => `run-step.ts 문법 오류: ${log.message}`));
|
|
170
|
+
}
|
|
171
|
+
} catch (error) { errors.push(`run-step.ts 문법 오류: ${String(error)}`); }
|
|
172
|
+
return errors;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export async function makePlugin({ request, name, parentDir, run, input, deps }: MakePluginOptions): Promise<MakePluginResult> {
|
|
176
|
+
const slug = (name ?? request).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 32).replace(/-$/, '') || `plugin-${Date.now()}`;
|
|
177
|
+
if (!request.trim() || !/^[a-z0-9][a-z0-9-]{1,39}$/.test(slug)) throw new Error(`invalid plugin request or name: ${slug}`);
|
|
178
|
+
if (name !== undefined && !/^[a-z0-9][a-z0-9-]{1,31}$/.test(name)) throw new Error(`invalid --name slug: ${name}`);
|
|
179
|
+
const root = elanousStateRoot();
|
|
180
|
+
const parent = resolve(parentDir ?? join(root, 'plugins-local'));
|
|
181
|
+
assertLocalParent(parent);
|
|
182
|
+
const dir = join(parent, slug);
|
|
183
|
+
if (existsSync(dir)) throw new Error(`plugin directory already exists: ${dir}`);
|
|
184
|
+
const timings: MakePluginResult['timings'] = { scaffold: 0, write: 0, validate: 0, install: 0 };
|
|
185
|
+
const result: MakePluginResult = { status: 'failed', dir, plugin: slug, graph: join(dir, 'graphs', `${slug}.yaml`), errors: [], timings };
|
|
186
|
+
const step = async (stage: keyof MakePluginResult['timings'], action: () => void | Promise<void>): Promise<void> => {
|
|
187
|
+
const start = performance.now();
|
|
188
|
+
try { await action(); }
|
|
189
|
+
finally { const ms = Math.round(performance.now() - start); timings[stage] = ms; debug.log('plugin.maker', stage, { ms, errors: result.errors }); }
|
|
190
|
+
};
|
|
191
|
+
try {
|
|
192
|
+
await step('scaffold', () => { mkdirSync(parent, { recursive: true }); mkdirSync(dir); scaffold(dir, slug, request); });
|
|
193
|
+
const codex = deps?.codex ?? codexWrite;
|
|
194
|
+
const prompt = `요청 원문:\n${request}\n\n이 폴더의 뼈대를 수정해 작동하는 로컬 그래프 플러그인을 작성하라. plugin.json extensions["ai.elanous"].graphs 와 capabilities 는 유지하고, graphs/<id>.yaml 은 entry_node, terminal_nodes: [done, failed], nodes 의 recipe: 'cmd:<id>', edges 의 on: outcome, map: { ok, fail } 을 써라. graphs/recipes.yaml 에 모든 cmd:<id> 의 command: 'bun "$ELANOUS_GRAPH_DIR/run-step.ts" <id>' 와 timeout_ms 를 선언하라. graphs/run-step.ts 의 각 단계 stdout 마지막 줄은 JSON {outcome, ...} 이며 이전 출력은 ELANOUS_GRAPH_CONTEXT JSON 의 outputs 에 있다. src/ import 금지. 엘라누스 기능은 CLI elanous ask --json 또는 elanous research --json 로만 사용. 아무것도 보내지 말 것. 실행 노드는 2~6개. examples/input.json 에 실제로 돌릴 입력을 넣을 것. 폴더 밖에는 쓰지 말 것.`;
|
|
195
|
+
await step('write', () => codex(dir, prompt));
|
|
196
|
+
await step('validate', async () => { result.errors = await validatePluginDir(dir); });
|
|
197
|
+
if (result.errors.length) {
|
|
198
|
+
await step('repair', () => codex(dir, `${prompt}\n\n아래 오류를 모두 수리하라:\n${result.errors.join('\n')}`));
|
|
199
|
+
await step('validate', async () => { result.errors = await validatePluginDir(dir); });
|
|
200
|
+
}
|
|
201
|
+
if (result.errors.length) return result;
|
|
202
|
+
let installedPath = '';
|
|
203
|
+
await step('install', async () => { installedPath = (await (deps?.install ?? installPlugin)(dir, { yes: true, root })).path; });
|
|
204
|
+
result.status = 'installed';
|
|
205
|
+
const graph = JSON.parse(readFileSync(join(dir, 'plugin.json'), 'utf8')).extensions['ai.elanous'].graphs[0] as string;
|
|
206
|
+
result.graph = resolve(installedPath, graph);
|
|
207
|
+
if (run) {
|
|
208
|
+
await step('run', async () => {
|
|
209
|
+
const value = input === undefined ? JSON.parse(readFileSync(join(dir, 'examples', 'input.json'), 'utf8')) : input;
|
|
210
|
+
result.runStatus = (await (deps?.runGraph ?? runGraph)(result.graph, { input: value })).status;
|
|
211
|
+
});
|
|
212
|
+
result.status = result.runStatus === 'done' ? 'ran' : 'failed';
|
|
213
|
+
if (result.status === 'failed') result.errors.push(`graph run: ${result.runStatus}`);
|
|
214
|
+
}
|
|
215
|
+
} catch (error) {
|
|
216
|
+
result.status = 'failed';
|
|
217
|
+
result.errors.push(error instanceof Error ? error.message : String(error));
|
|
218
|
+
}
|
|
219
|
+
return result;
|
|
220
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { isScalar, parseDocument } from 'yaml';
|
|
4
|
+
import { getElanousConfigDir } from '../elanous-config-dir.js';
|
|
5
|
+
import { DEFAULT_FALLBACK_CHAIN, isFallbackStep, type FallbackStep } from '../oauth/fallback-chain.js';
|
|
6
|
+
import { isCodexQuotaPolicy } from '../oauth/codex-quota-policy.js';
|
|
7
|
+
import { DEFAULT_BUDGET_GATE_MAX_USED_PERCENT, MODEL_ROLES, RUNTIME_LLM_PROVIDER_NAMES, parseRoleLlmEntry, type RoleLlmConfig } from '../user-config.js';
|
|
8
|
+
|
|
9
|
+
export interface LlmPolicy {
|
|
10
|
+
version: 1;
|
|
11
|
+
default: { provider: string; model?: string };
|
|
12
|
+
roles: RoleLlmConfig;
|
|
13
|
+
fallback: FallbackStep[];
|
|
14
|
+
accounts: { codex: {
|
|
15
|
+
order: string[];
|
|
16
|
+
rotateAtPercent: number;
|
|
17
|
+
perAccount: Record<string, { rotateAtPercent: number; until?: string; why?: string }>;
|
|
18
|
+
rotationEnabled: boolean;
|
|
19
|
+
} };
|
|
20
|
+
credits: {
|
|
21
|
+
codex: 'use' | 'fallback' | 'never';
|
|
22
|
+
grants: Array<{ account: string; amount: number; expires: string; source: string }>;
|
|
23
|
+
pace?: { targetPerDay: number; until: string; why?: string };
|
|
24
|
+
};
|
|
25
|
+
caps: { codex: { harness: number; pod: number; tox: number; intake: number; headroom: number }; grok: { weekly: number; tox: number; intake: number } };
|
|
26
|
+
resetCredits: { redeem: 'human' };
|
|
27
|
+
alerts: { lowRemainingPercent: number; resetCreditExpiryDays: number; creditsStep: number };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type LlmPolicySource = 'default' | 'legacy-config' | 'file';
|
|
31
|
+
export interface LlmPolicyValidation {
|
|
32
|
+
valid: boolean;
|
|
33
|
+
errors: string[];
|
|
34
|
+
warnings: string[];
|
|
35
|
+
}
|
|
36
|
+
export interface LoadedLlmPolicy extends LlmPolicyValidation {
|
|
37
|
+
policy: LlmPolicy;
|
|
38
|
+
sources: Record<string, LlmPolicySource>;
|
|
39
|
+
path: string;
|
|
40
|
+
filePresent: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface LoadLlmPolicyOptions {
|
|
43
|
+
/** All file IO remains scoped to this directory; tests can pass a temporary directory. */
|
|
44
|
+
configDir?: string;
|
|
45
|
+
path?: string;
|
|
46
|
+
/** An already-read legacy config; when omitted the sibling config.json is read directly. */
|
|
47
|
+
legacyConfig?: unknown;
|
|
48
|
+
now?: Date;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
type Obj = Record<string, unknown>;
|
|
52
|
+
const object = (v: unknown): v is Obj => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
53
|
+
const percent = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v) && v >= 0 && v <= 100;
|
|
54
|
+
const nonempty = (v: unknown): v is string => typeof v === 'string' && v.trim().length > 0;
|
|
55
|
+
const positive = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v) && v > 0;
|
|
56
|
+
const date = (v: unknown): v is string => typeof v === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(v) && !Number.isNaN(Date.parse(`${v}T00:00:00Z`)) && new Date(`${v}T00:00:00Z`).toISOString().slice(0, 10) === v;
|
|
57
|
+
|
|
58
|
+
function setOwn(target: Obj, key: string, value: unknown): void {
|
|
59
|
+
Object.defineProperty(target, key, { value, configurable: true, enumerable: true, writable: true });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Today's code defaults, not a snapshot of one machine's user config. */
|
|
63
|
+
export function defaultLlmPolicy(): LlmPolicy {
|
|
64
|
+
return {
|
|
65
|
+
version: 1,
|
|
66
|
+
default: { provider: 'auto' },
|
|
67
|
+
roles: {},
|
|
68
|
+
fallback: [...DEFAULT_FALLBACK_CHAIN],
|
|
69
|
+
accounts: { codex: { order: [], rotateAtPercent: 95, perAccount: { default: { rotateAtPercent: 60 } }, rotationEnabled: true } },
|
|
70
|
+
credits: { codex: 'fallback', grants: [] },
|
|
71
|
+
caps: {
|
|
72
|
+
codex: { harness: DEFAULT_BUDGET_GATE_MAX_USED_PERCENT['openai-codex']!, pod: 95, tox: 60, intake: 80, headroom: 15 },
|
|
73
|
+
grok: { weekly: DEFAULT_BUDGET_GATE_MAX_USED_PERCENT.grok!, tox: 50, intake: 48 },
|
|
74
|
+
},
|
|
75
|
+
resetCredits: { redeem: 'human' },
|
|
76
|
+
alerts: { lowRemainingPercent: 10, resetCreditExpiryDays: 7, creditsStep: 5000 },
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function leafPaths(value: unknown, prefix = ''): string[] {
|
|
81
|
+
if (!object(value) || Object.keys(value).length === 0) return prefix ? [prefix] : [];
|
|
82
|
+
return Object.entries(value).flatMap(([key, item]) => leafPaths(item, prefix ? `${prefix}.${key}` : key));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Validate the partial policy document. Unknown fields and invalid values are errors, not silent overrides. */
|
|
86
|
+
export function validateLlmPolicy(input: unknown): LlmPolicyValidation {
|
|
87
|
+
const errors: string[] = [];
|
|
88
|
+
const warnings: string[] = [];
|
|
89
|
+
if (!object(input)) return { valid: false, errors: ['policy must be a mapping'], warnings };
|
|
90
|
+
const check = (node: unknown, path: string, schema: Obj): void => {
|
|
91
|
+
if (!object(node)) { errors.push(`${path} must be a mapping`); return; }
|
|
92
|
+
for (const [key, value] of Object.entries(node)) {
|
|
93
|
+
if (!Object.hasOwn(schema, key)) { errors.push(`unknown field ${path}.${key}`); continue; }
|
|
94
|
+
const rule = schema[key];
|
|
95
|
+
if (object(rule)) check(value, `${path}.${key}`, rule);
|
|
96
|
+
else if (typeof rule === 'function' && !(rule as (v: unknown) => boolean)(value)) errors.push(`${path}.${key} is invalid`);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
const dateField = (v: unknown) => date(v);
|
|
100
|
+
const perAccount = (v: unknown) => object(v) && Object.entries(v).every(([key, row]) => nonempty(key) && object(row)
|
|
101
|
+
&& Object.keys(row).every(k => ['rotateAtPercent', 'until', 'why'].includes(k))
|
|
102
|
+
&& percent(row.rotateAtPercent) && (row.until === undefined || date(row.until)) && (row.why === undefined || nonempty(row.why)));
|
|
103
|
+
const grants = (v: unknown) => Array.isArray(v) && v.every(g => object(g)
|
|
104
|
+
&& Object.keys(g).every(k => ['account', 'amount', 'expires', 'source'].includes(k))
|
|
105
|
+
&& nonempty(g.account) && positive(g.amount) && date(g.expires) && nonempty(g.source));
|
|
106
|
+
const roles = (v: unknown) => object(v) && Object.entries(v).every(([role, spec]) => MODEL_ROLES.some(r => r === role)
|
|
107
|
+
&& object(spec) && Object.keys(spec).every(k => ['provider', 'tier', 'model'].includes(k)) && parseRoleLlmEntry(spec).ok);
|
|
108
|
+
check(input, 'policy', {
|
|
109
|
+
version: (v: unknown) => v === 1,
|
|
110
|
+
default: { provider: (v: unknown) => nonempty(v) && RUNTIME_LLM_PROVIDER_NAMES.some(p => p === v), model: nonempty },
|
|
111
|
+
roles,
|
|
112
|
+
fallback: (v: unknown) => Array.isArray(v) && v.length > 0 && v.every(isFallbackStep) && new Set(v).size === v.length,
|
|
113
|
+
accounts: { codex: {
|
|
114
|
+
order: (v: unknown) => Array.isArray(v) && v.every(nonempty) && new Set(v).size === v.length,
|
|
115
|
+
rotateAtPercent: percent, perAccount, rotationEnabled: (v: unknown) => typeof v === 'boolean',
|
|
116
|
+
} },
|
|
117
|
+
credits: {
|
|
118
|
+
codex: (v: unknown) => v === 'use' || v === 'fallback' || v === 'never',
|
|
119
|
+
grants,
|
|
120
|
+
pace: (v: unknown) => object(v) && positive(v.targetPerDay) && dateField(v.until)
|
|
121
|
+
&& (v.why === undefined || nonempty(v.why))
|
|
122
|
+
&& Object.keys(v).every(k => ['targetPerDay', 'until', 'why'].includes(k)),
|
|
123
|
+
},
|
|
124
|
+
caps: { codex: { harness: percent, pod: percent, tox: percent, intake: percent, headroom: percent },
|
|
125
|
+
grok: { weekly: percent, tox: percent, intake: percent } },
|
|
126
|
+
resetCredits: { redeem: (v: unknown) => v === 'human' },
|
|
127
|
+
alerts: { lowRemainingPercent: percent, resetCreditExpiryDays: positive, creditsStep: positive },
|
|
128
|
+
});
|
|
129
|
+
if (input.version !== undefined && input.version !== 1) warnings.push('unsupported policy version');
|
|
130
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function merge(base: Obj, overlay: Obj, sources: Record<string, LlmPolicySource>, source: LlmPolicySource, prefix = ''): void {
|
|
134
|
+
for (const [key, value] of Object.entries(overlay)) {
|
|
135
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
136
|
+
if (object(value) && Object.hasOwn(base, key) && object(base[key])) merge(base[key] as Obj, value, sources, source, path);
|
|
137
|
+
else {
|
|
138
|
+
setOwn(base, key, value !== null && typeof value === 'object' ? structuredClone(value) : value);
|
|
139
|
+
for (const leaf of leafPaths(value, path)) setOwn(sources, leaf, source);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** The date is inclusive: a rule with until=2026-10-29 remains active through that UTC day. */
|
|
145
|
+
function removeExpired(input: Obj, now: Date, warnings: string[], prefix = ''): void {
|
|
146
|
+
for (const [key, value] of Object.entries(input)) {
|
|
147
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
148
|
+
if (path === 'credits.grants' && Array.isArray(value)) {
|
|
149
|
+
input[key] = value.filter(grant => {
|
|
150
|
+
if ((grant as { expires: string }).expires >= now.toISOString().slice(0, 10)) return true;
|
|
151
|
+
warnings.push(`${path} ${String((grant as { account: string }).account)} expired on ${String((grant as { expires: string }).expires)}; ignored`);
|
|
152
|
+
return false;
|
|
153
|
+
});
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (!object(value)) continue;
|
|
157
|
+
if (typeof value.until === 'string' && date(value.until) && value.until < now.toISOString().slice(0, 10)) {
|
|
158
|
+
delete input[key];
|
|
159
|
+
warnings.push(`${path} expired on ${value.until}; ignored`);
|
|
160
|
+
} else removeExpired(value, now, warnings, path);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function loadLlmPolicy(opts: LoadLlmPolicyOptions = {}): LoadedLlmPolicy {
|
|
165
|
+
const configDir = opts.configDir ?? getElanousConfigDir();
|
|
166
|
+
const path = opts.path ?? join(configDir, 'policy', 'llm.yaml');
|
|
167
|
+
const policy = defaultLlmPolicy();
|
|
168
|
+
const sources: Record<string, LlmPolicySource> = Object.fromEntries(leafPaths(policy).map(p => [p, 'default']));
|
|
169
|
+
const warnings: string[] = [];
|
|
170
|
+
const errors: string[] = [];
|
|
171
|
+
let legacy: unknown = opts.legacyConfig;
|
|
172
|
+
if (legacy === undefined) {
|
|
173
|
+
const legacyPath = join(configDir, 'config.json');
|
|
174
|
+
try { legacy = JSON.parse(readFileSync(legacyPath, 'utf8')); }
|
|
175
|
+
catch (error) {
|
|
176
|
+
if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT')) {
|
|
177
|
+
warnings.push(`legacy config ${legacyPath} could not be loaded: ${error instanceof Error ? error.message : String(error)}`);
|
|
178
|
+
}
|
|
179
|
+
legacy = {};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (object(legacy)) {
|
|
183
|
+
const llm = object(legacy.llm) ? legacy.llm : {};
|
|
184
|
+
const overlay: Obj = {};
|
|
185
|
+
const add = (key: string, value: unknown, target: Obj, targetKey: string, valid: (v: unknown) => boolean): void => {
|
|
186
|
+
if (value === undefined) return;
|
|
187
|
+
if (valid(value)) { setOwn(target, targetKey, value); warnings.push(`legacy config ${key} applies; migrate to policy/llm.yaml`); }
|
|
188
|
+
else warnings.push(`legacy config ${key} is invalid; ignored`);
|
|
189
|
+
};
|
|
190
|
+
const base: Obj = {};
|
|
191
|
+
add('llm.provider', llm.provider, base, 'provider', v => nonempty(v) && RUNTIME_LLM_PROVIDER_NAMES.some(p => p === v));
|
|
192
|
+
add('llm.model', llm.model, base, 'model', nonempty);
|
|
193
|
+
if (Object.keys(base).length) overlay.default = base;
|
|
194
|
+
add('roleLlm', legacy.roleLlm, overlay, 'roles', v => object(v) && Object.entries(v).every(([r, spec]) => MODEL_ROLES.some(k => k === r) && parseRoleLlmEntry(spec).ok));
|
|
195
|
+
add('llm.fallbackChain', llm.fallbackChain, overlay, 'fallback', v => Array.isArray(v) && v.length > 0 && v.every(isFallbackStep) && new Set(v).size === v.length);
|
|
196
|
+
const codex: Obj = {};
|
|
197
|
+
add('llm.codexAccountOrder', llm.codexAccountOrder, codex, 'order', v => Array.isArray(v) && v.every(nonempty));
|
|
198
|
+
add('llm.codexAccountRotation', llm.codexAccountRotation, codex, 'rotationEnabled', v => typeof v === 'boolean');
|
|
199
|
+
add('llm.codexAccountRotationThresholdPercent', llm.codexAccountRotationThresholdPercent, codex, 'rotateAtPercent', percent);
|
|
200
|
+
if (object(llm.codexAccountRotationThresholdPercentByAccount)) {
|
|
201
|
+
const rows: Obj = {};
|
|
202
|
+
for (const [account, value] of Object.entries(llm.codexAccountRotationThresholdPercentByAccount)) {
|
|
203
|
+
add(`llm.codexAccountRotationThresholdPercentByAccount.${account}`, value, rows, account, percent);
|
|
204
|
+
if (Object.hasOwn(rows, account)) setOwn(rows, account, { rotateAtPercent: rows[account] });
|
|
205
|
+
}
|
|
206
|
+
if (Object.keys(rows).length) codex.perAccount = rows;
|
|
207
|
+
}
|
|
208
|
+
if (Object.keys(codex).length) overlay.accounts = { codex };
|
|
209
|
+
const credits: Obj = {};
|
|
210
|
+
if (llm.codexQuotaPolicy !== undefined) {
|
|
211
|
+
if (isCodexQuotaPolicy(llm.codexQuotaPolicy)) {
|
|
212
|
+
credits.codex = { credits: 'use', fallback: 'fallback', 'within-quota': 'never' }[llm.codexQuotaPolicy];
|
|
213
|
+
warnings.push('legacy config llm.codexQuotaPolicy applies; migrate to policy/llm.yaml');
|
|
214
|
+
} else warnings.push('legacy config llm.codexQuotaPolicy is invalid; ignored');
|
|
215
|
+
}
|
|
216
|
+
if (credits.codex === undefined && llm.codexCreditsAllowed === true) {
|
|
217
|
+
credits.codex = 'use';
|
|
218
|
+
warnings.push('legacy config llm.codexCreditsAllowed applies; migrate to policy/llm.yaml');
|
|
219
|
+
}
|
|
220
|
+
if (Object.keys(credits).length) overlay.credits = credits;
|
|
221
|
+
const gate = object(legacy.harness) && object(legacy.harness.budgetGate) ? legacy.harness.budgetGate : {};
|
|
222
|
+
const caps: Obj = {};
|
|
223
|
+
const harness: Obj = {};
|
|
224
|
+
add('harness.budgetGate.minHeadroomPercent', gate.minHeadroomPercent, harness, 'headroom', percent);
|
|
225
|
+
if (object(gate.maxUsedPercent)) {
|
|
226
|
+
add('harness.budgetGate.maxUsedPercent.openai-codex', gate.maxUsedPercent['openai-codex'], harness, 'harness', percent);
|
|
227
|
+
const grok: Obj = {};
|
|
228
|
+
add('harness.budgetGate.maxUsedPercent.grok', gate.maxUsedPercent.grok, grok, 'weekly', percent);
|
|
229
|
+
if (Object.keys(grok).length) caps.grok = grok;
|
|
230
|
+
}
|
|
231
|
+
if (Object.keys(harness).length) caps.codex = harness;
|
|
232
|
+
if (Object.keys(caps).length) overlay.caps = caps;
|
|
233
|
+
merge(policy as unknown as Obj, overlay, sources, 'legacy-config');
|
|
234
|
+
}
|
|
235
|
+
// Read directly: `existsSync` is also false when the folder cannot be searched (EACCES), which would
|
|
236
|
+
// report an unreadable policy as «no file · valid». Only ENOENT/ENOTDIR mean the file is absent.
|
|
237
|
+
let raw: string | undefined;
|
|
238
|
+
let filePresent = false;
|
|
239
|
+
try {
|
|
240
|
+
raw = readFileSync(path, 'utf8');
|
|
241
|
+
filePresent = true;
|
|
242
|
+
} catch (error) {
|
|
243
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
244
|
+
if (code !== 'ENOENT' && code !== 'ENOTDIR') {
|
|
245
|
+
filePresent = true;
|
|
246
|
+
errors.push(`cannot read policy file ${path}: ${code ?? (error instanceof Error ? error.message : String(error))}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (raw !== undefined) {
|
|
250
|
+
try {
|
|
251
|
+
const doc = parseDocument(raw, { uniqueKeys: true });
|
|
252
|
+
if (doc.errors.length) throw new Error(doc.errors.map(e => e.message).join('; '));
|
|
253
|
+
const emptyDocument = doc.contents === null || (isScalar(doc.contents) && doc.contents.type === 'PLAIN' && doc.contents.source === '');
|
|
254
|
+
const parsed: unknown = emptyDocument ? {} : doc.toJS();
|
|
255
|
+
const validation = validateLlmPolicy(parsed);
|
|
256
|
+
errors.push(...validation.errors);
|
|
257
|
+
warnings.push(...validation.warnings);
|
|
258
|
+
if (validation.valid && object(parsed)) {
|
|
259
|
+
const filtered = structuredClone(parsed);
|
|
260
|
+
removeExpired(filtered, opts.now ?? new Date(), warnings);
|
|
261
|
+
merge(policy as unknown as Obj, filtered, sources, 'file');
|
|
262
|
+
}
|
|
263
|
+
} catch (error) {
|
|
264
|
+
errors.push(`invalid policy file ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return { policy, sources, path, filePresent, valid: errors.length === 0, errors, warnings };
|
|
268
|
+
}
|
|
@@ -115,9 +115,15 @@ export function buildSessionGuidanceAddendum(
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
if (items.length === 0) return [];
|
|
118
|
+
// B4 — 파일 수 규칙. 명령을 실제로 돌릴 도구(Bash·PtyShellStart·WebTerminal*)가 있을 때만 `git ls-files` 로 세라고 하고,
|
|
119
|
+
// Read 만 켜진 세션에선 셀 수 없으니 추측하지 말고 확인 불가라고 답하게 한다(EVAL q09 «959» vs 실측 778 · 리뷰 3라운드 must-fix).
|
|
120
|
+
const canRunCommands = enabledTools.some(name => /^(Bash|PtyShellStart|WebTerminal)/.test(name));
|
|
121
|
+
const fileCountRule = canRunCommands
|
|
122
|
+
? '저장소의 파일 수·목록은 저장소 루트에서 `git ls-files <경로>`로 추적 파일 기준으로 센다 — 하위 디렉터리에서 실행 중이면 저장소 루트로 이동해서 세고, 다른 기준이면 그 기준을 답에 적는다.'
|
|
123
|
+
: '이 세션에는 명령을 실행할 도구가 없다 — 저장소의 파일 수는 추측하지 말고 «이 모드에서는 셀 수 없다»고 답한다(도구로 센 목록이 대화에 있으면 그 기준을 밝히고 쓴다).';
|
|
118
124
|
return [{
|
|
119
125
|
role: 'system',
|
|
120
|
-
content: '# Session-specific guidance\n\n' +
|
|
126
|
+
content: `${fileCountRule}\n\n` + '# Session-specific guidance\n\n' +
|
|
121
127
|
items.map(s => `- ${s}`).join('\n'),
|
|
122
128
|
}];
|
|
123
129
|
}
|
|
@@ -60,8 +60,8 @@ export const PROJECT_TREE_MAX_CHARS = 8 * 1024;
|
|
|
60
60
|
export const PROJECT_TREE_DIR_ENTRY_LIMIT = 20;
|
|
61
61
|
|
|
62
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.
|
|
64
|
-
|
|
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 date or time. When asked for facts you cannot verify here (including repository state, files, and dates), explicitly say you cannot check them in this mode. The project tree above is for navigation, not evidence for counts.
|
|
64
|
+
Give one line explaining how to enable tools: \`elanous agent "<your question>"\`.`;
|
|
65
65
|
|
|
66
66
|
// P4 (2026-05-03) — Family-agnostic coding-agent lifecycle. Maps the
|
|
67
67
|
// three user-facing pipelines to an explicit tool-batch sequence so every
|