elanous 0.2.6 → 0.2.8
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-3084c58c1725a40b.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/7032-f4ef698518fba61c.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-d976b441a104ab74.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8894-7317998f6cb97d8c.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-8f5ff96c522b5f55.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-9aa47b0bc6d570ff.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f4d2ecbbae6088d6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-2053e49930c82748.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-d4b6df905f185055.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-a70ba27c33003dd0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-186fc6952f027acc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-18166f885d831d3e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-1c9d6cfe68b121c2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-cbbe03c0da7b081a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-c108ff1fdb665c97.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-147a328753d983e2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-799395a9df7d95aa.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-6a8158b71d1fe9d7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-9567560270857531.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-e7ed3d010153d818.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-c0c0d19d51342d3e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-2e002ad8faa43291.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-b672e02e4bec8deb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-1ba4a5d857276334.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-9db2226f38c542fb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-70ee65f5f7a151dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-0bb8e43203efb23b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-092e1bcf27d751cc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-eb52580c4b75b905.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-7464c1987dd76e6d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-e573437b0a1c728f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-294300625e49e4d3.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-085847b088675978.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-9fe9d38c36999eb4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-d8ab4d1489223459.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-6141f67f531d8b6a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-295a02b1026b8255.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-1eb9a0e1e054209d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-82efa177a30f3741.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d0be51cec4f62606.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-2e367075ffdbc062.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-647ed2b66c1d49f1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ba058c56e38f2792.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/5ef72a618a8994f3.css +1 -0
- package/apps/pwa/out/_next/static/lywc8m_HDzH9MKSDQhyEj/_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/graphs/test-diet/recipes.yaml +9 -0
- package/graphs/test-diet/test-diet.yaml +22 -0
- package/package.json +6 -5
- package/persona-presets/_index.yaml +10 -0
- package/persona-presets/_schema.json +146 -0
- package/persona-presets/dana.yaml +26 -0
- package/persona-presets/haru.yaml +27 -0
- package/persona-presets/kai.yaml +27 -0
- package/persona-presets/leo.yaml +27 -0
- package/persona-presets/mira.yaml +30 -0
- package/persona-presets/nari.yaml +27 -0
- package/persona-presets/sora.yaml +27 -0
- package/persona-presets/yuna.yaml +27 -0
- 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 +58 -13
- 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 +57 -0
- package/scripts/field-feed/feed-node.ts +67 -0
- package/scripts/field-feed/lib.ts +164 -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 +109 -21
- 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/persona-presets/lint.ts +79 -0
- package/scripts/public-export.ts +8 -4
- package/scripts/release-loop/baseline-from-logs.ts +92 -0
- package/scripts/release-loop/checklist-gate-node.ts +2 -2
- package/scripts/release-loop/env-known-failures.ts +31 -0
- package/scripts/release-loop/gate-node.ts +108 -22
- package/scripts/release-loop/known-issues-node.ts +159 -0
- package/scripts/release-loop/npm-publish-node.ts +124 -0
- package/scripts/release-loop/ops-upgrade-node.ts +95 -0
- package/scripts/release-loop/publish-node.ts +23 -1
- package/scripts/release-loop/release-readiness.ts +58 -0
- package/scripts/release-loop/shard-plan.ts +17 -0
- package/scripts/release-loop/unattended-release.ts +93 -0
- package/scripts/storyboard/storyboard.ts +167 -0
- package/scripts/test-diet/lib.ts +88 -0
- package/scripts/test-diet/node.ts +102 -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/auth-methods.ts +30 -0
- package/src/acp/elanous-aware-peers.ts +30 -0
- package/src/acp/server.ts +96 -20
- 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/channel-import/detect.ts +191 -0
- package/src/channel-import/run.ts +111 -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/config-test-sync.ts +4 -0
- package/src/cli/decisions-cli.ts +13 -3
- package/src/cli/dependence-gauge.ts +237 -0
- package/src/cli/doctor-bundle.ts +128 -0
- package/src/cli/doctor-cli.ts +41 -1
- package/src/cli/doctor-local-llm.ts +95 -0
- package/src/cli/doctor-modes.ts +48 -0
- package/src/cli/import-cli.ts +60 -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/mcp-login.ts +13 -4
- package/src/cli/msg-cli.ts +137 -0
- package/src/cli/persona-cli.ts +116 -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 +50 -8
- 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/skills-cli.ts +173 -0
- package/src/cli/start-cli.ts +13 -4
- package/src/cli/usage-cli.ts +11 -1
- package/src/codex/setup.ts +37 -16
- package/src/dashboard/index.ts +18 -2
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +2 -2
- package/src/dashboard/slash-runtime/help-from-registry.ts +4 -2
- package/src/decisions/decision-cards.ts +291 -0
- package/src/decisions/decision-ledger.ts +5 -2
- package/src/decisions/discord-decision-cards.ts +133 -0
- package/src/decisions/telegram-decision-cards.ts +100 -0
- package/src/directives/version-at.ts +77 -0
- package/src/discord.ts +73 -6
- package/src/domains/core-tools.ts +37 -14
- package/src/domains/outbound-alert.ts +94 -12
- package/src/domains/repeated-failure.ts +1 -1
- package/src/error-report/report.ts +200 -0
- package/src/exec-requests/planner.ts +146 -0
- package/src/exec-requests/runner.ts +378 -0
- package/src/exec-requests/store.ts +78 -0
- package/src/field/field-feed-auto.ts +48 -0
- package/src/field/field-media.ts +8 -3
- package/src/field/field-reel.ts +203 -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/hooks/error-report.ts +210 -0
- package/src/hooks/receiver.ts +41 -1
- package/src/import/apply.ts +128 -0
- package/src/import/plan.ts +197 -0
- package/src/index.ts +168 -21
- package/src/input-core/text-entry.ts +13 -5
- 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-answer.ts +96 -0
- package/src/intake-plane/seat-doc-route.ts +221 -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 +128 -0
- package/src/nexus/api/discord-trigger-bot.ts +3 -0
- package/src/nexus/api/error-reports.ts +108 -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 +82 -8
- package/src/nexus/api/outbound-report.ts +7 -0
- package/src/nexus/api/running-runs-background.ts +68 -0
- package/src/nexus/api/running-runs-child.ts +6 -0
- package/src/nexus/api/seat-requests.ts +254 -0
- package/src/nexus/api/share-captures.ts +91 -0
- package/src/nexus/api/skill-problems.ts +56 -0
- package/src/nexus/api/terminals.ts +24 -8
- package/src/nexus/api/trace-run-scan.ts +37 -0
- package/src/nexus/api/trace.ts +19 -3
- package/src/nexus/index.ts +28 -1
- package/src/nexus/setup-status.ts +11 -7
- package/src/notify/llm-budget-alert.ts +16 -10
- package/src/oauth/browser-availability.ts +14 -0
- package/src/oauth/codex-account-rotation.ts +26 -4
- package/src/oauth/codex-account-store.ts +20 -2
- package/src/oauth/codex-browser-login.ts +173 -0
- package/src/oauth/codex-quota-policy.ts +16 -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 +34 -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 +90 -0
- package/src/onboarding.ts +287 -26
- package/src/persona/presets.ts +105 -0
- 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 +37 -13
- package/src/self-dev/decompose.ts +1 -0
- package/src/self-dev/dev-pipeline.ts +6 -3
- package/src/self-dev/orchestrate.ts +18 -11
- 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/shell-env-bootstrap.ts +44 -7
- package/src/skills/connect-claude-plugin.ts +142 -0
- package/src/skills/connect.ts +70 -0
- package/src/skills/index.ts +29 -1
- package/src/skills/repair.ts +124 -0
- package/src/skills/runner.ts +11 -2
- package/src/skills/shared-agent-skill-roots.ts +25 -0
- package/src/skills/trigger-extract.ts +1 -1
- 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-command-job.ts +16 -8
- 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-commands.ts +13 -0
- package/src/telegram.ts +72 -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 +189 -15
- 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 → lywc8m_HDzH9MKSDQhyEj}/_ssgManifest.js +0 -0
|
@@ -8,6 +8,7 @@ import { getUserConfig } from '../user-config.js';
|
|
|
8
8
|
import { decideBudget, readBudgetInputsLive } from '../self-implement/budget-gate.js';
|
|
9
9
|
import { findTrack } from './mission-phase-track.js';
|
|
10
10
|
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
11
|
+
import { decideShadowTrackAction, launchTrackAgentShadow, type ShadowOptions } from './track-agent-shadow.js';
|
|
11
12
|
|
|
12
13
|
export type TrackAgentInput = { missionId: string; taskId: string; title: string; prompt: string; track: string };
|
|
13
14
|
export type TrackAgentDecision = { action: 'say' | 'hold'; reason?: string };
|
|
@@ -25,6 +26,8 @@ type RunnerDeps = {
|
|
|
25
26
|
session?: (persona: string) => string;
|
|
26
27
|
root?: string;
|
|
27
28
|
maxConcurrent?: number;
|
|
29
|
+
shadow?: boolean;
|
|
30
|
+
shadowOptions?: ShadowOptions;
|
|
28
31
|
};
|
|
29
32
|
const root = resolve(import.meta.dir, '../..');
|
|
30
33
|
// The installed package is not a git checkout — the daemon's target repository comes first.
|
|
@@ -41,7 +44,8 @@ const runCli = async (args: string[]): Promise<{ stdout: string }> => {
|
|
|
41
44
|
const { argv, env } = universeLaunch(args);
|
|
42
45
|
return exec('bun', argv, { cwd: repoDir(), env, timeout: 120_000, maxBuffer: 1024 * 1024, encoding: 'utf8' });
|
|
43
46
|
};
|
|
44
|
-
const
|
|
47
|
+
const forbiddenActionRegex = /\b(?:delete|remove|deploy|publish|release|restart|reboot|merge|force|pay|purchase|secret|credential|sudo|rm\s+-rf|config\s+set)\b|(?:^|\s)--prod\b|삭제|배포|게시|릴리스|재시작|재부팅|병합|결제|자격|비밀|강제/i;
|
|
48
|
+
export const TRACK_AGENT_FORBIDDEN_ACTION_REGEX = new RegExp(forbiddenActionRegex.source, forbiddenActionRegex.flags);
|
|
45
49
|
let running = 0;
|
|
46
50
|
|
|
47
51
|
function context(input: TrackAgentInput, repo: string): string {
|
|
@@ -70,6 +74,17 @@ function context(input: TrackAgentInput, repo: string): string {
|
|
|
70
74
|
}
|
|
71
75
|
|
|
72
76
|
export async function decideTrackAction(input: TrackAgentInput, deps: RunnerDeps = {}): Promise<TrackAgentDecision> {
|
|
77
|
+
if (!(deps.shadow ?? getUserConfig().autopilot?.trackAgent?.shadow ?? false)) {
|
|
78
|
+
return decideTrackActionOnce(input, deps);
|
|
79
|
+
}
|
|
80
|
+
return decideShadowTrackAction(input, (value) => decideTrackActionOnce(value, deps), {
|
|
81
|
+
...deps.shadowOptions,
|
|
82
|
+
maxDecisionsPerDay: deps.shadowOptions?.maxDecisionsPerDay
|
|
83
|
+
?? getUserConfig().autopilot?.trackAgent?.shadowMaxDecisionsPerDay ?? 20,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function decideTrackActionOnce(input: TrackAgentInput, deps: RunnerDeps): Promise<TrackAgentDecision> {
|
|
73
88
|
try {
|
|
74
89
|
const prompt = context(input, deps.root ?? repoDir());
|
|
75
90
|
const persona = `track-${input.track}`;
|
|
@@ -83,7 +98,7 @@ export async function decideTrackAction(input: TrackAgentInput, deps: RunnerDeps
|
|
|
83
98
|
const values = decision as Record<string, unknown>;
|
|
84
99
|
if (!['say', 'hold'].includes(String(values.action)) || (values.reason !== undefined && typeof values.reason !== 'string')
|
|
85
100
|
|| Object.keys(values).some((key) => !['action', 'reason'].includes(key))) throw new Error('invalid decision');
|
|
86
|
-
if (typeof values.reason === 'string' &&
|
|
101
|
+
if (typeof values.reason === 'string' && forbiddenActionRegex.test(values.reason)) throw new Error('forbidden action');
|
|
87
102
|
return decision as TrackAgentDecision;
|
|
88
103
|
} catch (error) {
|
|
89
104
|
log('decision-failed', { track: input.track, taskId: input.taskId, error: redactSecretText(String(error)) });
|
|
@@ -93,7 +108,7 @@ export async function decideTrackAction(input: TrackAgentInput, deps: RunnerDeps
|
|
|
93
108
|
|
|
94
109
|
export async function executeTrackAction(decision: TrackAgentDecision, input: TrackAgentInput, deps: RunnerDeps = {}): Promise<TrackAgentResult> {
|
|
95
110
|
if (decision.action !== 'say') return { status: 'held', detail: decision.reason ?? '사람 확인' };
|
|
96
|
-
if (
|
|
111
|
+
if (forbiddenActionRegex.test(`${input.title}\n${input.prompt}`)) return { status: 'held', detail: '금지 작업 — 사람 확인' };
|
|
97
112
|
if (!findTrack(input.track)) return { status: 'held', detail: '트랙 미정 — 사람 확인' };
|
|
98
113
|
try {
|
|
99
114
|
const checkBudget = deps.checkBudget ?? (async () => {
|
|
@@ -112,6 +127,11 @@ export async function executeTrackAction(decision: TrackAgentDecision, input: Tr
|
|
|
112
127
|
running++;
|
|
113
128
|
let held = false;
|
|
114
129
|
try {
|
|
130
|
+
if (deps.shadow ?? getUserConfig().autopilot?.trackAgent?.shadow ?? false) {
|
|
131
|
+
const result = await launchTrackAgentShadow(input, deps.shadowOptions);
|
|
132
|
+
log('shadow-orchestrate', { track: input.track, taskId: input.taskId, status: result.status, detail: result.detail });
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
115
135
|
const launch = deps.launch ?? (async (args: string[]) => {
|
|
116
136
|
const { argv, env } = universeLaunch(args);
|
|
117
137
|
const child = spawn('bun', argv, { cwd: repoDir(), env, stdio: 'ignore', detached: true });
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function acpServerHelpText(): string {
|
|
2
|
+
return `
|
|
3
|
+
ACP server (Agent Client Protocol)
|
|
4
|
+
Usage: elanous --acp-server [--transport=stdio|unix-socket|websocket] [options]
|
|
5
|
+
Example: elanous --acp-server --transport=websocket
|
|
6
|
+
|
|
7
|
+
--transport=stdio|unix-socket|websocket Connection transport (default: stdio)
|
|
8
|
+
--socket-path=<path> Unix socket path (unix-socket only)
|
|
9
|
+
--port=<number> Listening port (websocket only)
|
|
10
|
+
--host=<host> Bind host (websocket only)
|
|
11
|
+
--tool-cwd=<path> Default working directory for tools
|
|
12
|
+
--no-auth Disable websocket token authentication
|
|
13
|
+
|
|
14
|
+
Zed setup and connection check: docs/manual/MANUAL-zed-acp.md
|
|
15
|
+
`;
|
|
16
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { classifyShortQuestion } from '../chat/short-question.js';
|
|
2
|
+
import { getUserConfig } from '../user-config.js';
|
|
1
3
|
import { runCoreTurn } from '../core-turn/index.js';
|
|
2
4
|
import type { LLMMessage, LLMToolSpec } from '../llm.js';
|
|
3
5
|
import { maybeImageBearingResult } from '../llm.js';
|
|
@@ -83,6 +85,8 @@ export function summarizeToolResult(result: unknown): string | undefined {
|
|
|
83
85
|
|
|
84
86
|
export async function runDaemonPromptTurn(opts: {
|
|
85
87
|
history: DaemonSessionHistory;
|
|
88
|
+
/** B1 빠른 경로 스위치 — 없으면 config `chat.fastPath`(기본 true). 시험·호출부가 못 박을 때만 준다. */
|
|
89
|
+
fastPathEnabled?: boolean;
|
|
86
90
|
request: DaemonPromptRequest;
|
|
87
91
|
/** Optional multimodal prompt blocks for the current user turn.
|
|
88
92
|
* When present, history persistence keeps the ACP block shape
|
|
@@ -173,6 +177,19 @@ export async function runDaemonPromptTurn(opts: {
|
|
|
173
177
|
reason: surfaceResolutionReason,
|
|
174
178
|
hasUserText: request.userText.length > 0,
|
|
175
179
|
});
|
|
180
|
+
const hasAttachments = (opts.promptBlocks?.length ? opts.promptBlocks : request.userContent)
|
|
181
|
+
?.some((block) => block.type !== 'text') ?? false;
|
|
182
|
+
// SW2 스위치 `chat.fastPath`(기본 켜짐 · PWA «짧은 물음은 빠르게» 카드) — 끄면 분류하지 않는다.
|
|
183
|
+
const fastPathEnabled = opts.fastPathEnabled ?? (() => { try { return getUserConfig().chat?.fastPath !== false; } catch { return true; } })();
|
|
184
|
+
const { fast, reason } = fastPathEnabled
|
|
185
|
+
? classifyShortQuestion(request.userText, { hasAttachments })
|
|
186
|
+
: { fast: false, reason: 'disabled' };
|
|
187
|
+
const activeToolSurface = fast ? undefined : toolSurface;
|
|
188
|
+
const toolsBefore = toolSurface && toolSurface.kind !== 'none' ? toolSurface.specs.length : 0;
|
|
189
|
+
const tools: LLMToolSpec[] = activeToolSurface && activeToolSurface.kind !== 'none'
|
|
190
|
+
? activeToolSurface.specs
|
|
191
|
+
: [];
|
|
192
|
+
debug.log('chat.fast-path', 'decided', { fast, reason, toolsBefore, toolsAfter: tools.length });
|
|
176
193
|
// Image-pipeline followup #4 (2026-05-05) — when the webterm surface
|
|
177
194
|
// is active, prepend a daemon-context block ("Current ACP sessionId:
|
|
178
195
|
// …" + active terminals) to the system prompt so the LLM has the
|
|
@@ -181,21 +198,18 @@ export async function runDaemonPromptTurn(opts: {
|
|
|
181
198
|
const augmentedSystemPrompt = appendWebtermContext(
|
|
182
199
|
request.effectiveSystemPrompt,
|
|
183
200
|
request.sessionId,
|
|
184
|
-
|
|
201
|
+
activeToolSurface,
|
|
185
202
|
);
|
|
186
203
|
const ctrl = new AbortController();
|
|
187
204
|
if (opts.signal) {
|
|
188
205
|
if (opts.signal.aborted) ctrl.abort();
|
|
189
206
|
else opts.signal.addEventListener('abort', () => ctrl.abort(), { once: true });
|
|
190
207
|
}
|
|
191
|
-
const tools: LLMToolSpec[] = toolSurface && toolSurface.kind !== 'none'
|
|
192
|
-
? toolSurface.specs
|
|
193
|
-
: [];
|
|
194
208
|
const toolCwdResolver = createToolCwdResolver({
|
|
195
|
-
tools:
|
|
209
|
+
tools: activeToolSurface?.kind ?? 'none',
|
|
196
210
|
...(opts.toolCwd !== undefined ? { toolCwd: opts.toolCwd } : {}),
|
|
197
211
|
});
|
|
198
|
-
const rawDispatchTool =
|
|
212
|
+
const rawDispatchTool = activeToolSurface && activeToolSurface.kind !== 'none'
|
|
199
213
|
? async (name: string, args: Record<string, unknown>, perCallCtx?: { callId: string }): Promise<unknown> => {
|
|
200
214
|
const dispatchCtrl = new AbortController();
|
|
201
215
|
const onAbort = (): void => dispatchCtrl.abort();
|
|
@@ -204,7 +218,7 @@ export async function runDaemonPromptTurn(opts: {
|
|
|
204
218
|
// Image-pipeline followup #1 (2026-05-05) — pass the current
|
|
205
219
|
// request.sessionId through so WebTerminal* tools can auto-
|
|
206
220
|
// resolve scope when the LLM omits sessionId from args.
|
|
207
|
-
return await
|
|
221
|
+
return await activeToolSurface.dispatch(name, args, {
|
|
208
222
|
cwd: toolCwdResolver.cwd!,
|
|
209
223
|
resolveWriteCwd: toolCwdResolver.resolveWriteCwd,
|
|
210
224
|
signal: dispatchCtrl.signal,
|
|
@@ -246,9 +260,13 @@ export async function runDaemonPromptTurn(opts: {
|
|
|
246
260
|
})
|
|
247
261
|
: null;
|
|
248
262
|
const dispatchTool = terminal ? terminal.dispatch : rawDispatchTool;
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
263
|
+
const fastPathGuidance = '확인이 필요하면 "확인해 볼까요?" 한 줄로 끝낸다';
|
|
264
|
+
const effectiveSystemPrompt = fast
|
|
265
|
+
? [augmentedSystemPrompt, fastPathGuidance].filter(Boolean).join('\n')
|
|
266
|
+
: terminal
|
|
267
|
+
? terminal.systemPromptParts.join('\n\n')
|
|
268
|
+
: augmentedSystemPrompt;
|
|
269
|
+
const isFirstTurn = history.get(request.sessionId).length === 0;
|
|
252
270
|
const messages = opts.promptBlocks && opts.promptBlocks.length > 0
|
|
253
271
|
? appendUserPromptBlocksAndBuildMessages(
|
|
254
272
|
history,
|
|
@@ -262,6 +280,9 @@ export async function runDaemonPromptTurn(opts: {
|
|
|
262
280
|
request.userText,
|
|
263
281
|
effectiveSystemPrompt,
|
|
264
282
|
);
|
|
283
|
+
if (fast && !isFirstTurn) {
|
|
284
|
+
messages.unshift({ role: 'system', content: fastPathGuidance });
|
|
285
|
+
}
|
|
265
286
|
const result = await runCoreTurn({
|
|
266
287
|
sessionId: request.sessionId,
|
|
267
288
|
userText: request.userText,
|
|
@@ -389,7 +389,13 @@ export function toolSurface(kind: DaemonToolSurfaceKind, cfg?: import('../../use
|
|
|
389
389
|
}
|
|
390
390
|
}
|
|
391
391
|
if (shared.names.has(name)) {
|
|
392
|
-
|
|
392
|
+
// L2 core + L3 finance(gated) — buildSharedAppTools 단일 dispatch · B5: 진행 한 줄(emitFeedback)·세션·호출 id 를 넘긴다.
|
|
393
|
+
return shared.dispatch(name, args, {
|
|
394
|
+
surface: 'tui',
|
|
395
|
+
...(ctx.sessionId ? { sessionId: ctx.sessionId } : {}),
|
|
396
|
+
...(ctx.toolCallId ? { toolCallId: ctx.toolCallId } : {}),
|
|
397
|
+
...(ctx.emitFeedback ? { emitFeedback: ctx.emitFeedback } : {}),
|
|
398
|
+
});
|
|
393
399
|
}
|
|
394
400
|
if (name === 'Bash') {
|
|
395
401
|
// PLAN-ios-rich-dev-feedback-hydrate M3 (2026-05-13) — pass
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { codexCredentialRoot } from './codex-reset-credit-state.js';
|
|
4
|
+
import type { LlmPolicy } from '../policy/llm-policy.js';
|
|
5
|
+
|
|
6
|
+
interface CreditDay {
|
|
7
|
+
day: string;
|
|
8
|
+
accounts: Record<string, { dayOpen: number; balance: number }>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type CreditBalances = Readonly<Record<string, number>>;
|
|
12
|
+
|
|
13
|
+
function kstDay(now: Date): string {
|
|
14
|
+
return new Date(now.getTime() + 9 * 3_600_000).toISOString().slice(0, 10);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function dayNumber(day: string): number {
|
|
18
|
+
return Date.parse(`${day}T00:00:00Z`) / 86_400_000;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function creditDayPath(stateDir?: string): string {
|
|
22
|
+
return join(stateDir ?? codexCredentialRoot(), 'budget', 'codex-credit-day.json');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readCreditDay(stateDir?: string): CreditDay | undefined {
|
|
26
|
+
try {
|
|
27
|
+
const parsed = JSON.parse(readFileSync(creditDayPath(stateDir), 'utf8')) as CreditDay;
|
|
28
|
+
return typeof parsed.day === 'string' && parsed.accounts && typeof parsed.accounts === 'object' && !Array.isArray(parsed.accounts)
|
|
29
|
+
? parsed : undefined;
|
|
30
|
+
} catch { return undefined; }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Only a poller's observed balance can move the opening balance; readers never modify the ledger. */
|
|
34
|
+
export function recordCreditBalances(balances: CreditBalances, now: Date = new Date(), stateDir?: string): void {
|
|
35
|
+
const day = kstDay(now);
|
|
36
|
+
const previous = readCreditDay(stateDir);
|
|
37
|
+
const accounts: CreditDay['accounts'] = previous?.day === day ? { ...previous.accounts } : {};
|
|
38
|
+
for (const [name, balance] of Object.entries(balances)) {
|
|
39
|
+
if (!Number.isFinite(balance) || balance < 0) continue;
|
|
40
|
+
const prior = Object.prototype.hasOwnProperty.call(accounts, name) ? accounts[name] : undefined;
|
|
41
|
+
const dayOpen = prior && Number.isFinite(prior.dayOpen) && Number.isFinite(prior.balance)
|
|
42
|
+
? (balance > prior.balance ? balance : prior.dayOpen)
|
|
43
|
+
: balance;
|
|
44
|
+
Object.defineProperty(accounts, name, { value: { dayOpen, balance }, enumerable: true, configurable: true, writable: true });
|
|
45
|
+
}
|
|
46
|
+
const path = creditDayPath(stateDir);
|
|
47
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
48
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
49
|
+
writeFileSync(temporary, `${JSON.stringify({ day, accounts }, null, 2)}\n`);
|
|
50
|
+
renameSync(temporary, path);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function creditPaceStatus({ policy, balances, now = new Date(), stateDir }: {
|
|
54
|
+
policy: LlmPolicy;
|
|
55
|
+
balances: CreditBalances;
|
|
56
|
+
now?: Date;
|
|
57
|
+
stateDir?: string;
|
|
58
|
+
}): { active: boolean; todaySpent: number; target: number; remaining: number; daysLeft: number; projectedAtUntil: number } {
|
|
59
|
+
const day = kstDay(now);
|
|
60
|
+
const ledger = readCreditDay(stateDir);
|
|
61
|
+
const observed = Object.entries(balances).filter(([, balance]) => Number.isFinite(balance) && balance >= 0);
|
|
62
|
+
const todaySpent = ledger?.day === day ? observed.reduce((sum, [name, balance]) => {
|
|
63
|
+
const open = Object.prototype.hasOwnProperty.call(ledger.accounts, name) ? ledger.accounts[name]?.dayOpen : undefined;
|
|
64
|
+
return sum + (typeof open === 'number' && Number.isFinite(open) ? Math.max(0, open - balance) : 0);
|
|
65
|
+
}, 0) : 0;
|
|
66
|
+
const remaining = observed.reduce((sum, [, balance]) => sum + balance, 0);
|
|
67
|
+
const until = policy.credits.pace?.until;
|
|
68
|
+
const expiry = until ? Date.parse(`${until}T15:00:00Z`) : NaN;
|
|
69
|
+
const daysLeft = until ? Math.max(1, dayNumber(until) - dayNumber(day) + 1) : 1;
|
|
70
|
+
// The poller's opening observation anchors today's allocation. Spending cannot lower it;
|
|
71
|
+
// a new grant or the next KST day changes the opening observation.
|
|
72
|
+
const startingBalance = ledger?.day === day
|
|
73
|
+
? observed.reduce((sum, [name, balance]) => {
|
|
74
|
+
const entry = Object.prototype.hasOwnProperty.call(ledger.accounts, name) ? ledger.accounts[name] : undefined;
|
|
75
|
+
return sum + (entry && Number.isFinite(entry.dayOpen) ? entry.dayOpen : balance);
|
|
76
|
+
}, 0)
|
|
77
|
+
: remaining;
|
|
78
|
+
const target = until ? Math.ceil(startingBalance / daysLeft) : 0;
|
|
79
|
+
return {
|
|
80
|
+
active: policy.credits.codex === 'use' && !!until && now.getTime() < expiry && remaining > 0 && todaySpent < target,
|
|
81
|
+
todaySpent, target, remaining, daysLeft,
|
|
82
|
+
projectedAtUntil: Math.max(0, remaining - Math.max(0, target - todaySpent) - target * (daysLeft - 1)),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// Codex prepaid-credit plan: days left until the credits expire, the daily spend that would use the balance up,
|
|
2
|
+
// and what was actually spent over the last 7 days. Disk only — the balance history is appended by
|
|
3
|
+
// writeQuotaSignal whenever a measured balance changes; nothing here touches the network.
|
|
4
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
const DAY_MS = 86_400_000;
|
|
8
|
+
const HISTORY_KEEP_MS = 30 * DAY_MS;
|
|
9
|
+
const HISTORY_FILE = 'codex-credit-history.jsonl';
|
|
10
|
+
|
|
11
|
+
export interface CreditHistoryEntry { at: string; home: string; balance: number }
|
|
12
|
+
|
|
13
|
+
export function creditHistoryPath(signalDir: string): string {
|
|
14
|
+
return join(signalDir, HISTORY_FILE);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function readCreditHistory(signalDir: string): CreditHistoryEntry[] {
|
|
18
|
+
try {
|
|
19
|
+
const path = creditHistoryPath(signalDir);
|
|
20
|
+
if (!existsSync(path)) return [];
|
|
21
|
+
const out: CreditHistoryEntry[] = [];
|
|
22
|
+
for (const line of readFileSync(path, 'utf8').split('\n')) {
|
|
23
|
+
if (!line.trim()) continue;
|
|
24
|
+
try {
|
|
25
|
+
const e = JSON.parse(line) as Partial<CreditHistoryEntry>;
|
|
26
|
+
if (typeof e.at === 'string' && typeof e.home === 'string' && typeof e.balance === 'number' && Number.isFinite(e.balance)) out.push(e as CreditHistoryEntry);
|
|
27
|
+
} catch { /* a torn line is skipped */ }
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
} catch { return []; }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Appends only when this home's balance changed; prunes entries older than 30 days. Fail-soft. */
|
|
34
|
+
export function appendCreditHistory(signalDir: string, home: string, balance: number, now: number = Date.now()): void {
|
|
35
|
+
try {
|
|
36
|
+
if (!Number.isFinite(balance)) return;
|
|
37
|
+
const history = readCreditHistory(signalDir);
|
|
38
|
+
const last = [...history].reverse().find((e) => e.home === home);
|
|
39
|
+
if (last && last.balance === balance) return;
|
|
40
|
+
mkdirSync(signalDir, { recursive: true });
|
|
41
|
+
const entry: CreditHistoryEntry = { at: new Date(now).toISOString(), home, balance };
|
|
42
|
+
const kept = history.filter((e) => now - Date.parse(e.at) <= HISTORY_KEEP_MS);
|
|
43
|
+
if (kept.length !== history.length) {
|
|
44
|
+
writeFileSync(creditHistoryPath(signalDir), [...kept, entry].map((e) => JSON.stringify(e)).join('\n') + '\n', 'utf8');
|
|
45
|
+
} else {
|
|
46
|
+
appendFileSync(creditHistoryPath(signalDir), `${JSON.stringify(entry)}\n`, 'utf8');
|
|
47
|
+
}
|
|
48
|
+
} catch { /* observation never blocks the fetch */ }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Credits spent in the window: sum of balance drops per home (rises are top-ups and are not spend). */
|
|
52
|
+
export function creditSpend(history: readonly CreditHistoryEntry[], now: number, windowMs = 7 * DAY_MS): { spent: number; spanDays: number } | null {
|
|
53
|
+
const since = now - windowMs;
|
|
54
|
+
let spent = 0;
|
|
55
|
+
let earliest = Infinity;
|
|
56
|
+
const byHome = new Map<string, CreditHistoryEntry[]>();
|
|
57
|
+
for (const e of history) {
|
|
58
|
+
const at = Date.parse(e.at);
|
|
59
|
+
if (!Number.isFinite(at) || at > now) continue;
|
|
60
|
+
byHome.set(e.home, [...(byHome.get(e.home) ?? []), e]);
|
|
61
|
+
}
|
|
62
|
+
for (const entries of byHome.values()) {
|
|
63
|
+
entries.sort((a, b) => Date.parse(a.at) - Date.parse(b.at));
|
|
64
|
+
// The last entry before the window is the baseline for the first change inside it.
|
|
65
|
+
let start = entries.findIndex((e) => Date.parse(e.at) >= since);
|
|
66
|
+
if (start === -1) continue;
|
|
67
|
+
if (start > 0) start -= 1;
|
|
68
|
+
const used = entries.slice(start);
|
|
69
|
+
if (used.length < 2) continue;
|
|
70
|
+
earliest = Math.min(earliest, Math.max(since, Date.parse(used[0]!.at)));
|
|
71
|
+
for (let i = 1; i < used.length; i++) {
|
|
72
|
+
const drop = used[i - 1]!.balance - used[i]!.balance;
|
|
73
|
+
if (drop > 0) spent += drop;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (!Number.isFinite(earliest)) return null;
|
|
77
|
+
return { spent, spanDays: Math.max((now - earliest) / DAY_MS, 1 / 24) };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface CodexCreditPlan {
|
|
81
|
+
/** policy credits.pace.until, else the earliest grant expiry; null when the policy records neither. */
|
|
82
|
+
expiresAt: string | null;
|
|
83
|
+
daysLeft: number | null;
|
|
84
|
+
/** Sum of the known per-account balances; null when no balance is known. */
|
|
85
|
+
totalBalance: number | null;
|
|
86
|
+
unknownBalances: number;
|
|
87
|
+
accounts: Array<{ name: string; balance: number | null; expires: string | null }>;
|
|
88
|
+
/** Credits per day that would use the balance up by the expiry date. */
|
|
89
|
+
dailyNeeded: number | null;
|
|
90
|
+
/** policy credits.pace.targetPerDay — the declared pace, shown beside the computed one. */
|
|
91
|
+
targetPerDay: number | null;
|
|
92
|
+
/** Credits actually spent per day over the last 7 days (or the shorter recorded span). */
|
|
93
|
+
actualPerDay: number | null;
|
|
94
|
+
actualSpanDays: number | null;
|
|
95
|
+
/** policy credits.pace.why */
|
|
96
|
+
note: string | null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface CreditPolicyView {
|
|
100
|
+
codex: string;
|
|
101
|
+
grants: ReadonlyArray<{ account: string; amount: number; expires: string }>;
|
|
102
|
+
pace?: { targetPerDay: number; until: string; why?: string };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
106
|
+
|
|
107
|
+
export function codexCreditPlan(input: {
|
|
108
|
+
now: number;
|
|
109
|
+
balances: ReadonlyArray<{ name: string; balance?: number }>;
|
|
110
|
+
credits: CreditPolicyView;
|
|
111
|
+
history: readonly CreditHistoryEntry[];
|
|
112
|
+
}): CodexCreditPlan {
|
|
113
|
+
const { credits } = input;
|
|
114
|
+
const accounts = input.balances.map((a) => {
|
|
115
|
+
const grantDates = credits.grants.filter((g) => g.account === a.name && DATE.test(g.expires)).map((g) => g.expires).sort();
|
|
116
|
+
return { name: a.name, balance: typeof a.balance === 'number' && Number.isFinite(a.balance) ? a.balance : null, expires: grantDates[0] ?? null };
|
|
117
|
+
});
|
|
118
|
+
const known = accounts.map((a) => a.balance).filter((b): b is number => b !== null);
|
|
119
|
+
const totalBalance = known.length ? known.reduce((x, y) => x + y, 0) : null;
|
|
120
|
+
const earliestGrant = credits.grants.map((g) => g.expires).filter((d) => DATE.test(d)).sort()[0];
|
|
121
|
+
const expiresAt = credits.pace?.until && DATE.test(credits.pace.until) ? credits.pace.until : earliestGrant ?? null;
|
|
122
|
+
const expiry = expiresAt ? Date.parse(`${expiresAt}T23:59:59+09:00`) : NaN;
|
|
123
|
+
const daysLeft = Number.isFinite(expiry) ? Math.max(0, Math.ceil((expiry - input.now) / DAY_MS)) : null;
|
|
124
|
+
const spend = creditSpend(input.history, input.now);
|
|
125
|
+
return {
|
|
126
|
+
expiresAt,
|
|
127
|
+
daysLeft,
|
|
128
|
+
totalBalance,
|
|
129
|
+
unknownBalances: accounts.length - known.length,
|
|
130
|
+
accounts,
|
|
131
|
+
dailyNeeded: daysLeft && totalBalance !== null ? totalBalance / daysLeft : null,
|
|
132
|
+
targetPerDay: credits.pace?.targetPerDay ?? null,
|
|
133
|
+
actualPerDay: spend ? spend.spent / Math.min(7, spend.spanDays) : null,
|
|
134
|
+
actualSpanDays: spend ? Math.round(Math.min(7, spend.spanDays) * 10) / 10 : null,
|
|
135
|
+
note: credits.pace?.why?.trim() || null,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** The lines both surfaces print under the policy line. */
|
|
140
|
+
export function formatCodexCreditPlan(plan: CodexCreditPlan): string[] {
|
|
141
|
+
const n = (v: number) => String(Math.round(v));
|
|
142
|
+
const unknown = plan.unknownBalances ? ` (+${plan.unknownBalances}개 계정 모름)` : '';
|
|
143
|
+
const perAccount = plan.accounts.map((a) => `${a.name} ${a.balance === null ? '?' : n(a.balance)}${a.expires ? `(~${a.expires})` : ''}`).join(' · ');
|
|
144
|
+
const lines = [
|
|
145
|
+
`크레딧 잔액 합 ${plan.totalBalance === null ? '?' : n(plan.totalBalance)}${unknown} · 만료 ${plan.expiresAt ?? '모름 (policy credits.pace.until · credits.grants)'}${perAccount ? ` · ${perAccount}` : ''}`,
|
|
146
|
+
];
|
|
147
|
+
const days = plan.daysLeft === null ? '만료일 모름' : `만료까지 ${plan.daysLeft}일`;
|
|
148
|
+
const needed = plan.dailyNeeded === null ? '?' : n(plan.dailyNeeded);
|
|
149
|
+
const target = plan.targetPerDay === null ? '' : ` (목표 ${n(plan.targetPerDay)})`;
|
|
150
|
+
const actual = plan.actualPerDay === null ? '기록 없음' : `${n(plan.actualPerDay)}/일${plan.actualSpanDays !== null && plan.actualSpanDays < 7 ? ` (기록 ${plan.actualSpanDays}일)` : ''}`;
|
|
151
|
+
lines.push(`계산 ${days} · 하루 소진 필요 ${needed}${target} · 최근 7일 실제 ${actual}`);
|
|
152
|
+
if (plan.note) lines.push(`메모 ${plan.note}`);
|
|
153
|
+
return lines;
|
|
154
|
+
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// ⇒ 직접 부르는 쪽은 «자기가» 감싸야 한다. 여기서 삼키면 저장 실패가 영영 안 보인다.
|
|
10
10
|
// ⭐ 반면 `writeQuotaSignal` 은 «판정 신호»라 조회를 막으면 안 되므로 자기가 삼킨다(아래 주석).
|
|
11
11
|
|
|
12
|
+
import { appendCreditHistory } from './codex-credit-plan.js';
|
|
12
13
|
import { mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
|
|
13
14
|
import { createHash } from 'node:crypto';
|
|
14
15
|
import { homedir } from 'node:os';
|
|
@@ -233,6 +234,7 @@ export function writeQuotaSignal(
|
|
|
233
234
|
...(typeof credits?.hasCredits === 'boolean' ? { hasCredits: credits.hasCredits } : {}),
|
|
234
235
|
};
|
|
235
236
|
writeFileSync(path, `${JSON.stringify(next, null, 2)}\n`, 'utf8');
|
|
237
|
+
if (next.creditsBalance !== undefined && next.measuredHome) appendCreditHistory(dirname(path), next.measuredHome, next.creditsBalance);
|
|
236
238
|
} catch { /* fail-soft */ }
|
|
237
239
|
}
|
|
238
240
|
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// EN13 — 셋업이 «이미 쓰던» 텔레그램·디스코드 설정을 찾아 온다(대표 10-01).
|
|
2
|
+
// 출처: OpenClaw(~/.openclaw/openclaw.json · tokenFile) · Hermes(~/.hermes/.env) · 이전 엘라누스(~/.monad/config.json).
|
|
3
|
+
// ⛔ 토큰 값은 이 모듈 밖으로 «문자열»로 나가지 않는다 — `BotToken` 이 감싸고, 문자열화·JSON·inspect 는 전부 가린다.
|
|
4
|
+
// 값이 필요한 곳(저장·연결 확인)만 `reveal()` 을 부른다. 미리보기는 «있음 · 끝 4자리 가림»만 낸다.
|
|
5
|
+
import { createHash } from 'node:crypto';
|
|
6
|
+
import { existsSync, lstatSync, readFileSync } from 'node:fs';
|
|
7
|
+
import { homedir } from 'node:os';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
|
|
10
|
+
export type ImportPlatform = 'telegram' | 'discord';
|
|
11
|
+
export type ImportSource = 'openclaw' | 'hermes' | 'elanous-legacy';
|
|
12
|
+
|
|
13
|
+
const MASK = '있음 · 끝 4자리 가림';
|
|
14
|
+
|
|
15
|
+
/** A bot token that never prints itself. */
|
|
16
|
+
export class BotToken {
|
|
17
|
+
readonly #value: string;
|
|
18
|
+
constructor(value: string) { this.#value = value; }
|
|
19
|
+
reveal(): string { return this.#value; }
|
|
20
|
+
/** Same-token check without exposing either value. */
|
|
21
|
+
fingerprint(): string { return createHash('sha256').update(this.#value).digest('hex').slice(0, 8); }
|
|
22
|
+
toString(): string { return MASK; }
|
|
23
|
+
toJSON(): string { return MASK; }
|
|
24
|
+
[Symbol.for('nodejs.util.inspect.custom')](): string { return MASK; }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ImportCandidate {
|
|
28
|
+
platform: ImportPlatform;
|
|
29
|
+
source: ImportSource;
|
|
30
|
+
/** `~`-relative file the value came from. */
|
|
31
|
+
file: string;
|
|
32
|
+
token: BotToken;
|
|
33
|
+
allowedUsers: string[];
|
|
34
|
+
/** Allowlist entries we could not use (not a numeric Telegram id, etc.). */
|
|
35
|
+
droppedUsers: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const SOURCE_LABEL: Record<ImportSource, string> = {
|
|
39
|
+
openclaw: 'OpenClaw', hermes: 'Hermes', 'elanous-legacy': '이전 엘라누스(~/.monad)',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function tilde(path: string, home: string): string { return path.startsWith(home) ? `~${path.slice(home.length)}` : path; }
|
|
43
|
+
function obj(value: unknown): Record<string, unknown> | null {
|
|
44
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : null;
|
|
45
|
+
}
|
|
46
|
+
function text(value: unknown): string | null { return typeof value === 'string' && value.trim() ? value.trim() : null; }
|
|
47
|
+
|
|
48
|
+
/** JSON with comments and trailing commas (openclaw.json is JSON5-ish). Strings are kept intact. */
|
|
49
|
+
export function parseLooseJson(raw: string): unknown {
|
|
50
|
+
try { return JSON.parse(raw); } catch { /* fall through */ }
|
|
51
|
+
let out = '';
|
|
52
|
+
let inString = false;
|
|
53
|
+
for (let i = 0; i < raw.length; i++) {
|
|
54
|
+
const ch = raw[i]!;
|
|
55
|
+
if (inString) {
|
|
56
|
+
out += ch;
|
|
57
|
+
if (ch === '\\') { out += raw[++i] ?? ''; continue; }
|
|
58
|
+
if (ch === '"') inString = false;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (ch === '"') { inString = true; out += ch; continue; }
|
|
62
|
+
if (ch === '/' && raw[i + 1] === '/') { while (i < raw.length && raw[i] !== '\n') i++; out += '\n'; continue; }
|
|
63
|
+
if (ch === '/' && raw[i + 1] === '*') { i += 2; while (i < raw.length && !(raw[i] === '*' && raw[i + 1] === '/')) i++; i++; continue; }
|
|
64
|
+
out += ch;
|
|
65
|
+
}
|
|
66
|
+
return JSON.parse(out.replace(/,(\s*[}\]])/g, '$1'));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** `KEY=value` lines (quotes and `export ` allowed, `#` comments ignored). */
|
|
70
|
+
export function parseDotEnv(raw: string): Record<string, string> {
|
|
71
|
+
const out: Record<string, string> = {};
|
|
72
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
73
|
+
const m = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\s*$/.exec(line);
|
|
74
|
+
if (!m) continue;
|
|
75
|
+
let value = m[2]!;
|
|
76
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) value = value.slice(1, -1);
|
|
77
|
+
else value = value.replace(/\s+#.*$/, '');
|
|
78
|
+
out[m[1]!] = value.trim();
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Telegram allowlists must be numeric user ids; `telegram:123` / `tg:123` prefixes are accepted. */
|
|
84
|
+
export function normalizeUsers(platform: ImportPlatform, raw: unknown): { users: string[]; dropped: number } {
|
|
85
|
+
const list = Array.isArray(raw) ? raw : typeof raw === 'string' ? raw.split(',') : [];
|
|
86
|
+
const users: string[] = [];
|
|
87
|
+
let dropped = 0;
|
|
88
|
+
for (const entry of list) {
|
|
89
|
+
const value = String(entry).trim().replace(/^(?:telegram|tg|discord|user):/i, '');
|
|
90
|
+
if (!value || value === '*') { if (value === '*') dropped++; continue; }
|
|
91
|
+
const ok = platform === 'telegram' ? /^\d+$/.test(value) && Number.isSafeInteger(Number(value)) && Number(value) > 0 : /^\d{5,}$/.test(value);
|
|
92
|
+
if (ok && !users.includes(value)) users.push(value); else if (!ok) dropped++;
|
|
93
|
+
}
|
|
94
|
+
return { users, dropped };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function readRegularFile(path: string): string | null {
|
|
98
|
+
try { return lstatSync(path).isFile() ? readFileSync(path, 'utf8').trim() || null : null; } catch { return null; }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function candidate(platform: ImportPlatform, source: ImportSource, file: string, token: string | null, users: unknown): ImportCandidate | null {
|
|
102
|
+
if (!token) return null;
|
|
103
|
+
const { users: allowedUsers, dropped } = normalizeUsers(platform, users);
|
|
104
|
+
return { platform, source, file, token: new BotToken(token), allowedUsers, droppedUsers: dropped };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function fromOpenClaw(home: string): ImportCandidate[] {
|
|
108
|
+
const path = join(home, '.openclaw', 'openclaw.json');
|
|
109
|
+
if (!existsSync(path)) return [];
|
|
110
|
+
const cfg = obj(parseLooseJson(readFileSync(path, 'utf8')));
|
|
111
|
+
const channels = obj(cfg?.channels);
|
|
112
|
+
const file = tilde(path, home);
|
|
113
|
+
const out: ImportCandidate[] = [];
|
|
114
|
+
const telegram = obj(channels?.telegram);
|
|
115
|
+
if (telegram) {
|
|
116
|
+
const firstAccount = obj(Object.values(obj(telegram.accounts) ?? {})[0]);
|
|
117
|
+
const token = text(telegram.botToken) ?? (text(telegram.tokenFile) ? readRegularFile(text(telegram.tokenFile)!.replace(/^~(?=\/)/, home)) : null)
|
|
118
|
+
?? text(firstAccount?.botToken);
|
|
119
|
+
const c = candidate('telegram', 'openclaw', file, token, telegram.allowFrom ?? firstAccount?.allowFrom);
|
|
120
|
+
if (c) out.push(c);
|
|
121
|
+
}
|
|
122
|
+
const discord = obj(channels?.discord);
|
|
123
|
+
if (discord) {
|
|
124
|
+
const firstAccount = obj(Object.values(obj(discord.accounts) ?? {})[0]);
|
|
125
|
+
const c = candidate('discord', 'openclaw', file, text(discord.token) ?? text(firstAccount?.token), discord.allowFrom ?? obj(discord.dm)?.allowFrom);
|
|
126
|
+
if (c) out.push(c);
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function fromHermes(home: string): ImportCandidate[] {
|
|
132
|
+
const path = join(home, '.hermes', '.env');
|
|
133
|
+
if (!existsSync(path)) return [];
|
|
134
|
+
const env = parseDotEnv(readFileSync(path, 'utf8'));
|
|
135
|
+
const file = tilde(path, home);
|
|
136
|
+
return [
|
|
137
|
+
candidate('telegram', 'hermes', file, text(env.TELEGRAM_BOT_TOKEN), env.TELEGRAM_ALLOWED_USERS),
|
|
138
|
+
candidate('discord', 'hermes', file, text(env.DISCORD_BOT_TOKEN), env.DISCORD_ALLOWED_USERS),
|
|
139
|
+
].filter((c): c is ImportCandidate => c !== null);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function fromElanousLegacy(home: string): ImportCandidate[] {
|
|
143
|
+
const path = join(home, '.monad', 'config.json');
|
|
144
|
+
if (!existsSync(path)) return [];
|
|
145
|
+
const cfg = obj(parseLooseJson(readFileSync(path, 'utf8')));
|
|
146
|
+
const file = tilde(path, home);
|
|
147
|
+
return (['telegram', 'discord'] as const)
|
|
148
|
+
.map((platform) => candidate(platform, 'elanous-legacy', file, text(obj(cfg?.[platform])?.botToken), obj(cfg?.[platform])?.allowedUsers))
|
|
149
|
+
.filter((c): c is ImportCandidate => c !== null);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface DetectResult { candidates: ImportCandidate[]; unreadable: { file: string; reason: string }[] }
|
|
153
|
+
|
|
154
|
+
/** Look in every known place; a broken file is reported by name (never by content) and the rest still count. */
|
|
155
|
+
export function detectChannelImports(home = homedir()): DetectResult {
|
|
156
|
+
const candidates: ImportCandidate[] = [];
|
|
157
|
+
const unreadable: DetectResult['unreadable'] = [];
|
|
158
|
+
const readers: [string, (h: string) => ImportCandidate[]][] = [
|
|
159
|
+
[join(home, '.monad', 'config.json'), fromElanousLegacy],
|
|
160
|
+
[join(home, '.openclaw', 'openclaw.json'), fromOpenClaw],
|
|
161
|
+
[join(home, '.hermes', '.env'), fromHermes],
|
|
162
|
+
];
|
|
163
|
+
for (const [path, read] of readers) {
|
|
164
|
+
try { candidates.push(...read(home)); }
|
|
165
|
+
catch (error) {
|
|
166
|
+
// JSON parse errors can quote the offending text — keep only the error class.
|
|
167
|
+
unreadable.push({ file: tilde(path, home), reason: error instanceof SyntaxError ? '형식을 읽지 못했습니다' : '읽지 못했습니다' });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return { candidates, unreadable };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** One per platform: the first source in priority order; the rest are named as «다른 토큰도 있음». */
|
|
174
|
+
export function pickPerPlatform(candidates: readonly ImportCandidate[]): { chosen: ImportCandidate[]; others: ImportCandidate[] } {
|
|
175
|
+
const chosen: ImportCandidate[] = [];
|
|
176
|
+
const others: ImportCandidate[] = [];
|
|
177
|
+
for (const c of candidates) {
|
|
178
|
+
const first = chosen.find((x) => x.platform === c.platform);
|
|
179
|
+
if (!first) chosen.push(c);
|
|
180
|
+
else if (first.token.fingerprint() !== c.token.fingerprint()) others.push(c);
|
|
181
|
+
else for (const user of c.allowedUsers) if (!first.allowedUsers.includes(user)) first.allowedUsers.push(user);
|
|
182
|
+
}
|
|
183
|
+
return { chosen, others };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function previewLine(c: ImportCandidate): string {
|
|
187
|
+
const name = c.platform === 'telegram' ? '텔레그램' : '디스코드';
|
|
188
|
+
const users = c.allowedUsers.length ? `허용 사용자 ${c.allowedUsers.length}명` : '허용 사용자 없음(나중에 정해야 합니다)';
|
|
189
|
+
const dropped = c.droppedUsers ? ` · 쓸 수 없는 항목 ${c.droppedUsers}개 제외` : '';
|
|
190
|
+
return `${name} ← ${SOURCE_LABEL[c.source]} (${c.file}): 토큰 ${MASK} · ${users}${dropped}`;
|
|
191
|
+
}
|