elanous 0.2.6 → 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 +4 -0
- 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/{3995-bee512a7063036ce.js → 2375-cb6debea56cce4fb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +1 -0
- 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/{8d0d9faf-97887ff99713b84e.js → 2ea96958-e8c7e112de50b067.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{5329.8b7e4bca684b6eec.js → 3403.4bc97b4066945a94.js} +2 -2
- 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/{7633.0654fe6ea259c1ee.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/{2984-977cfa339fcd65a5.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/{763-7d0e1913b5074e98.js → 5791-bb82743fa6299562.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{4240-7153881b0b377306.js → 6062-73e68343a9814f6a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +1 -0
- 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/{91ff6dda-3b33d42d20657429.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-10ed24f413ed66e9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-211810b6c93ab66a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-624f7a2f89b60c8f.js +1 -0
- 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-55d50e9b0006a91d.js +1 -0
- 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-37b8bf78a3c6eec5.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/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 +10 -0
- package/graphs/release/release-loop.yaml +10 -3
- package/package.json +5 -5
- package/plugins/card-followup/README.md +13 -4
- package/plugins/card-followup/graphs/card-followup.yaml +3 -1
- package/plugins/card-followup/graphs/recipes.yaml +5 -1
- package/plugins/card-followup/graphs/run-step.ts +122 -18
- package/plugins/card-followup/graphs/sales-strategy.ts +173 -0
- package/plugins/card-followup/plugin.json +1 -1
- 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 +3 -1
- package/plugins/geo-check/graphs/run-step.ts +24 -3
- package/plugins/geo-check/plugin.json +1 -1
- package/plugins/job-coach/.codex-plugin/plugin.json +1 -1
- package/plugins/job-coach/README.md +1 -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/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/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/env-known-failures.ts +31 -0
- package/scripts/release-loop/gate-node.ts +81 -19
- 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/unattended-release.ts +93 -0
- package/scripts/ux/measure-first-text.ts +102 -0
- 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 +12 -9
- package/skills/explainer-video/engine/build.mjs +1 -1
- package/skills/explainer-video/engine/reel.mjs +138 -31
- package/skills/explainer-video/engine/reel.sh +2 -1
- 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/track-agent-shadow.ts +151 -0
- package/src/autopilot/track-agent.ts +23 -3
- 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/plugin-cli.ts +25 -0
- package/src/cli/pr-cli.ts +36 -0
- package/src/cli/provider-cli.ts +5 -0
- package/src/cli/release-cli.ts +42 -1
- 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 +50 -11
- 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 +8 -3
- 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 +86 -14
- 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 +40 -3
- package/src/nexus/api/graph-approvals-feed.ts +140 -0
- package/src/nexus/api/graph-approvals.ts +71 -4
- package/src/nexus/api/http-server.ts +61 -7
- 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 +16 -10
- 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 +19 -7
- 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/relay-cli.ts +20 -0
- package/src/relay/relay-server.ts +220 -0
- package/src/release-loop/checklist.ts +9 -8
- 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/session/chat.ts +17 -5
- 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-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 +38 -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 +80 -4
- package/src/version/packed-revision.json +1 -1
- package/src/video-pipeline/recipes/character.ts +42 -5
- 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/apps/pwa/out/_next/static/chunks/1169-696263ac7af8c4d4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1883.c91a2648af4842fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1947-9d64872eeb9cfdec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2808-dc06482994b40f70.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3165-95515ca5f299a120.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3190-e9282753e7ebde85.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4259-99614149577e77cb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4283-329eabeaf233e6cd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4328-cb5058b1acc2de5e.js +0 -25
- package/apps/pwa/out/_next/static/chunks/4801-ecee6ef62b349b18.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5110-8058791dd25a2e14.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5330-c41e9670f594c38f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5423-e95fd125d1794349.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5478-b5eae07dc79ea619.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5787-96ee6642b9a661e5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6448-79d72368589f98bc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6522-9866c002b5531836.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6729-b4a558b7aa891e0c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6969-cb03f9410b7d2e03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7007-3b9861091bb8dd21.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7930-8dc7e20fc6fc19ca.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8071-187edbc22a86741f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8198-d4101ad70cb9a579.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8250.4a2c5553a07cdf47.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8293-123b91e7b0330dc0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8296-d6d5d87dfa911d41.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8305-603963c33ee8eec3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8612-4a0066f20951b8fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9181-cf4e1b61c4783f92.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9262-26c2ecbd5a67e4a2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9328-45abdebce4fbecb7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9355-dbc67205adf8745f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9763.255b90dd69875689.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9933-66d31ec25c0231af.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-bd06fb55f4da0381.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-4e586142da455a25.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-889ad5cddcf45b0d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-47ba401d56572c27.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-0b54f9c83458e038.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-4fcc296ef6c8ed90.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-fe9fda494a87f4e5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-f9642e25ce129590.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-e1063e8ba5f3e2e7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-9d0df30027d8aa9d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-bd06fb55f4da0381.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-d97adf2c9455b8d0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-236b80d202110362.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-21a30f35f1a276ad.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-b1a5be357c4fa25d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1705a60303f7f573.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-76b337c11ad0e237.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-7c0f85fbb56f64c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-65ca16b20617b44d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-e7f9b8fc29411c1d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-92ea6213d1aeec04.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a913a005aacb5476.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-2d78804a40dfdef6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-08a99148e780ac00.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-deb674e7ed36f815.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-49f8de0534d192d7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-bd06fb55f4da0381.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-ba162a28d2176e18.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-1ff1a37b550ab41a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-ccaa698f56c0fbdb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-53b944082de1d7db.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-9b70adf1552789bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-30d482aa63241fe8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-aecf786f54651d8a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6ab9b62c7b6e08f2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-08949735455e0c9a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-a1479005234a46c2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3890e90bd232732.js +0 -1
- package/apps/pwa/out/_next/static/chunks/beb5cf10-9adcc10b83930090.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-d68d41c88e05c801.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-11c63971b7a9593b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-d09452353bd0d9cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-184d727cebeca767.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-21bbbbac99b720b1.js +0 -1
- package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +0 -1
- package/apps/pwa/out/_next/static/qMI0jnlZaBjjm4aIDYNGM/_buildManifest.js +0 -1
- /package/apps/pwa/out/_next/static/{qMI0jnlZaBjjm4aIDYNGM → keajW7DDyghEs1y8oJe6T}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { statSync } from 'node:fs';
|
|
2
|
+
import type { LogInstanceView } from '../../mss/logging/instance-registry.js';
|
|
3
|
+
import type { LogStore } from '../../mss/logging/log-store.js';
|
|
4
|
+
|
|
5
|
+
/** Bounded, read-only fallback when a run's ledger is not on this host. */
|
|
6
|
+
export function scanStoresForRun(
|
|
7
|
+
runId: string,
|
|
8
|
+
views: readonly LogInstanceView[],
|
|
9
|
+
open: (view: LogInstanceView) => Pick<LogStore, 'queryTraceRun' | 'close'> | null,
|
|
10
|
+
opts: { sinceMs?: number; untilMs?: number; maxStores?: number; deadlineMs?: number; now?: () => number } = {},
|
|
11
|
+
): { universe?: string; checked: number; truncated: boolean; unresolved?: boolean } {
|
|
12
|
+
const now = opts.now ?? Date.now;
|
|
13
|
+
const deadline = now() + (opts.deadlineMs ?? 3000);
|
|
14
|
+
const maxStores = opts.maxStores ?? 200;
|
|
15
|
+
const mtime = (view: LogInstanceView): number => {
|
|
16
|
+
try { return statSync(view.dbPath).mtimeMs; } catch { return -Infinity; }
|
|
17
|
+
};
|
|
18
|
+
const candidates = views.map((view) => ({ view, at: view.dbExists ? mtime(view) : -Infinity }))
|
|
19
|
+
.sort((a, b) => (b.at === a.at ? a.view.name.localeCompare(b.view.name) : b.at - a.at));
|
|
20
|
+
let checked = 0;
|
|
21
|
+
let unresolved = false;
|
|
22
|
+
for (const { view } of candidates) {
|
|
23
|
+
if (checked >= maxStores || now() >= deadline) return { checked, truncated: true };
|
|
24
|
+
checked++;
|
|
25
|
+
let store: ReturnType<typeof open> = null;
|
|
26
|
+
try {
|
|
27
|
+
if (!view.dbExists) continue;
|
|
28
|
+
store = open(view);
|
|
29
|
+
if (!store) { unresolved = true; continue; }
|
|
30
|
+
if (store.queryTraceRun(runId, { sinceMs: opts.sinceMs, untilMs: opts.untilMs, limit: 1 }).length) {
|
|
31
|
+
return { universe: view.name, checked, truncated: false };
|
|
32
|
+
}
|
|
33
|
+
} catch { unresolved = true; }
|
|
34
|
+
finally { try { store?.close(); } catch { /* read-only handle cleanup is best effort */ } }
|
|
35
|
+
}
|
|
36
|
+
return { checked, truncated: false, ...(unresolved ? { unresolved } : {}) };
|
|
37
|
+
}
|
package/src/nexus/api/trace.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { activeStoreNames, parseSinceParam, type LogFabricDeps, registeredLogSto
|
|
|
8
8
|
import { checkAuth, type MetaApiOpts } from './meta-api.js';
|
|
9
9
|
import { jsonResponse } from './json-response.js';
|
|
10
10
|
import { runLedgerDir, runLedgerPath } from '../../self-implement/run-ledger.js';
|
|
11
|
+
import { scanStoresForRun } from './trace-run-scan.js';
|
|
11
12
|
|
|
12
13
|
export type TraceLevel = 'L0' | 'L1' | 'L2' | 'L3';
|
|
13
14
|
export interface TraceEvent {
|
|
@@ -346,7 +347,8 @@ export function handleTrace(req: Request, opts: MetaApiOpts, deps: TraceDeps = {
|
|
|
346
347
|
}
|
|
347
348
|
if (!opened) return jsonResponse({ ok: false, error: 'log-store-unavailable', failedStores: failures }, 503);
|
|
348
349
|
const runId = url.searchParams.get('runId');
|
|
349
|
-
let resolution: { universe?: string; checked: number } | undefined;
|
|
350
|
+
let resolution: { universe?: string; checked: number; truncated?: boolean; unresolved?: boolean } | undefined;
|
|
351
|
+
let resolvedFrom: 'ledger' | 'log-scan' | undefined;
|
|
350
352
|
if ((parsed.level === 'L2' || parsed.level === 'L3') && runId && !rows.some((row) => matches(row, url)) && !requestedUniverse) {
|
|
351
353
|
// A store with no log db at all («has no log store yet» — e.g. the daemon's own tree-derived universe) cannot
|
|
352
354
|
// hold the run, so it must not block the ledger lookup. A store that exists but failed to open/read still does:
|
|
@@ -356,6 +358,20 @@ export function handleTrace(req: Request, opts: MetaApiOpts, deps: TraceDeps = {
|
|
|
356
358
|
try { resolution = findRunUniverse(runId, names, deps); }
|
|
357
359
|
catch (error) { return jsonResponse({ ok: false, error: 'run-universe-unavailable', reason: error instanceof Error ? error.message : String(error) }, 503); }
|
|
358
360
|
try { debug.log('nexus.trace', 'run-universe-resolved', { runId, universe: resolution.universe ?? 'not-found', checked: resolution.checked }); } catch { /* observation must not block reads */ }
|
|
361
|
+
if (resolution.universe) resolvedFrom = 'ledger';
|
|
362
|
+
else {
|
|
363
|
+
let views: LogInstanceView[];
|
|
364
|
+
try { views = (deps.instances ?? readLogInstances)(); }
|
|
365
|
+
catch (error) { return jsonResponse({ ok: false, error: 'run-universe-unavailable', reason: error instanceof Error ? error.message : String(error) }, 503); }
|
|
366
|
+
resolution = scanStoresForRun(runId, views, (view) => {
|
|
367
|
+
const store = (deps.openRemoteStore ?? ((v: LogInstanceView) => LogStore.openReadOnly(v.dbPath)))(view);
|
|
368
|
+
if (!store) return null;
|
|
369
|
+
return { queryTraceRun: (id, query) => store.queryTraceRun(id, query),
|
|
370
|
+
close: () => (deps.closeRemoteStore ?? ((s: LogStore) => s.close()))(store) };
|
|
371
|
+
}, { sinceMs: parsed.from, untilMs: parsed.to });
|
|
372
|
+
if (resolution.universe) resolvedFrom = 'log-scan';
|
|
373
|
+
try { debug.log('nexus.trace', 'run-universe-scanned', { runId, universe: resolution.universe ?? (resolution.truncated || resolution.unresolved ? 'unresolved' : 'not-found'), checked: resolution.checked, truncated: resolution.truncated }); } catch { /* observation must not block reads */ }
|
|
374
|
+
}
|
|
359
375
|
if (resolution.universe && !names.includes(resolution.universe)) {
|
|
360
376
|
const name = resolution.universe;
|
|
361
377
|
names.push(name);
|
|
@@ -373,9 +389,9 @@ export function handleTrace(req: Request, opts: MetaApiOpts, deps: TraceDeps = {
|
|
|
373
389
|
}
|
|
374
390
|
}
|
|
375
391
|
const filtered = rows.filter((row) => matches(row, url)).sort(sortRows);
|
|
376
|
-
const trace = buildTrace(filtered.slice(0, parsed.limit), parsed.level, filtered.length > parsed.limit || candidateLimitReached || failures.length > 0);
|
|
392
|
+
const trace = buildTrace(filtered.slice(0, parsed.limit), parsed.level, filtered.length > parsed.limit || candidateLimitReached || failures.length > 0 || resolution?.truncated === true);
|
|
377
393
|
try { (deps.queryLog ?? ((details) => debug.log('logs.trace', 'query', details)))({ stores: names, level: parsed.level, count: trace.events.length, truncated: trace.truncated, mode: 'rows', runs: trace.nodes.filter((node) => node.level === 'L1' && node.count > 0).length }); } catch { /* query logging must not block reads */ }
|
|
378
|
-
return jsonResponse({ ok: true, ...trace, stores: names, registeredStores: registeredStoreCount(deps), ...(resolution ? resolution.universe ? { resolvedFrom: '
|
|
394
|
+
return jsonResponse({ ok: true, ...trace, stores: names, registeredStores: registeredStoreCount(deps), ...(resolution ? resolution.universe ? { resolvedFrom, runUniverse: resolution.universe, ...(resolvedFrom === 'log-scan' ? { checked: resolution.checked } : {}) } : { runUniverse: resolution.truncated || resolution.unresolved ? 'unresolved' as const : 'not-found' as const, checked: resolution.checked } : {}), ...(failures.length ? { failedStores: failures } : {}) }, 200);
|
|
379
395
|
} finally { closeStores(stores, deps); }
|
|
380
396
|
}
|
|
381
397
|
|
package/src/nexus/index.ts
CHANGED
|
@@ -2626,9 +2626,12 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
|
|
|
2626
2626
|
getBot: () => getBotRef.current,
|
|
2627
2627
|
allowedUsers: [...cfg.discord.allowedUsers],
|
|
2628
2628
|
});
|
|
2629
|
+
const chatChannels = cfg.discord.chatChannels ?? [];
|
|
2630
|
+
debug.log('nexus.discord', 'chat-channels', { count: chatChannels.length });
|
|
2629
2631
|
const handle = createNexusDiscordTriggerBot({
|
|
2630
2632
|
token: discordToken,
|
|
2631
2633
|
allowedUsers: [...cfg.discord.allowedUsers],
|
|
2634
|
+
...(chatChannels.length ? { guildTextChannels: chatChannels } : {}),
|
|
2632
2635
|
dispatch: (event) => workflowDaemon!.dispatchDiscord(event),
|
|
2633
2636
|
onMessage: composedOnMessage,
|
|
2634
2637
|
onInteraction: async (raw) => {
|
|
@@ -13,6 +13,7 @@ import type { UserConfig as NexusUserConfig } from './config/types.js';
|
|
|
13
13
|
import { resolveLaunchdEnvironment } from './install/launchd.js';
|
|
14
14
|
import { resolveSystemdEnvironment } from './install/systemd.js';
|
|
15
15
|
import { resolvePwaStaticDir } from './static-dir-resolve.js';
|
|
16
|
+
import { SKILLS_STEP_COMMAND, unattendedSetupHint } from '../onboarding/entry-hints.js';
|
|
16
17
|
|
|
17
18
|
export type SetupItemId = 'llm' | 'pwa-build' | 'channel-bot' | 'skill-dirs' | 'os-install';
|
|
18
19
|
|
|
@@ -97,8 +98,6 @@ export interface SetupCheckOpts {
|
|
|
97
98
|
decideProviderForConfig?: typeof decideProviderForConfig;
|
|
98
99
|
}
|
|
99
100
|
|
|
100
|
-
const UNATTENDED_SETUP_HINT = 'unattended: `elanous setup --non-interactive --config <ans.json>`';
|
|
101
|
-
|
|
102
101
|
function hasText(value: unknown): boolean {
|
|
103
102
|
return typeof value === 'string' && value.trim().length > 0;
|
|
104
103
|
}
|
|
@@ -146,7 +145,7 @@ function checkLlm(
|
|
|
146
145
|
id: 'llm',
|
|
147
146
|
label: 'LLM provider',
|
|
148
147
|
passed,
|
|
149
|
-
hint: `run \`elanous
|
|
148
|
+
hint: `run \`elanous onboarding llm\` or interactive \`elanous nexus\`; ${unattendedSetupHint()}`,
|
|
150
149
|
...(detailProvider && detailProvider !== 'none' ? {
|
|
151
150
|
detail: `provider=${detailProvider}${credential ? ` · credential=${credential}` : ''}`,
|
|
152
151
|
} : {}),
|
|
@@ -173,7 +172,7 @@ function checkChannelBot(cfg: NexusUserConfig): SetupItem {
|
|
|
173
172
|
id: 'channel-bot',
|
|
174
173
|
label: 'Channel bot',
|
|
175
174
|
passed: hasText(telegram) || hasText(discord),
|
|
176
|
-
hint: `run \`elanous nexus channel-bot setup telegram|discord\`; ${
|
|
175
|
+
hint: `run \`elanous nexus channel-bot setup telegram|discord\`; ${unattendedSetupHint()}`,
|
|
177
176
|
};
|
|
178
177
|
}
|
|
179
178
|
|
|
@@ -187,7 +186,7 @@ function checkSkillDirs(cfg: MainUserConfig, exists: (path: string) => boolean):
|
|
|
187
186
|
label: 'Skill dirs',
|
|
188
187
|
passed: existingDirs.length > 0,
|
|
189
188
|
hint: missingDirs.length > 0
|
|
190
|
-
? `create the missing skill directories or choose an already-existing skill directory; ${
|
|
189
|
+
? `create the missing skill directories or choose an already-existing skill directory; run \`${SKILLS_STEP_COMMAND}\`; ${unattendedSetupHint()}`
|
|
191
190
|
: '',
|
|
192
191
|
...(dirs.length > 0 ? {
|
|
193
192
|
detail: missingDirs.length > 0 ? `${detail} · missing: ${missingDirs.join(', ')}` : detail,
|
|
@@ -42,6 +42,8 @@ export interface LlmBudgetAlert {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
const EXPIRY_WARN_DAYS = 7;
|
|
45
|
+
/** 공개 알림에 표시할 수 있는 폴백 제공자명. 현재 폴백 체인의 외부 제공자는 grok 뿐이다. */
|
|
46
|
+
const PUBLIC_FALLBACK_PROVIDERS = new Set(['grok']);
|
|
45
47
|
/** 이 시각(서울) 뒤에야 «오늘 페이스가 느리다»를 말한다 — 아침에 0 인 건 정상이다. */
|
|
46
48
|
const PACE_CHECK_HOUR = 18;
|
|
47
49
|
|
|
@@ -87,22 +89,26 @@ export function buildLlmBudgetAlert(now: LlmBudgetSnapshot, prevKey?: string): L
|
|
|
87
89
|
`구독 계정 ${accounts}개 한도 · 크레딧·대체 모델도 못 씁니다.`,
|
|
88
90
|
'할 일: 크레딧 사용을 허락하거나 대체 모델 키를 확인해 주세요.',
|
|
89
91
|
];
|
|
90
|
-
} else if (now.selected.reason === 'fallback'
|
|
91
|
-
const soonest = now.accounts.map((a) => a.resetInHours)
|
|
92
|
-
|
|
92
|
+
} else if (now.selected.reason === 'fallback') {
|
|
93
|
+
const soonest = now.accounts.map((a) => a.resetInHours)
|
|
94
|
+
.filter((h): h is number => typeof h === 'number' && Number.isFinite(h) && h > 0)
|
|
95
|
+
.sort((a, b) => a - b)[0];
|
|
96
|
+
const candidate = now.fallback?.provider?.trim();
|
|
97
|
+
const provider = candidate && PUBLIC_FALLBACK_PROVIDERS.has(candidate) ? candidate : undefined;
|
|
98
|
+
key = `fallback:${provider || 'unknown'}`;
|
|
93
99
|
lines = [
|
|
94
|
-
`⚠️ codex 를 못 써서 ${
|
|
100
|
+
`⚠️ codex 를 못 써서 ${provider ? `${provider} 로` : '대체 모델로'} 일하고 있습니다${typeof now.fallback?.remainingPct === 'number' ? `(남은 ${Math.round(now.fallback.remainingPct)}%)` : ''}.`,
|
|
95
101
|
soonest !== undefined ? `codex 는 ${Math.ceil(soonest)}시간 뒤 돌아옵니다.` : 'codex 가 언제 돌아오는지 아직 모릅니다.',
|
|
96
102
|
'할 일: 없음 — 참고만 하세요.',
|
|
97
103
|
];
|
|
98
|
-
} else if (expiryDays !== undefined && expiryDays <= EXPIRY_WARN_DAYS && now.credits.total > 0) {
|
|
99
|
-
key = `expiry:${
|
|
104
|
+
} else if (expiryDays !== undefined && expiryDays >= 0 && expiryDays <= EXPIRY_WARN_DAYS && now.credits.total > 0) {
|
|
105
|
+
key = `expiry:${expiryDays}`;
|
|
100
106
|
lines = [
|
|
101
|
-
`💳 크레딧 ${n(now.credits.total)} 이 ${expiryDays
|
|
107
|
+
`💳 크레딧 ${n(now.credits.total)} 이 ${expiryDays === 0 ? '오늘' : `${expiryDays}일 뒤`} 사라집니다.`,
|
|
102
108
|
creditsLine(now),
|
|
103
|
-
'할 일:
|
|
109
|
+
'할 일: 만료 전에 크레딧 사용을 확인해 주세요.',
|
|
104
110
|
];
|
|
105
|
-
} else if (now.credits.useFirst && now.credits.total > 0 && hour >= PACE_CHECK_HOUR && now.credits.usedToday < now.credits.paceTarget * 0.5) {
|
|
111
|
+
} else if (now.credits.useFirst && now.credits.total > 0 && (expiryDays === undefined || expiryDays >= 0) && hour >= PACE_CHECK_HOUR && now.credits.usedToday < now.credits.paceTarget * 0.5) {
|
|
106
112
|
// 하루 한 번만 — 날짜를 열쇠에 넣는다.
|
|
107
113
|
key = `pace-behind:${date}`;
|
|
108
114
|
lines = [
|
|
@@ -115,5 +121,5 @@ export function buildLlmBudgetAlert(now: LlmBudgetSnapshot, prevKey?: string): L
|
|
|
115
121
|
}
|
|
116
122
|
|
|
117
123
|
const send = key !== 'ok' && key !== prevKey;
|
|
118
|
-
return { send, key, text: lines.join('\n') };
|
|
124
|
+
return { send, key, text: send ? lines.join('\n') : '' };
|
|
119
125
|
}
|
|
@@ -47,6 +47,8 @@ type RotationReason =
|
|
|
47
47
|
| 'no-candidate'
|
|
48
48
|
/** 찼고 구독 잔량이 남은 계정도 없지만 대표 가 크레딧 사용을 허가했다 — 지금 계정에 머물러 선불 크레딧으로 계속(grok 폴백 안 함). */
|
|
49
49
|
| 'credits-allowed'
|
|
50
|
+
/** 오늘 크레딧 몫을 먼저 쓰기 위해 찬 계정으로 이동하거나 머문다. */
|
|
51
|
+
| 'credit-pace'
|
|
50
52
|
/** 넘겼다. */
|
|
51
53
|
| 'rotated';
|
|
52
54
|
|
|
@@ -101,6 +103,7 @@ interface RotationInput {
|
|
|
101
103
|
/** 지금 계정의 크레딧 잔액·보유(모르면 없다). */
|
|
102
104
|
readonly currentCreditBalance?: number;
|
|
103
105
|
readonly currentHasCredits?: boolean;
|
|
106
|
+
readonly creditPace?: { active: boolean };
|
|
104
107
|
}
|
|
105
108
|
|
|
106
109
|
/**
|
|
@@ -182,6 +185,23 @@ export function decideCodexRotation(input: RotationInput): RotationDecision {
|
|
|
182
185
|
});
|
|
183
186
|
if (input.explicit) return { reason: 'explicit', candidateCount, accountThresholds };
|
|
184
187
|
if (!input.enabled) return { reason: 'disabled', candidateCount, accountThresholds, ...(input.disabledProvenance ? { disabledProvenance: input.disabledProvenance } : {}) };
|
|
188
|
+
if (input.creditPace?.active) {
|
|
189
|
+
const self = input.candidates.find((candidate) => candidate.name === input.current.name);
|
|
190
|
+
const eligible = (name: string, balance: number | undefined, hasCredits: boolean | undefined, home: string | undefined): boolean =>
|
|
191
|
+
!!home?.trim() && typeof balance === 'number' && Number.isFinite(balance) && balance > 0
|
|
192
|
+
&& hasCredits !== false && accountThresholds.some((row) => row.name === name
|
|
193
|
+
&& (row.status === 'reached' || row.status === 'threshold-reached'));
|
|
194
|
+
const currentBalance = input.currentCreditBalance ?? self?.creditBalance;
|
|
195
|
+
const currentHas = input.currentHasCredits ?? self?.hasCredits;
|
|
196
|
+
const to = otherCandidates.filter((candidate) => eligible(candidate.name, candidate.creditBalance, candidate.hasCredits, candidate.home))
|
|
197
|
+
.sort((a, b) => (b.creditBalance! - a.creditBalance!) || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))[0];
|
|
198
|
+
if (eligible(input.current.name, currentBalance, currentHas, self?.home ?? input.current.home)) {
|
|
199
|
+
return { reason: 'credit-pace', candidateCount, accountThresholds };
|
|
200
|
+
}
|
|
201
|
+
if (to) {
|
|
202
|
+
return { reason: 'credit-pace', candidateCount, accountThresholds, to };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
185
205
|
const currentUsageUnknown = input.currentReached === undefined && input.currentUsedPercent == null;
|
|
186
206
|
if (input.currentReached !== true && !currentUsageUnknown && accountThresholds[0]?.status !== 'threshold-reached') {
|
|
187
207
|
return { reason: 'not-reached', candidateCount, accountThresholds };
|
|
@@ -271,7 +291,7 @@ export function observeRotation(decision: RotationDecision, from: string): void
|
|
|
271
291
|
...(decision.reason === 'disabled' && decision.disabledProvenance
|
|
272
292
|
? { disabledProvenance: decision.disabledProvenance }
|
|
273
293
|
: {}),
|
|
274
|
-
}, { level: decision.reason === 'rotated' || decision.reason === 'reset-credit-unknown' ? 'warn' : 'debug' });
|
|
294
|
+
}, { level: decision.reason === 'rotated' || decision.reason === 'reset-credit-unknown' || (decision.reason === 'credit-pace' && decision.to) ? 'warn' : 'debug' });
|
|
275
295
|
for (const account of decision.accountThresholds ?? []) {
|
|
276
296
|
debug.log('oauth.codex-account', 'rotation-account-threshold', account);
|
|
277
297
|
}
|
|
@@ -279,7 +299,8 @@ export function observeRotation(decision: RotationDecision, from: string): void
|
|
|
279
299
|
if (decision.reason !== 'explicit' && decision.reason !== 'disabled') {
|
|
280
300
|
const fromRow = decision.accountThresholds?.find((a) => a.name === from);
|
|
281
301
|
const toCredit = decision.to?.creditBalance;
|
|
282
|
-
const why =
|
|
302
|
+
const why = decision.reason === 'credit-pace' ? '오늘 크레딧 몫을 먼저 쓴다'
|
|
303
|
+
: typeof toCredit === 'number'
|
|
283
304
|
// 금액은 문장에 넣지 않는다 — 판단 사유는 Live·Trace 카드에 글자로 뜨고 공개 캡처가 문장 속 숫자를 못 가린다(09-28 🅣).
|
|
284
305
|
? `구독 한도 전부 참 · ${decision.to!.name} 크레딧이 가장 많다`
|
|
285
306
|
: fromRow ? `${from} ${fromRow.usedPercent ?? '?'}% · 임계 ${fromRow.thresholdPercent}%` : `판정 ${decision.reason}`;
|
|
@@ -287,7 +308,8 @@ export function observeRotation(decision: RotationDecision, from: string): void
|
|
|
287
308
|
kind: 'ROUTE',
|
|
288
309
|
what: decision.to ? `codex 계정 ${from} → ${decision.to.name}` : `codex 계정 ${from} 유지 (${decision.reason})`,
|
|
289
310
|
reason: why,
|
|
290
|
-
purpose: decision.reason === '
|
|
311
|
+
purpose: decision.reason === 'credit-pace' ? '하루 크레딧 몫을 채운 뒤 구독 순서로 복귀'
|
|
312
|
+
: decision.reason === 'credits-allowed' ? '한도가 찼다 — 정책상 크레딧으로 계속'
|
|
291
313
|
: typeof toCredit === 'number' ? '크레딧을 세 계정에 고르게 · 한 계정이 먼저 마르지 않게' : '구독 한도 보존 · 끊김 없이 계속',
|
|
292
314
|
target: decision.to?.name ?? from,
|
|
293
315
|
refs: { account: decision.to?.name ?? from },
|
|
@@ -301,7 +323,7 @@ export function applyRotation(
|
|
|
301
323
|
current: CodexAccountResolution,
|
|
302
324
|
decision: RotationDecision,
|
|
303
325
|
): CodexAccountResolution {
|
|
304
|
-
if ((decision.reason !== 'rotated' && decision.reason !== 'reset-credit-unknown') || !decision.to) return current;
|
|
326
|
+
if ((decision.reason !== 'rotated' && decision.reason !== 'reset-credit-unknown' && decision.reason !== 'credit-pace') || !decision.to) return current;
|
|
305
327
|
return {
|
|
306
328
|
name: decision.to.name,
|
|
307
329
|
storeKey: decision.to.storeKey,
|
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
} from './codex-account.js';
|
|
14
14
|
import { authStorePath, defaultCodexHome, listProviders, loadTokens, saveTokens } from './store.js';
|
|
15
15
|
import { readFreshAvailabilityState, quotaSignalDir, readQuotaSignal, readQuotaSignalCredits, readQuotaSignalUsedPercent, readQuotaSignalObservedAt, readQuotaSignalObservedAtRaw } from '../budget/codex-reset-credit-state.js';
|
|
16
|
+
import { creditPaceStatus } from '../budget/codex-credit-pace.js';
|
|
17
|
+
import { loadLlmPolicy } from '../policy/llm-policy.js';
|
|
16
18
|
import { getUserConfig } from '../user-config.js';
|
|
17
19
|
import {
|
|
18
20
|
decideCodexRotation, observeRotation, applyRotation, readCodexAccountRotationConfig, rotatedChildEnv,
|
|
@@ -278,6 +280,13 @@ function buildRotationCandidates(path: string, now: number, env: NodeJS.ProcessE
|
|
|
278
280
|
.filter((c): c is RotationCandidate => c !== null);
|
|
279
281
|
}
|
|
280
282
|
|
|
283
|
+
function rotationCreditPace(candidates: readonly RotationCandidate[], now: number) {
|
|
284
|
+
const policy = loadLlmPolicy({ now: new Date(now) }).policy;
|
|
285
|
+
const balances = Object.fromEntries(candidates.filter((c) => c.creditBalance !== undefined)
|
|
286
|
+
.map((c) => [c.name, c.creditBalance!]));
|
|
287
|
+
return creditPaceStatus({ policy, balances, now: new Date(now) });
|
|
288
|
+
}
|
|
289
|
+
|
|
281
290
|
function formatUsedPercent(value: number | undefined): string {
|
|
282
291
|
return value === undefined ? 'unknown' : `${value}%`;
|
|
283
292
|
}
|
|
@@ -517,7 +526,7 @@ export function inspectCodexRotation(
|
|
|
517
526
|
const rawThreshold = rotationThresholdFromConfig();
|
|
518
527
|
const thresholdPercentByAccount = rotationThresholdsByAccountFromConfig();
|
|
519
528
|
const decision = decideCodexRotation({
|
|
520
|
-
current, explicit, enabled, disabledProvenance: rotationConfig.state, currentReached, currentUsedPercent, accountOrder: accountOrderFromConfig(), resetCreditAvailability: resetCreditAvailability(currentHome, now), thresholdPercent: rawThreshold, thresholdPercentByAccount, creditsAllowed: codexCreditsAllowedFromConfig(), ...currentCredits, candidates,
|
|
529
|
+
current, explicit, enabled, disabledProvenance: rotationConfig.state, currentReached, currentUsedPercent, accountOrder: accountOrderFromConfig(), resetCreditAvailability: resetCreditAvailability(currentHome, now), thresholdPercent: rawThreshold, thresholdPercentByAccount, creditsAllowed: codexCreditsAllowedFromConfig(), ...currentCredits, candidates, creditPace: rotationCreditPace(candidates, now),
|
|
521
530
|
});
|
|
522
531
|
// ⛔⭐ 나이는 «만료돼도» 낸다 — 「65분 전」과 「3일 전」은 다른 진단이다(리뷰 must-fix).
|
|
523
532
|
const observedAtByHome: Record<string, number> = {};
|
|
@@ -603,6 +612,7 @@ export function resolveCodexAccountForRun(
|
|
|
603
612
|
const candidates = buildRotationCandidates(path, now, env);
|
|
604
613
|
const currentSignalAttributable = signalAttributableToAccount(currentHomeInfo);
|
|
605
614
|
|
|
615
|
+
const creditPace = rotationCreditPace(candidates, now);
|
|
606
616
|
const decision = decideCodexRotation({
|
|
607
617
|
current,
|
|
608
618
|
explicit,
|
|
@@ -617,6 +627,13 @@ export function resolveCodexAccountForRun(
|
|
|
617
627
|
creditsAllowed: codexCreditsAllowedFromConfig(),
|
|
618
628
|
...currentCreditsInput(currentSignalAttributable ? currentHome : undefined, now),
|
|
619
629
|
candidates,
|
|
630
|
+
creditPace,
|
|
631
|
+
});
|
|
632
|
+
debug.log('codex.rotation', 'credit-pace', {
|
|
633
|
+
active: creditPace.active,
|
|
634
|
+
todaySpent: creditPace.todaySpent,
|
|
635
|
+
target: creditPace.target,
|
|
636
|
+
to: decision.reason === 'credit-pace' ? decision.to?.name ?? current.name : decision.to?.name,
|
|
620
637
|
});
|
|
621
638
|
observeRotation(decision, current.name);
|
|
622
639
|
const resolved = applyRotation(current, decision);
|
|
@@ -726,7 +743,7 @@ export function resolveRunFallback(
|
|
|
726
743
|
try {
|
|
727
744
|
const snapshot = inspectCodexRotation(env, deps.storePath ? { storePath: deps.storePath } : {});
|
|
728
745
|
let rotation: RotationOutcome;
|
|
729
|
-
if ((snapshot.reason === 'rotated' || snapshot.reason === 'reset-credit-unknown') && snapshot.to) {
|
|
746
|
+
if ((snapshot.reason === 'rotated' || snapshot.reason === 'reset-credit-unknown' || snapshot.reason === 'credit-pace') && snapshot.to) {
|
|
730
747
|
// ⛔ 스냅샷은 이름만 준다 — 후보 목록에서 «그 객체»를 되찾는다(홈까지 필요하다).
|
|
731
748
|
const to = snapshot.candidates.find((c) => c.name === snapshot.to);
|
|
732
749
|
rotation = to ? { reason: snapshot.reason, to } : { reason: 'no-candidate' };
|
|
@@ -734,6 +751,7 @@ export function resolveRunFallback(
|
|
|
734
751
|
snapshot.reason === 'explicit'
|
|
735
752
|
|| snapshot.reason === 'disabled'
|
|
736
753
|
|| snapshot.reason === 'not-reached'
|
|
754
|
+
|| snapshot.reason === 'credit-pace'
|
|
737
755
|
|| snapshot.reason === 'reset-credit-available'
|
|
738
756
|
|| snapshot.reason === 'no-candidate'
|
|
739
757
|
) {
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { createServer, type Server } from 'node:http';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { debug } from '../debug/log.js';
|
|
5
|
+
import { CODEX_OAUTH_CLIENT_ID, CODEX_OAUTH_TOKEN_URL } from './codex.js';
|
|
6
|
+
import { expiresAtFromSeconds, saveTokens, type ProviderAuthState } from './store.js';
|
|
7
|
+
|
|
8
|
+
const AUTHORIZE_URL = 'https://auth.openai.com/oauth/authorize';
|
|
9
|
+
const DEFAULT_PORT = 1455;
|
|
10
|
+
const TIMEOUT_MS = 5 * 60 * 1000;
|
|
11
|
+
|
|
12
|
+
export interface CodexBrowserLoginOpts {
|
|
13
|
+
fetchImpl?: typeof fetch;
|
|
14
|
+
openBrowser?: (url: string) => boolean | Promise<boolean>;
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
preferredPort?: number;
|
|
17
|
+
mirrorCodex?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function browserLaunchCommand(url: string, platform: NodeJS.Platform = process.platform): { command: string; args: string[] } {
|
|
21
|
+
if (platform === 'win32') {
|
|
22
|
+
// Use the Windows URL handler directly; cmd.exe expands & and % even inside OAuth URLs.
|
|
23
|
+
return { command: 'rundll32.exe', args: ['url.dll,FileProtocolHandler', url] };
|
|
24
|
+
}
|
|
25
|
+
return { command: platform === 'darwin' ? 'open' : 'xdg-open', args: [url] };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** A launcher that exits non-zero this soon has failed; one still running (or exiting 0) handed the URL off. */
|
|
29
|
+
const LAUNCH_GRACE_MS = 1_000;
|
|
30
|
+
|
|
31
|
+
function openBrowser(url: string): Promise<boolean> {
|
|
32
|
+
const { command, args } = browserLaunchCommand(url);
|
|
33
|
+
return new Promise(resolve => {
|
|
34
|
+
let settled = false;
|
|
35
|
+
const settle = (ok: boolean) => { if (!settled) { settled = true; resolve(ok); } };
|
|
36
|
+
try {
|
|
37
|
+
const child = spawn(command, args, { stdio: 'ignore', detached: true });
|
|
38
|
+
child.once('error', () => settle(false));
|
|
39
|
+
child.once('exit', code => settle(code === 0));
|
|
40
|
+
child.once('spawn', () => {
|
|
41
|
+
const grace = setTimeout(() => { child.unref(); settle(true); }, LAUNCH_GRACE_MS);
|
|
42
|
+
grace.unref?.();
|
|
43
|
+
child.once('exit', () => clearTimeout(grace));
|
|
44
|
+
});
|
|
45
|
+
} catch { settle(false); }
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function listen(server: Server, port: number): Promise<void> {
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
server.once('error', reject);
|
|
52
|
+
server.listen(port, '127.0.0.1', () => {
|
|
53
|
+
server.off('error', reject);
|
|
54
|
+
resolve();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Local OAuth callback, independent of a bundled or PATH Codex executable. */
|
|
60
|
+
export async function loginWithCodexBrowser(opts: CodexBrowserLoginOpts = {}): Promise<ProviderAuthState> {
|
|
61
|
+
const verifier = randomBytes(32).toString('base64url');
|
|
62
|
+
const challenge = createHash('sha256').update(verifier).digest('base64url');
|
|
63
|
+
const state = randomBytes(32).toString('base64url');
|
|
64
|
+
let port = opts.preferredPort ?? DEFAULT_PORT;
|
|
65
|
+
let resolveCallback!: (value: ProviderAuthState) => void;
|
|
66
|
+
let rejectCallback!: (reason: Error) => void;
|
|
67
|
+
const callback = new Promise<ProviderAuthState>((resolve, reject) => {
|
|
68
|
+
resolveCallback = resolve;
|
|
69
|
+
rejectCallback = reject;
|
|
70
|
+
});
|
|
71
|
+
void callback.catch(() => {});
|
|
72
|
+
let settled = false;
|
|
73
|
+
let handling = false;
|
|
74
|
+
const tokenAbort = new AbortController();
|
|
75
|
+
const server = createServer((req, res) => {
|
|
76
|
+
const url = new URL(req.url ?? '/', `http://localhost:${port}`);
|
|
77
|
+
if (req.method !== 'GET' || url.pathname !== '/auth/callback') {
|
|
78
|
+
res.writeHead(404).end();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
debug.log('oauth.codex-browser', 'callback', { port });
|
|
82
|
+
if (handling || settled) { res.writeHead(409).end(); return; }
|
|
83
|
+
if (url.searchParams.get('state') !== state) {
|
|
84
|
+
res.writeHead(400).end('Invalid login state.');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const code = url.searchParams.get('code');
|
|
88
|
+
if (!code || url.searchParams.has('error')) {
|
|
89
|
+
res.writeHead(400).end('Login failed. Return to the terminal.');
|
|
90
|
+
rejectCallback(new Error('authorization-denied'));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
handling = true;
|
|
94
|
+
void (async () => {
|
|
95
|
+
try {
|
|
96
|
+
const response = await (opts.fetchImpl ?? fetch)(CODEX_OAUTH_TOKEN_URL, {
|
|
97
|
+
method: 'POST',
|
|
98
|
+
signal: tokenAbort.signal,
|
|
99
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
|
|
100
|
+
body: new URLSearchParams({
|
|
101
|
+
grant_type: 'authorization_code', code, redirect_uri: `http://localhost:${port}/auth/callback`,
|
|
102
|
+
client_id: CODEX_OAUTH_CLIENT_ID, code_verifier: verifier,
|
|
103
|
+
}).toString(),
|
|
104
|
+
});
|
|
105
|
+
if (!response.ok) throw new Error('token-exchange');
|
|
106
|
+
const token = await response.json() as {
|
|
107
|
+
access_token?: string; refresh_token?: string; id_token?: string;
|
|
108
|
+
expires_in?: number; scope?: string; token_type?: string;
|
|
109
|
+
};
|
|
110
|
+
if (!token.access_token || !token.refresh_token) throw new Error('token-exchange');
|
|
111
|
+
if (settled) { res.writeHead(410).end(); return; }
|
|
112
|
+
const saved = saveTokens('openai-codex', {
|
|
113
|
+
accessToken: token.access_token,
|
|
114
|
+
refreshToken: token.refresh_token,
|
|
115
|
+
...(token.id_token ? { idToken: token.id_token } : {}),
|
|
116
|
+
expiresAt: expiresAtFromSeconds(token.expires_in),
|
|
117
|
+
scope: token.scope,
|
|
118
|
+
tokenType: token.token_type ?? 'Bearer',
|
|
119
|
+
}, { authMode: 'chatgpt', mirrorCodex: opts.mirrorCodex });
|
|
120
|
+
debug.log('oauth.codex-browser', 'exchanged', { port });
|
|
121
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
|
|
122
|
+
.end('<!doctype html><html lang="ko"><meta charset="utf-8"><body>로그인됨 — 터미널로 돌아가세요</body></html>');
|
|
123
|
+
resolveCallback(saved);
|
|
124
|
+
} catch {
|
|
125
|
+
res.writeHead(400).end('Login failed. Return to the terminal.');
|
|
126
|
+
rejectCallback(new Error('token-exchange'));
|
|
127
|
+
}
|
|
128
|
+
})();
|
|
129
|
+
});
|
|
130
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
131
|
+
const cancel = () => rejectCallback(new Error('cancelled'));
|
|
132
|
+
const onExit = () => server.close();
|
|
133
|
+
const onServerError = () => rejectCallback(new Error('port-unavailable'));
|
|
134
|
+
let listening = false;
|
|
135
|
+
try {
|
|
136
|
+
try { await listen(server, port); }
|
|
137
|
+
catch (err) {
|
|
138
|
+
if ((err as NodeJS.ErrnoException).code !== 'EADDRINUSE' || port === 0) throw err;
|
|
139
|
+
await listen(server, 0);
|
|
140
|
+
}
|
|
141
|
+
const address = server.address();
|
|
142
|
+
if (!address || typeof address === 'string') throw new Error('port-unavailable');
|
|
143
|
+
listening = true;
|
|
144
|
+
port = address.port;
|
|
145
|
+
server.on('error', onServerError);
|
|
146
|
+
process.once('SIGINT', cancel);
|
|
147
|
+
process.once('exit', onExit);
|
|
148
|
+
timer = setTimeout(() => rejectCallback(new Error('timeout')), opts.timeoutMs ?? TIMEOUT_MS);
|
|
149
|
+
const authorize = new URL(AUTHORIZE_URL);
|
|
150
|
+
for (const [key, value] of Object.entries({
|
|
151
|
+
client_id: CODEX_OAUTH_CLIENT_ID, redirect_uri: `http://localhost:${port}/auth/callback`,
|
|
152
|
+
response_type: 'code', code_challenge: challenge, code_challenge_method: 'S256', state,
|
|
153
|
+
scope: 'openid profile email offline_access', id_token_add_organizations: 'true',
|
|
154
|
+
codex_cli_simplified_flow: 'true', originator: 'codex_cli_rs',
|
|
155
|
+
})) authorize.searchParams.set(key, value);
|
|
156
|
+
debug.log('oauth.codex-browser', 'started', { port });
|
|
157
|
+
if (!await (opts.openBrowser ?? openBrowser)(authorize.toString())) throw new Error('browser-unavailable');
|
|
158
|
+
return await callback;
|
|
159
|
+
} catch (err) {
|
|
160
|
+
const reason = err instanceof Error && ['timeout', 'cancelled', 'browser-unavailable', 'authorization-denied', 'token-exchange'].includes(err.message)
|
|
161
|
+
? err.message : 'port-unavailable';
|
|
162
|
+
debug.log('oauth.codex-browser', 'failed', { port, reason });
|
|
163
|
+
throw new Error(`codex browser login failed: ${reason}`);
|
|
164
|
+
} finally {
|
|
165
|
+
settled = true;
|
|
166
|
+
tokenAbort.abort();
|
|
167
|
+
if (timer) clearTimeout(timer);
|
|
168
|
+
process.off('SIGINT', cancel);
|
|
169
|
+
process.off('exit', onExit);
|
|
170
|
+
if (listening) server.close();
|
|
171
|
+
server.off('error', onServerError);
|
|
172
|
+
}
|
|
173
|
+
}
|
package/src/oauth/codex.ts
CHANGED
|
@@ -121,6 +121,8 @@ interface UserCodeResponse {
|
|
|
121
121
|
device_auth_id: string;
|
|
122
122
|
interval?: number;
|
|
123
123
|
expires_in?: number;
|
|
124
|
+
verification_uri_complete?: string;
|
|
125
|
+
verification_uri?: string;
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
interface PollSuccessResponse {
|
|
@@ -278,7 +280,8 @@ export async function loginWithCodex(opts: CodexLoginOpts = {}): Promise<Provide
|
|
|
278
280
|
const userCode = await requestDeviceCode({ fetchImpl });
|
|
279
281
|
const serverInterval = typeof userCode.interval === 'number' ? userCode.interval * 1000 : 0;
|
|
280
282
|
const pollInterval = Math.max(reportedInterval, serverInterval || 0);
|
|
281
|
-
onProgress({ type: 'user_code', userCode: userCode.user_code,
|
|
283
|
+
onProgress({ type: 'user_code', userCode: userCode.user_code,
|
|
284
|
+
loginUrl: userCode.verification_uri_complete || userCode.verification_uri || CODEX_DEVICE_LOGIN_URL });
|
|
282
285
|
|
|
283
286
|
// 2) Poll until ready or timeout.
|
|
284
287
|
const startedAt = Date.now();
|
|
@@ -69,8 +69,8 @@ export function normalizeFallbackChain(
|
|
|
69
69
|
/** 회전 판정이 낸 이유 — `codex-account-rotation.ts` 의 `RotationReason` 과 같은 어휘.
|
|
70
70
|
* ⛔ 그쪽 타입이 비공개라 구조적으로 받는다(그 파일의 리뷰 규율: dead export 금지). */
|
|
71
71
|
export type RotationOutcome =
|
|
72
|
-
| { readonly reason: 'rotated' | 'reset-credit-unknown'; readonly to: RotationCandidate }
|
|
73
|
-
| { readonly reason: 'explicit' | 'disabled' | 'not-reached' | 'reset-credit-available' | 'no-candidate' };
|
|
72
|
+
| { readonly reason: 'rotated' | 'reset-credit-unknown' | 'credit-pace'; readonly to: RotationCandidate }
|
|
73
|
+
| { readonly reason: 'explicit' | 'disabled' | 'not-reached' | 'credit-pace' | 'reset-credit-available' | 'no-candidate' };
|
|
74
74
|
|
|
75
75
|
export type FallbackDecision =
|
|
76
76
|
/** codex 계정을 갈아탄다(종전 동작). */
|
|
@@ -156,14 +156,14 @@ export function decideFallback(input: FallbackInput): FallbackDecision {
|
|
|
156
156
|
return stepAfter(chain, currentStep, grokAvailable, grokQuota, 'chain-exhausted');
|
|
157
157
|
}
|
|
158
158
|
if (rotation.reason === 'explicit') return { action: 'stay', why: 'explicit' };
|
|
159
|
-
if (rotation.reason === 'not-reached') {
|
|
159
|
+
if (rotation.reason === 'not-reached' || (rotation.reason === 'credit-pace' && !('to' in rotation))) {
|
|
160
160
|
return { action: 'stay', why: 'not-reached' };
|
|
161
161
|
}
|
|
162
162
|
if (rotation.reason === 'reset-credit-available' && stayOnResetCreditAvailable) {
|
|
163
163
|
return { action: 'stay', why: 'reset-credit-available' };
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
if (rotation.reason === 'rotated' || rotation.reason === 'reset-credit-unknown') {
|
|
166
|
+
if (rotation.reason === 'rotated' || rotation.reason === 'reset-credit-unknown' || (rotation.reason === 'credit-pace' && 'to' in rotation)) {
|
|
167
167
|
// 회전이 답을 냈는데 체인이 codex-rotate 를 «빼» 놨다면 그 뜻을 존중하고
|
|
168
168
|
// 다음 칸으로 간다(회전을 원치 않는 구성).
|
|
169
169
|
if (chain.includes('codex-rotate')) return { action: 'codex-rotate', to: rotation.to };
|
|
@@ -172,7 +172,7 @@ export function decideFallback(input: FallbackInput): FallbackDecision {
|
|
|
172
172
|
|
|
173
173
|
// no-candidate | disabled | reset-credit-available(기본) — codex 축이 끝났다. 다음 칸을 본다.
|
|
174
174
|
// ⛔ 다음 칸이 «없을 때»의 이름만 고른다. 진행(stepAfterCodex)은 그대로다.
|
|
175
|
-
const fallbackWhy = stayReasonWhenCodexAxisEnds(rotation.reason);
|
|
175
|
+
const fallbackWhy = stayReasonWhenCodexAxisEnds(rotation.reason === 'credit-pace' ? 'no-candidate' : rotation.reason);
|
|
176
176
|
return stepAfterCodex(chain, grokAvailable, grokQuota, fallbackWhy);
|
|
177
177
|
}
|
|
178
178
|
|