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
package/src/cli/plugin-cli.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { OFFICIAL_INDEX_KEYS } from '../market/official-keys.js';
|
|
|
12
12
|
import { installPlugin, listInstalledPlugins, PluginInstallError, removePlugin, type InstallEvent } from '../plugins/install/plugin-install.js';
|
|
13
13
|
import { credentialStatus, setPluginCredentials } from '../plugins/install/plugin-credentials.js';
|
|
14
14
|
import { makePlugin } from '../plugins/maker/plugin-maker.js';
|
|
15
|
+
import { addAndInstallNode } from '../plugins/maker/node-install.js';
|
|
15
16
|
|
|
16
17
|
function keys(): ReadonlyArray<{ keyId: string; publicKey: string }> {
|
|
17
18
|
const configPath = userConfigPath();
|
|
@@ -83,6 +84,30 @@ export function registerPluginCommands(program: Command): void {
|
|
|
83
84
|
process.exitCode = 1;
|
|
84
85
|
}
|
|
85
86
|
});
|
|
87
|
+
plugin.command('node').description('Manage plugin workflow nodes')
|
|
88
|
+
.command('add <dir> <request>').description('Write, validate and install a workflow node in a plugin')
|
|
89
|
+
.option('--kind <slug>', 'Workflow node kind')
|
|
90
|
+
.option('--yes', 'Accept requested capabilities')
|
|
91
|
+
.option('--json', 'Print the result as one JSON line')
|
|
92
|
+
.action(async (dir: string, request: string, opts: { kind?: string; yes?: boolean; json?: boolean }) => {
|
|
93
|
+
try {
|
|
94
|
+
const result = await addAndInstallNode({ dir, request, kind: opts.kind, yes: opts.yes,
|
|
95
|
+
deps: { consent: capabilities => askConsent(capabilities, !!opts.json) } });
|
|
96
|
+
if (opts.json) stdout.write(JSON.stringify(result) + '\n');
|
|
97
|
+
else {
|
|
98
|
+
console.log(`작성 ${result.timings.write}ms · 검증 ${result.timings.validate}ms${result.timings.repair === undefined ? '' : ` · 수리 ${result.timings.repair}ms`}`);
|
|
99
|
+
console.log(`설치 ${result.plugin ? '완료' : result.status === 'failed' && result.timings.install === 0 ? '미실행' : '실패'} · ${result.timings.install}ms`);
|
|
100
|
+
console.log(`동기화 ${result.status === 'installed' ? '완료' : result.plugin ? '실패' : '미실행'} · ${result.timings.sync}ms`);
|
|
101
|
+
for (const error of result.errors) console.error(error);
|
|
102
|
+
}
|
|
103
|
+
if (result.status === 'failed') process.exitCode = 1;
|
|
104
|
+
} catch (error) {
|
|
105
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
106
|
+
if (opts.json) stdout.write(JSON.stringify({ status: 'failed', errors: [message] }) + '\n');
|
|
107
|
+
else console.error(`plugin node add failed: ${message}`);
|
|
108
|
+
process.exitCode = 1;
|
|
109
|
+
}
|
|
110
|
+
});
|
|
86
111
|
plugin.command('add <spec>').description('Install a local path, pinned git plugin or signed market plugin')
|
|
87
112
|
.option('--yes', 'Accept requested capabilities')
|
|
88
113
|
.option('--json', 'NDJSON progress events')
|
package/src/cli/pr-cli.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { runMockModuleRestoreGate } from '../../scripts/ci-mock-module-restore-g
|
|
|
17
17
|
import { runModelHardcodeGate } from '../../scripts/ci-model-hardcode-gate.js';
|
|
18
18
|
import { runDaemonPortGate } from '../../scripts/ci-daemon-port-gate.js';
|
|
19
19
|
import { runPublicLeakGate } from '../../scripts/ci-public-leak-gate.js';
|
|
20
|
+
import { exportLeakCheck, type ExportLeakCheck } from '../../scripts/ci-public-export-leak-gate.js';
|
|
20
21
|
import { checkCommands, extractElanousCommands, type Finding as DocsCliFinding } from '../../scripts/docs-cli-check.js';
|
|
21
22
|
import { runTestInterferenceGate } from '../../scripts/ci-test-interference-gate.js';
|
|
22
23
|
import { isGoalDocumentFileName } from '../self-implement/goal-document.js';
|
|
@@ -68,6 +69,8 @@ export interface PrLandOpts {
|
|
|
68
69
|
excludeActiveRunFiles?: boolean;
|
|
69
70
|
excludeActiveRunSources?: boolean;
|
|
70
71
|
landReason?: string;
|
|
72
|
+
/** Land even though changed files would leak into the public export (the reason goes in --land-reason). */
|
|
73
|
+
allowPublicLeak?: boolean;
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
export interface PrGranularityOpts {
|
|
@@ -101,6 +104,8 @@ export interface PrLandDeps {
|
|
|
101
104
|
runDaemonPortGate?: (out: { log: (message: string) => void; error: (message: string) => void }) => boolean;
|
|
102
105
|
/** 공개 유출 래칫(경고 전용) — 반환 0 통과 · 1 늘었다 · 2 못 쟀다. */
|
|
103
106
|
runPublicLeakGate?: (changedFiles: readonly string[], out: { log: (message: string) => void; error: (message: string) => void }) => number;
|
|
107
|
+
/** Test seam — the post-transform export leak check on changed files. */
|
|
108
|
+
runExportLeakCheck?: (changedFiles: readonly string[], cwd: string) => ExportLeakCheck;
|
|
104
109
|
/** 공개 문서의 `elanous …` 호출 ↔ 실제 `--help` 대조(경고 전용) — 바뀐 공개 문서 경로를 받아 어긋남 목록을 돌려준다. */
|
|
105
110
|
runDocsCliCheck?: (docFiles: readonly string[]) => DocsCliFinding[];
|
|
106
111
|
/** 변경 시험 파일 간 간섭 검사 심(시험 주입용). */
|
|
@@ -269,6 +274,35 @@ function runPrLandPublicLeakGate(changedFiles: readonly string[], out: { log: (m
|
|
|
269
274
|
return runPublicLeakGate({ args: ['--changed-files', ...changedFiles], log: out.log, error: out.log });
|
|
270
275
|
}
|
|
271
276
|
|
|
277
|
+
/** The prepare check on this landing's changed files — scripts/ci-public-export-leak-gate.ts. */
|
|
278
|
+
export function runPrLandExportLeakCheck(changedFiles: readonly string[], cwd: string): ExportLeakCheck {
|
|
279
|
+
const top = runGitCommand(cwd, ['rev-parse', '--show-toplevel'], { encoding: 'utf-8' });
|
|
280
|
+
return exportLeakCheck(changedFiles, top.status === 0 && String(top.stdout ?? '').trim() ? String(top.stdout).trim() : cwd);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** Blocks the landing when changed files would leak into the public export, unless allowPublicLeak. Returns false to stop. */
|
|
284
|
+
export function exportLeakBlock(
|
|
285
|
+
changedFiles: readonly string[] | undefined,
|
|
286
|
+
check: () => ExportLeakCheck,
|
|
287
|
+
allow: boolean,
|
|
288
|
+
out: { log: (message: string) => void; error: (message: string) => void },
|
|
289
|
+
): boolean {
|
|
290
|
+
if (!changedFiles || changedFiles.length === 0) return true;
|
|
291
|
+
let result: ExportLeakCheck;
|
|
292
|
+
try { result = check(); } catch (error) { result = { measured: false, hits: [], detail: error instanceof Error ? error.message : String(error) }; }
|
|
293
|
+
record('public-export-leak', result.measured ? result.hits.length === 0 || allow : true, { measured: result.measured, hits: result.hits.length, allowed: allow && result.hits.length > 0 });
|
|
294
|
+
if (!result.measured) { out.log(`⚠ public-export-leak: 못 쟀다(${result.detail ?? '?'}) — 착지는 막지 않는다. 확인: bun scripts/public-export.ts --leak-check --files <파일…>`); return true; }
|
|
295
|
+
if (result.hits.length === 0) { out.log('✓ public-export-leak: 바뀐 파일이 공개본에 사적 흔적을 싣지 않는다(prepare 와 같은 변환 뒤).'); return true; }
|
|
296
|
+
const write = allow ? out.log : out.error;
|
|
297
|
+
write(`${allow ? '⚠' : '✗'} public-export-leak: 바뀐 파일 ${result.hits.length}곳이 공개본에 사적 흔적을 싣는다 — 다음 release prepare 가 여기서 실패한다:`);
|
|
298
|
+
for (const h of result.hits.slice(0, 20)) write(` ${h.file}:${h.line} ${h.marker}`);
|
|
299
|
+
if (result.hits.length > 20) write(` … 외 ${result.hits.length - 20}곳`);
|
|
300
|
+
write(' 확인: bun scripts/public-export.ts --leak-check --all --files <파일…>');
|
|
301
|
+
if (allow) { out.log(' --allow-public-leak 로 넘긴다(이유는 --land-reason).'); return true; }
|
|
302
|
+
out.error(' 고치거나, 공개본과 무관한 것이 확실하면 --allow-public-leak --land-reason "<이유>" 로 넘긴다.');
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
|
|
272
306
|
export function publicLeakWarning(
|
|
273
307
|
changedFiles: readonly string[] | undefined,
|
|
274
308
|
gate: (changedFiles: readonly string[], out: { log: (message: string) => void; error: (message: string) => void }) => number,
|
|
@@ -1286,6 +1320,7 @@ export async function runPrLand(opts: PrLandOpts = {}, deps: PrLandDeps = {}): P
|
|
|
1286
1320
|
|
|
1287
1321
|
const changedPaths = currentChangePaths(run, cwd, staged, base, out);
|
|
1288
1322
|
publicLeakWarning(changedPaths, deps.runPublicLeakGate ?? runPrLandPublicLeakGate, out);
|
|
1323
|
+
if (!exportLeakBlock(changedPaths, () => (deps.runExportLeakCheck ?? runPrLandExportLeakCheck)(changedPaths ?? [], cwd), opts.allowPublicLeak === true, out)) return 1;
|
|
1289
1324
|
if (publicDocPaths(changedPaths, () => true).length > 0) {
|
|
1290
1325
|
const top = run('git', ['rev-parse', '--show-toplevel'], { cwd });
|
|
1291
1326
|
const docsRoot = top.ok && top.out.trim() ? top.out.trim() : cwd;
|
|
@@ -1549,6 +1584,7 @@ export function registerPrCommands(program: Command, deps: PrLandDeps = {}): voi
|
|
|
1549
1584
|
.option('--exclude-active-run-files', '도는 런과 겹친 파일을 이번 착지에서 제외')
|
|
1550
1585
|
.option('--exclude-active-run-sources', '⚠ 위험: --exclude-active-run-files 가 골 문서가 아닌 파일(소스)을 빼도 착지를 강행')
|
|
1551
1586
|
.option('--land-reason <text>', '권고를 보고도 지금 내는 이유(예: 남이 기다리는 차단 해제)')
|
|
1587
|
+
.option('--allow-public-leak', '바뀐 파일이 공개본에 사적 흔적을 실어도 착지(이유는 --land-reason)')
|
|
1552
1588
|
.action(async (opts: PrLandOpts) => {
|
|
1553
1589
|
// ⛔⭐⭐⭐ **관측 sink 를 먼저 건다** — 라이브 도그푸드가 잡은 결함(2026-08-03).
|
|
1554
1590
|
// 단위 테스트는 `debug.log` 가 **불렸다**를 단언하지만, standalone CLI 는 sink 를 등록하지
|
package/src/cli/provider-cli.ts
CHANGED
|
@@ -239,6 +239,9 @@ export function registerProviderCommands(program: Command): void {
|
|
|
239
239
|
// ⛔⭐ 「후보」는 판정기가 «자기 자신을 뺀» 것이다 — 표면이 현재 계정을 후보로 보여 주면
|
|
240
240
|
// ***있지도 않은 선택지를 말한다***(리뷰 must-fix). 판정기와 같은 기준(storeKey)으로 거른다.
|
|
241
241
|
const shownCandidates = s.candidates.filter((c) => c.storeKey !== s.current.storeKey);
|
|
242
|
+
const { codexCreditPlanFromDisk } = await import('./codex-credit-plan-view.js');
|
|
243
|
+
const { formatCodexCreditPlan } = await import('../budget/codex-credit-plan.js');
|
|
244
|
+
const creditPlan = codexCreditPlanFromDisk(now);
|
|
242
245
|
const ageMinOf = (home: string | undefined): number | null => {
|
|
243
246
|
if (!home) return null;
|
|
244
247
|
const at = home === s.currentHome ? s.currentObservedAt : s.observedAtByHome[home];
|
|
@@ -258,6 +261,7 @@ export function registerProviderCommands(program: Command): void {
|
|
|
258
261
|
if (opts.json) {
|
|
259
262
|
await writeStdoutJson(JSON.stringify({
|
|
260
263
|
policy: { value: s.policy.policy, source: s.policy.source },
|
|
264
|
+
creditPlan,
|
|
261
265
|
universe: {
|
|
262
266
|
instanceRoot, signalDir, authStore: authStorePath(),
|
|
263
267
|
// ⭐ JSON 에도 싣는다 — 화면만 알면 스크립트가 못 센다
|
|
@@ -303,6 +307,7 @@ export function registerProviderCommands(program: Command): void {
|
|
|
303
307
|
const policySource = s.policy.source === 'legacy-credits' ? ' · 옛 codexCreditsAllowed 에서'
|
|
304
308
|
: s.policy.source === 'default' ? ' · 기본값' : '';
|
|
305
309
|
console.log(`정책 ${s.policy.policy} (${CODEX_QUOTA_POLICY_LABEL[s.policy.policy]} · llm.codexQuotaPolicy)${policySource}`);
|
|
310
|
+
for (const line of formatCodexCreditPlan(creditPlan)) console.log(line);
|
|
306
311
|
console.log(`지금 계정 ${s.current.name} (source=${s.current.source})`);
|
|
307
312
|
console.log(` 홈=${s.currentHome ?? '(모름)'} 사용=${s.currentUsedPercent ?? '?'}% 찼나=${s.currentReached ?? '모름'} 신호=${ageOf(s.currentHome)} 크레딧=${creditText(s.currentCreditBalance, s.currentHasCredits)}`);
|
|
308
313
|
console.log('후보');
|
package/src/cli/release-cli.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// elanous release tag --version <x.y.z> --source <commit> [--yes]
|
|
6
6
|
// elanous release verify [--version <x.y.z>]
|
|
7
7
|
// elanous release notes --from <ref> [--to <ref>]
|
|
8
|
+
// elanous release run --version <x.y.z> [--dry-run] [--json] [--if-ready]
|
|
8
9
|
//
|
|
9
10
|
// 🩸 계기(2026-09-25 v0.1.0 첫 공개): 손으로 밟은 절차에서 둘을 빠뜨릴 뻔했다 — 공개본 git 커밋(판 커밋이 공개본 커밋이 된다) ·
|
|
10
11
|
// PWA 빌드(빌드 산출은 공개본에 안 실린다 → 빠뜨리면 «웹 화면 없는 판»). 절차를 명령으로 굳힌다.
|
|
@@ -24,6 +25,9 @@ import { envLiteral } from '../platform/env-literal.js';
|
|
|
24
25
|
import { userConfigPath } from '../user-config.js';
|
|
25
26
|
import { addItem, devVersion, listChecklist, removeItem, seedFromRoadmap, setItem, summarizeChecklist, type ChecklistStatus, type ChecklistDisposition } from '../release-loop/checklist.js';
|
|
26
27
|
import { writeStdoutJson } from './stdout-json.js';
|
|
28
|
+
import { runUnattendedRelease, type UnattendedReleaseDeps } from '../../scripts/release-loop/unattended-release.js';
|
|
29
|
+
import { releaseReadiness } from '../../scripts/release-loop/release-readiness.js';
|
|
30
|
+
import { runReleaseIfReady } from './release-run-if-ready.js';
|
|
27
31
|
|
|
28
32
|
export const DEFAULT_PUBLIC_REPO = 'ElanvitalAI/elanous';
|
|
29
33
|
const SEMVER = /^\d+\.\d+\.\d+(?:-(?:rc|alpha|beta)\.\d+)?$/;
|
|
@@ -482,7 +486,7 @@ async function checklistOutput(v: string, codenames: Record<string, string>, mod
|
|
|
482
486
|
}
|
|
483
487
|
}
|
|
484
488
|
|
|
485
|
-
export function registerReleaseCommands(program: Command): void {
|
|
489
|
+
export function registerReleaseCommands(program: Command, releaseRunDeps: UnattendedReleaseDeps = {}): void {
|
|
486
490
|
const release = program.command('release').description('공개 배포 한 판 — prepare(로컬) → publish(--yes) → verify (docs/manual/MANUAL-versioning-and-release-2026-09-25.md)');
|
|
487
491
|
const checklist = release.command('checklist').description('판별 확인표 조회·갱신')
|
|
488
492
|
.option('--version <v>', '판 또는 별칭(기본: package.json 의 개발판에서 -dev.N 제거)')
|
|
@@ -587,4 +591,41 @@ export function registerReleaseCommands(program: Command): void {
|
|
|
587
591
|
const { draftReleaseNotes, readLandedCommits, renderReleaseNotes } = await import('../../scripts/release-notes.js');
|
|
588
592
|
console.log(renderReleaseNotes(draftReleaseNotes(readLandedCommits(o.from, o.to), o.from, o.to)));
|
|
589
593
|
});
|
|
594
|
+
release.command('run')
|
|
595
|
+
.description('릴리스 루프 실행 — 원장 직전 판과 release.loop 설정으로 그래프 입력 구성 · --dry-run 은 입력만 출력')
|
|
596
|
+
.requiredOption('--version <v>', '공개할 판(x.y.z)')
|
|
597
|
+
.option('--dry-run', '체크리스트·그래프 실행 없이 전체 입력 보기')
|
|
598
|
+
.option('--if-ready', '준비되지 않았으면 사유를 출력하고 성공으로 건너뛴다')
|
|
599
|
+
.option('--json', '결과 한 줄 JSON(stdout) · 사람 줄은 stderr')
|
|
600
|
+
.action(async (o: { version: string; dryRun?: boolean; ifReady?: boolean; json?: boolean }) => {
|
|
601
|
+
if (o.ifReady && !o.dryRun) {
|
|
602
|
+
try {
|
|
603
|
+
const outcome = await runReleaseIfReady(o.version, {
|
|
604
|
+
ledgerRoot: releaseRunDeps.ledgerRoot,
|
|
605
|
+
readiness: (version, options) => releaseReadiness(version, { ledgerRoot: releaseRunDeps.ledgerRoot, checklist: releaseRunDeps.checklist, ...options }),
|
|
606
|
+
run: () => runUnattendedRelease({ version: o.version }, releaseRunDeps),
|
|
607
|
+
});
|
|
608
|
+
if (outcome.skipped) {
|
|
609
|
+
if (o.json) await writeStdoutJson(`${JSON.stringify(outcome)}\n`);
|
|
610
|
+
else console.log(`· 준비 안 됨 ${o.version} · ${outcome.reason} · ${outcome.detail}`);
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
const result = outcome.result;
|
|
614
|
+
const ok = result.dryRun || result.state?.status === 'done' || result.state?.status === 'awaiting-approval';
|
|
615
|
+
(o.json ? console.error : console.log)(`${result.dryRun ? '· 드라이런' : '▶ 릴리스 루프'} ${result.input.version} · 입력 ${JSON.stringify(result.input)}`);
|
|
616
|
+
if (o.json) await writeStdoutJson(`${JSON.stringify({ ok, ...result })}\n`);
|
|
617
|
+
if (!ok && !process.exitCode) process.exitCode = 1;
|
|
618
|
+
} catch (e) {
|
|
619
|
+
if (o.json) await writeStdoutJson(`${JSON.stringify({ ok: false, error: (e as Error).message })}\n`);
|
|
620
|
+
else console.error(`⛔ ${(e as Error).message}`);
|
|
621
|
+
process.exitCode = 1;
|
|
622
|
+
}
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
await jsonAction(o.json, async (log) => {
|
|
626
|
+
const result = await runUnattendedRelease({ version: o.version, dryRun: o.dryRun }, releaseRunDeps);
|
|
627
|
+
log(`${result.dryRun ? '· 드라이런' : '▶ 릴리스 루프'} ${result.input.version} · 입력 ${JSON.stringify(result.input)}`);
|
|
628
|
+
return result;
|
|
629
|
+
}, (r) => r.dryRun || r.state?.status === 'done' || r.state?.status === 'awaiting-approval');
|
|
630
|
+
});
|
|
590
631
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { closeSync, mkdirSync, openSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { debug } from '../debug/log.js';
|
|
4
|
+
import { releaseLedgerRoot } from '../instance/resolve.js';
|
|
5
|
+
import type { ReleaseReadiness } from '../../scripts/release-loop/release-readiness.js';
|
|
6
|
+
|
|
7
|
+
export type ReleaseIfReadySkip = { skipped: true; reason: Exclude<ReleaseReadiness, { ready: true }>['reason']; detail: string };
|
|
8
|
+
|
|
9
|
+
export async function runReleaseIfReady<T>(version: string, deps: {
|
|
10
|
+
readiness: (version: string, options?: { isPidAlive?: (pid: number) => boolean }) => ReleaseReadiness | Promise<ReleaseReadiness>;
|
|
11
|
+
run: () => Promise<T>;
|
|
12
|
+
ledgerRoot?: string;
|
|
13
|
+
pid?: number;
|
|
14
|
+
now?: () => Date;
|
|
15
|
+
}): Promise<ReleaseIfReadySkip | { skipped: false; result: T }> {
|
|
16
|
+
const skip = (readiness: Exclude<ReleaseReadiness, { ready: true }>): ReleaseIfReadySkip => {
|
|
17
|
+
const detail = readiness.reason === 'checklist-blocked'
|
|
18
|
+
? [...readiness.details.red, ...readiness.details.undecided, ...readiness.details.blocked].join(', ')
|
|
19
|
+
: readiness.details;
|
|
20
|
+
debug.log('release-loop.readiness', readiness.reason, { version, detail });
|
|
21
|
+
return { skipped: true, reason: readiness.reason, detail };
|
|
22
|
+
};
|
|
23
|
+
const readiness = await deps.readiness(version);
|
|
24
|
+
if (!readiness.ready) return skip(readiness);
|
|
25
|
+
|
|
26
|
+
const dir = join(deps.ledgerRoot ?? releaseLedgerRoot(), 'release', version);
|
|
27
|
+
mkdirSync(dir, { recursive: true });
|
|
28
|
+
const lock = join(dir, 'run.lock');
|
|
29
|
+
let fd: number;
|
|
30
|
+
try { fd = openSync(lock, 'wx', 0o600); }
|
|
31
|
+
catch (error) {
|
|
32
|
+
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
|
|
33
|
+
const detail = 'run.lock already exists';
|
|
34
|
+
debug.log('release-loop.readiness', 'already-running', { version, detail });
|
|
35
|
+
return { skipped: true, reason: 'already-running', detail };
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
writeFileSync(fd, `${JSON.stringify({ pid: deps.pid ?? process.pid, startedAt: (deps.now ?? (() => new Date()))().toISOString() })}\n`);
|
|
39
|
+
// This call still checks publication and checklist, but must not treat our own lock as another runner.
|
|
40
|
+
const insideLock = await deps.readiness(version, { isPidAlive: () => false });
|
|
41
|
+
if (!insideLock.ready) return skip(insideLock);
|
|
42
|
+
debug.log('release-loop.readiness', 'ready', { version });
|
|
43
|
+
return { skipped: false, result: await deps.run() };
|
|
44
|
+
} finally {
|
|
45
|
+
closeSync(fd);
|
|
46
|
+
rmSync(lock);
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/cli/setup-cli.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import
|
|
5
|
-
import { formatDoctorReport, runDoctor, type DoctorOptions, type DoctorReport } from './doctor-cli.js';
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
import { formatDoctorReport, registerDoctorCommand, runDoctor, type DoctorOptions, type DoctorReport } from './doctor-cli.js';
|
|
6
6
|
import { applyClaudePluginSetup, planClaudePluginSetup } from './claude-plugin-setup.js';
|
|
7
|
+
import { recommendedSetup } from './setup-recommend.js';
|
|
7
8
|
import { debug } from '../debug/log.js';
|
|
8
9
|
import { getUserConfig, saveUserConfig, type UserConfig } from '../user-config.js';
|
|
9
10
|
|
|
@@ -20,6 +21,7 @@ type SetupStep = {
|
|
|
20
21
|
|
|
21
22
|
export interface SetupCliDeps extends DoctorOptions {
|
|
22
23
|
runDoctor?: (options: DoctorOptions) => DoctorReport;
|
|
24
|
+
runDoctorFix?: (args: readonly string[]) => Promise<number>;
|
|
23
25
|
readFile?: (path: string) => string;
|
|
24
26
|
exists?: (path: string) => boolean;
|
|
25
27
|
homeDir?: () => string;
|
|
@@ -154,14 +156,16 @@ export function registerSetupCommand(program: Command, deps: SetupCliDeps = {}):
|
|
|
154
156
|
const setup = program.command('setup')
|
|
155
157
|
.description('Check OpenAI Codex setup and guide each missing credential step')
|
|
156
158
|
.option('--non-interactive', 'Report setup state without prompts or writes')
|
|
157
|
-
.
|
|
159
|
+
.option('--yes', 'Set a missing Codex provider and apply recommended doctor repairs without asking')
|
|
160
|
+
.action(async (opts: { nonInteractive?: boolean; yes?: boolean }) => {
|
|
158
161
|
const doctor = doctorRunner(deps);
|
|
159
162
|
if (opts.nonInteractive) {
|
|
160
163
|
out.log(formatSetupReport(doctor, setupSteps(deps), true));
|
|
164
|
+
for (const line of recommendedSetup((deps.getUserConfig ?? getUserConfig)())) out.log(line);
|
|
161
165
|
setExitCode(0);
|
|
162
166
|
return;
|
|
163
167
|
}
|
|
164
|
-
if (!isStdinTty()) {
|
|
168
|
+
if (!isStdinTty() && !opts.yes) {
|
|
165
169
|
(out.error ?? out.log)('대화형 온보딩은 stdin TTY가 있는 자리에서만 실행할 수 있다. 무인 설정은 `elanous setup --non-interactive`를 사용하라.');
|
|
166
170
|
setExitCode(1);
|
|
167
171
|
return;
|
|
@@ -169,8 +173,8 @@ export function registerSetupCommand(program: Command, deps: SetupCliDeps = {}):
|
|
|
169
173
|
let steps = setupSteps(deps);
|
|
170
174
|
const provider = steps[1]!;
|
|
171
175
|
if (!provider.complete) {
|
|
172
|
-
const
|
|
173
|
-
if (
|
|
176
|
+
const approved = opts.yes || (await prompt('Set llm.provider to openai-codex now? [y/N] '))?.trim().toLowerCase() === 'y';
|
|
177
|
+
if (approved) {
|
|
174
178
|
const config = (deps.getUserConfig ?? getUserConfig)();
|
|
175
179
|
(deps.saveUserConfig ?? saveUserConfig)({ ...config, llm: { ...config.llm, provider: 'openai-codex' } });
|
|
176
180
|
steps = setupSteps(deps);
|
|
@@ -179,6 +183,23 @@ export function registerSetupCommand(program: Command, deps: SetupCliDeps = {}):
|
|
|
179
183
|
}
|
|
180
184
|
}
|
|
181
185
|
out.log(formatSetupReport(doctor, steps, false));
|
|
186
|
+
for (const line of recommendedSetup((deps.getUserConfig ?? getUserConfig)())) out.log(line);
|
|
187
|
+
const accepted = opts.yes || (isStdinTty() && /^(?:y|yes)?$/i.test((await prompt('추천대로 켤까요? [Y/n] '))?.trim() ?? 'n'));
|
|
188
|
+
if (accepted) {
|
|
189
|
+
const runDoctorFix = deps.runDoctorFix ?? (async (args: readonly string[]) => {
|
|
190
|
+
const doctorProgram = new Command();
|
|
191
|
+
let exitCode = 0;
|
|
192
|
+
registerDoctorCommand(doctorProgram, {
|
|
193
|
+
out,
|
|
194
|
+
err: { error: (message) => (out.error ?? out.log)(message) },
|
|
195
|
+
setExitCode: (code) => { exitCode = code; },
|
|
196
|
+
});
|
|
197
|
+
await doctorProgram.parseAsync([...args], { from: 'user' });
|
|
198
|
+
return exitCode;
|
|
199
|
+
});
|
|
200
|
+
setExitCode(await runDoctorFix(['doctor', '--fix', '--yes']));
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
182
203
|
setExitCode(0);
|
|
183
204
|
});
|
|
184
205
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function recommendedSetup(state: { chat: { fastPath: boolean } }): [string, string] {
|
|
2
|
+
return [
|
|
3
|
+
'고칠 것 고치기 — 권한·PATH·서비스 파일·파이썬 환경 등 고칠 수 있는 항목을 고칩니다(로그인·서비스 재시작은 별도): `elanous doctor --fix --yes`',
|
|
4
|
+
state.chat.fastPath
|
|
5
|
+
? '짧은 물음은 빠르게 — 켜져 있음(chat.fastPath=true): 짧은 물음에 도구 없이 먼저 답합니다. 끄기: `elanous config set chat.fastPath false`'
|
|
6
|
+
: '짧은 물음은 빠르게 — 꺼져 있음(chat.fastPath=false): 켜면 짧은 물음에 도구 없이 먼저 답합니다. 켜기: `elanous config set chat.fastPath true`',
|
|
7
|
+
];
|
|
8
|
+
}
|
package/src/cli/usage-cli.ts
CHANGED
|
@@ -12,9 +12,13 @@ import { consumeCodexResetCredits, listCodexResetCredits } from '../budget/codex
|
|
|
12
12
|
import { rollupRunUsage, type RunUsageInput } from '../budget/run-usage-rollup.js';
|
|
13
13
|
import { LogStore, logsDbPath, type LogQuery, type LogStoreRow } from '../mss/logging/log-store.js';
|
|
14
14
|
import { LOGS_SINCE_OPTION, parseSince } from './logs-cli.js';
|
|
15
|
+
import { formatCodexCreditPlan, type CodexCreditPlan } from '../budget/codex-credit-plan.js';
|
|
16
|
+
import { codexCreditPlanFromDisk } from './codex-credit-plan-view.js';
|
|
15
17
|
|
|
16
18
|
export interface UsageCliDeps {
|
|
17
19
|
readonly collect?: typeof collectUnifiedUsage;
|
|
20
|
+
/** Codex credit plan (disk only). Returns null when it cannot be read. */
|
|
21
|
+
readonly creditPlan?: () => CodexCreditPlan | null;
|
|
18
22
|
readonly out?: { log: (s: string) => void };
|
|
19
23
|
/** Read-only LogStore query seam; returns stored llm-usage rows, newest first. */
|
|
20
24
|
readonly readRunLogs?: (query: LogQuery) => LogStoreRow[];
|
|
@@ -61,6 +65,10 @@ function usageData(row: LogStoreRow): RunUsageInput | null {
|
|
|
61
65
|
} catch { return null; }
|
|
62
66
|
}
|
|
63
67
|
|
|
68
|
+
function defaultCreditPlan(): CodexCreditPlan | null {
|
|
69
|
+
try { return codexCreditPlanFromDisk(); } catch { return null; }
|
|
70
|
+
}
|
|
71
|
+
|
|
64
72
|
export function registerUsageCommand(program: Command, deps: UsageCliDeps = {}): void {
|
|
65
73
|
const out = deps.out ?? { log: (s: string) => console.log(s) };
|
|
66
74
|
const collect = deps.collect ?? collectUnifiedUsage;
|
|
@@ -71,11 +79,13 @@ export function registerUsageCommand(program: Command, deps: UsageCliDeps = {}):
|
|
|
71
79
|
.option('--json', 'JSON 출력 — 자격 값은 없다')
|
|
72
80
|
.action(async (opts: { json?: boolean }) => {
|
|
73
81
|
const report = await collect();
|
|
82
|
+
const creditPlan = (deps.creditPlan ?? defaultCreditPlan)();
|
|
74
83
|
if (opts.json) {
|
|
75
|
-
out.log(JSON.stringify(report, null, 2));
|
|
84
|
+
out.log(JSON.stringify({ ...report, codexCreditPlan: creditPlan }, null, 2));
|
|
76
85
|
return;
|
|
77
86
|
}
|
|
78
87
|
out.log(formatUnifiedUsage(report));
|
|
88
|
+
if (creditPlan) out.log(['', 'codex 크레딧', ...formatCodexCreditPlan(creditPlan).map((l) => ` ${l}`)].join('\n'));
|
|
79
89
|
});
|
|
80
90
|
|
|
81
91
|
usage
|
package/src/codex/setup.ts
CHANGED
|
@@ -62,14 +62,33 @@ export interface CodexSetupResult {
|
|
|
62
62
|
|
|
63
63
|
// ── Picker helpers (shared with onboarding) ──────────────────────────
|
|
64
64
|
|
|
65
|
-
export
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
65
|
+
export interface CodexAuthModeDeps {
|
|
66
|
+
loadTokens?: typeof loadTokens;
|
|
67
|
+
/** OB1 — a codex CLI login (~/.codex/auth.json) the runtime already uses; default = `inspectSubscription`. */
|
|
68
|
+
hasCodexCliLogin?: () => boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function pickCodexAuthMode(io: WizardIO, deps: CodexAuthModeDeps = {}): Promise<CodexAuthMode> {
|
|
72
|
+
const existing = (deps.loadTokens ?? loadTokens)('openai-codex');
|
|
73
|
+
// OB1 (10-01): a user already signed in to the codex CLI was sent through the device-code flow again,
|
|
74
|
+
// because only elanous's own token store counted. The runtime reads ~/.codex/auth.json, so it counts too.
|
|
75
|
+
// Same judgment as the runtime (`codexOAuthAvailable` in llm.ts) — two judgments drifting apart was the bug.
|
|
76
|
+
const codexCli = !existing && (deps.hasCodexCliLogin ?? (() => {
|
|
77
|
+
try {
|
|
78
|
+
const { codexOAuthAvailable } = require('../llm.js') as typeof import('../llm.js');
|
|
79
|
+
return codexOAuthAvailable();
|
|
80
|
+
} catch { return false; }
|
|
81
|
+
}))();
|
|
82
|
+
if (existing || codexCli) {
|
|
83
|
+
if (existing) {
|
|
84
|
+
const exp = existing.tokens.expiresAt;
|
|
85
|
+
const when = exp != null
|
|
86
|
+
? (Date.now() > exp ? 'EXPIRED' : `${Math.round((exp - Date.now()) / 60000)}min left`)
|
|
87
|
+
: 'no expiry tracked';
|
|
88
|
+
io.print(` OAuth tokens already on file (${when}).`);
|
|
89
|
+
} else {
|
|
90
|
+
io.print(' Found your ChatGPT/Codex login from the codex CLI (~/.codex/auth.json).');
|
|
91
|
+
}
|
|
73
92
|
// Sprint 11 — chooseFrom Yes/No so fullScreenIO renders the arrow
|
|
74
93
|
// picker (with 'y'/'n' + 한글 자모 quick-pick from PR #986 + #988)
|
|
75
94
|
// instead of a bare text input. The runner distinguishes 'oauth-keep'
|
package/src/dashboard/index.ts
CHANGED
|
@@ -16779,7 +16779,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
|
|
|
16779
16779
|
preferHistoryArrowKeys: slashQuickMode,
|
|
16780
16780
|
history: inputHistory,
|
|
16781
16781
|
placeholder: anyProviderAvailable()
|
|
16782
|
-
? '
|
|
16782
|
+
? '말 한 줄로 시작 — 질문하거나 «하니스로 구현 …» · /command · /path/to.pdf 첨부'
|
|
16783
16783
|
: '/quit /clear (run `elanous setup` — no LLM provider available)',
|
|
16784
16784
|
debugLog: debug.enabled ? (event, label, payload) => debug.log(event, label, payload) : undefined,
|
|
16785
16785
|
textInputOpts: {
|
package/src/discord.ts
CHANGED
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
import { markdownToTelegramHtml as _unusedMarkdownFn } from './telegram-format.js'; // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
24
24
|
import { debug } from './debug/log.js';
|
|
25
25
|
import { getUserConfig } from './user-config.js';
|
|
26
|
+
import { handleDiscordSeatWork, type DiscordSeatWorkDeps } from './intake-plane/discord-seat-work.js';
|
|
27
|
+
import { parseSeatAddress } from './seat-address/seat-address.js';
|
|
26
28
|
|
|
27
29
|
// Telegram's HTML markdown isn't directly useful for Discord (Discord
|
|
28
30
|
// uses CommonMark + its own subset). Import kept as a reminder that
|
|
@@ -163,6 +165,8 @@ export interface DiscordBotOpts {
|
|
|
163
165
|
* primary `onMessage` LLM chat path. Errors are swallowed by the
|
|
164
166
|
* caller — this is a best-effort fan-out. */
|
|
165
167
|
onTriggerTap?: (event: DcTriggerEvent) => void | Promise<void>;
|
|
168
|
+
/** Override the intake door's dependencies for isolated Discord seat-work tests. */
|
|
169
|
+
seatWorkDeps?: DiscordSeatWorkDeps;
|
|
166
170
|
}
|
|
167
171
|
|
|
168
172
|
/** Surface-unification v2 — minimal trigger-side event shape. Maps
|
|
@@ -204,6 +208,8 @@ export class DiscordBot {
|
|
|
204
208
|
private readonly token: string;
|
|
205
209
|
private readonly allowedUsers: Set<string>;
|
|
206
210
|
private readonly guildTextChannels: Set<string>;
|
|
211
|
+
/** The bot's own user id (from READY) — EV12c lets an allowlisted @mention open any guild channel. */
|
|
212
|
+
private selfUserId: string | undefined;
|
|
207
213
|
private readonly onMessage: DcMessageHandler;
|
|
208
214
|
private readonly maxChars: number;
|
|
209
215
|
private readonly log: (msg: string) => void;
|
|
@@ -216,6 +222,7 @@ export class DiscordBot {
|
|
|
216
222
|
private readonly onReaction: ((event: DcReactionEvent) => void | Promise<void>) | null;
|
|
217
223
|
/** Surface-unification v2 (FU-1) — workflow-runtime tap. */
|
|
218
224
|
private readonly onTriggerTap: ((event: DcTriggerEvent) => void | Promise<void>) | null;
|
|
225
|
+
private readonly seatWorkDeps: DiscordSeatWorkDeps;
|
|
219
226
|
private ws: WebSocket | null = null;
|
|
220
227
|
private running = false;
|
|
221
228
|
private heartbeatTimer: ReturnType<typeof setInterval> | null = null;
|
|
@@ -239,6 +246,7 @@ export class DiscordBot {
|
|
|
239
246
|
this.onInteraction = opts.onInteraction ?? null;
|
|
240
247
|
this.onReaction = opts.onReaction ?? null;
|
|
241
248
|
this.onTriggerTap = opts.onTriggerTap ?? null;
|
|
249
|
+
this.seatWorkDeps = opts.seatWorkDeps ?? {};
|
|
242
250
|
}
|
|
243
251
|
|
|
244
252
|
/** Send a raw payload on the gateway WebSocket. Voice adapter uses
|
|
@@ -604,6 +612,7 @@ export class DiscordBot {
|
|
|
604
612
|
session_id?: string;
|
|
605
613
|
};
|
|
606
614
|
this.log(`READY — logged in as @${d.user?.username ?? '?'} (${d.user?.id ?? '?'})`);
|
|
615
|
+
if (d.user?.id) this.selfUserId = d.user.id;
|
|
607
616
|
if (this.voiceTap?.onReady && d.session_id && d.user?.id) {
|
|
608
617
|
try { this.voiceTap.onReady(d.session_id, d.user.id); }
|
|
609
618
|
catch (err: any) { this.log(`voice tap onReady failed: ${err?.message ?? err}`); }
|
|
@@ -684,6 +693,20 @@ export class DiscordBot {
|
|
|
684
693
|
|
|
685
694
|
// ── Message routing ────────────────────────────────────────
|
|
686
695
|
|
|
696
|
+
/** EV12c — true when this message @mentions the bot itself (mentions array or the raw `<@id>` token). */
|
|
697
|
+
private mentionsSelf(m: Record<string, unknown>, text: string): boolean {
|
|
698
|
+
const self = this.selfUserId;
|
|
699
|
+
if (!self) return false;
|
|
700
|
+
if (Array.isArray(m.mentions) && (m.mentions as Array<{ id?: unknown }>).some(u => String(u?.id) === self)) return true;
|
|
701
|
+
return text.includes(`<@${self}>`) || text.includes(`<@!${self}>`);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
private stripSelfMention(text: string): string {
|
|
705
|
+
const self = this.selfUserId;
|
|
706
|
+
if (!self) return text;
|
|
707
|
+
return text.split(`<@!${self}>`).join('').split(`<@${self}>`).join('').replace(/\s+/g, ' ').trim();
|
|
708
|
+
}
|
|
709
|
+
|
|
687
710
|
private async handleMessageCreate(m: Record<string, unknown>): Promise<void> {
|
|
688
711
|
// Ignore bot's own messages (DM reply from our own send would
|
|
689
712
|
// re-enter here).
|
|
@@ -692,19 +715,25 @@ export class DiscordBot {
|
|
|
692
715
|
|
|
693
716
|
// DM-only v1 scope: guild_id is absent for DM channels.
|
|
694
717
|
// Voice channel commands (`/voice-join`, `/voice-leave`,
|
|
695
|
-
// `/voice-status`)
|
|
696
|
-
//
|
|
697
|
-
//
|
|
718
|
+
// `/voice-status`) and allowlisted, seat-addressed work bypass
|
|
719
|
+
// DM-only. All other guild traffic stays gated unless explicitly
|
|
720
|
+
// configured as a guild text channel.
|
|
698
721
|
const isDm = !m.guild_id;
|
|
722
|
+
const rawText = typeof m.content === 'string' ? m.content : '';
|
|
723
|
+
const mentionsSelf = this.mentionsSelf(m, rawText);
|
|
699
724
|
if (!isDm && !this.guildTextChannels.has(String(m.channel_id))) {
|
|
700
|
-
|
|
701
|
-
|
|
725
|
+
if (!/^\/voice-(join|leave|status)\b/.test(rawText.trim())
|
|
726
|
+
&& !(this.allowedUsers.has(author.id) && (parseSeatAddress(rawText) || mentionsSelf))) {
|
|
727
|
+
debug.log('discord.trigger', 'dropped', { reason: 'guild-not-chat', channelId: String(m.channel_id) });
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
702
730
|
}
|
|
703
731
|
|
|
704
732
|
// Allowlist enforcement. An empty allowlist refuses everyone —
|
|
705
733
|
// safer default than "open to the world" when the bot runs
|
|
706
734
|
// with no user config.
|
|
707
735
|
if (this.allowedUsers.size > 0 && !this.allowedUsers.has(author.id)) {
|
|
736
|
+
debug.log('discord.trigger', 'dropped', { reason: 'not-allowed', isDm });
|
|
708
737
|
this.log(`discord: refusing unknown user ${author.id}`);
|
|
709
738
|
try {
|
|
710
739
|
await this.sendMessage(
|
|
@@ -738,7 +767,7 @@ export class DiscordBot {
|
|
|
738
767
|
channelId: String(m.channel_id),
|
|
739
768
|
userId: author.id,
|
|
740
769
|
userName: author.username,
|
|
741
|
-
text:
|
|
770
|
+
text: mentionsSelf ? this.stripSelfMention(rawText) : rawText,
|
|
742
771
|
messageId: String(m.id),
|
|
743
772
|
isDm,
|
|
744
773
|
attachments,
|
|
@@ -790,6 +819,27 @@ export class DiscordBot {
|
|
|
790
819
|
}
|
|
791
820
|
}
|
|
792
821
|
|
|
822
|
+
// Addressed work is submitted only by configured owners; ordinary chat keeps its existing path.
|
|
823
|
+
if (this.allowedUsers.has(author.id) && parseSeatAddress(ctx.text)) {
|
|
824
|
+
const threadId = typeof m.thread_id === 'string' && m.thread_id ? m.thread_id : undefined;
|
|
825
|
+
const replyChannelId = threadId ?? ctx.channelId;
|
|
826
|
+
try {
|
|
827
|
+
const seatReply = await handleDiscordSeatWork(ctx.text, {
|
|
828
|
+
channelId: replyChannelId,
|
|
829
|
+
messageId: ctx.messageId,
|
|
830
|
+
...(threadId ? { threadId } : {}),
|
|
831
|
+
}, this.seatWorkDeps);
|
|
832
|
+
if (seatReply !== null) {
|
|
833
|
+
await this.sendMessage(replyChannelId, seatReply);
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
} catch (err: unknown) {
|
|
837
|
+
this.log(`discord seat work failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
838
|
+
await this.sendMessage(replyChannelId, '좌석 요청을 접수하지 못했습니다. 잠시 후 다시 보내 주세요.');
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
793
843
|
// §C5 스트리밍 flip — streaming.discord ON 이면 옛 경로(placeholder+streamer+delivery) 억제,
|
|
794
844
|
// 청크 fan-out sink 가 담당(handler chunkProducer 가 primarySurfaces=['discord']). 텔레그램 동형.
|
|
795
845
|
const dcStreamingFlip = getUserConfig().sessionFabric?.streaming?.discord === true;
|