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
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// EN13 — 감지 → 미리보기 → 동의 → 시크릿 저장 → 연결 확인(읽기 1회). 토큰 값은 화면·로그·채널 어디에도 안 나간다.
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { debug } from '../debug/log.js';
|
|
4
|
+
import { detectChannelImports, pickPerPlatform, previewLine, SOURCE_LABEL, type ImportCandidate, type ImportPlatform } from './detect.js';
|
|
5
|
+
|
|
6
|
+
export interface ChannelImportDeps {
|
|
7
|
+
home?: string;
|
|
8
|
+
out?: { log: (s: string) => void; error: (s: string) => void };
|
|
9
|
+
/** Asks once; returns true for yes. Absent or non-interactive → no. */
|
|
10
|
+
ask?: (question: string) => Promise<boolean>;
|
|
11
|
+
yes?: boolean;
|
|
12
|
+
/** Current token for that platform (to say «already the same»), or null. */
|
|
13
|
+
currentToken?: (platform: ImportPlatform) => string | null | Promise<string | null>;
|
|
14
|
+
store?: (platform: ImportPlatform, token: string, allowedUsers: string[] | undefined) => Promise<void>;
|
|
15
|
+
probe?: (platform: ImportPlatform, token: string) => Promise<{ ok: boolean; botName?: string }>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ChannelImportOutcome {
|
|
19
|
+
exitCode: number;
|
|
20
|
+
imported: { platform: ImportPlatform; source: string; connected: boolean; botName?: string }[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const NAME: Record<ImportPlatform, string> = { telegram: '텔레그램', discord: '디스코드' };
|
|
24
|
+
|
|
25
|
+
async function defaultStore(platform: ImportPlatform, token: string, allowedUsers: string[] | undefined): Promise<void> {
|
|
26
|
+
const { storeChannelBotToken } = await import('../channel-bot-token.js');
|
|
27
|
+
await storeChannelBotToken(platform, token, allowedUsers);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function defaultProbe(platform: ImportPlatform, token: string): Promise<{ ok: boolean; botName?: string }> {
|
|
31
|
+
const { probeChannelBotToken } = await import('../channel-bot-token.js');
|
|
32
|
+
return probeChannelBotToken(platform, token);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function defaultCurrentToken(platform: ImportPlatform): Promise<string | null> {
|
|
36
|
+
try {
|
|
37
|
+
const { resolveChannelBotToken } = await import('../channel-bot-token.js');
|
|
38
|
+
const { getUserConfig } = await import('../user-config.js');
|
|
39
|
+
return resolveChannelBotToken(platform, getUserConfig())?.token ?? null;
|
|
40
|
+
} catch { return null; }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function runChannelImport(deps: ChannelImportDeps = {}): Promise<ChannelImportOutcome> {
|
|
44
|
+
const out = deps.out ?? console;
|
|
45
|
+
const home = deps.home ?? homedir();
|
|
46
|
+
const { candidates, unreadable } = detectChannelImports(home);
|
|
47
|
+
for (const u of unreadable) out.log(`· ${u.file}: ${u.reason} — 건너뜁니다`);
|
|
48
|
+
const { chosen, others } = pickPerPlatform(candidates);
|
|
49
|
+
debug.log('channel-import', 'detected', {
|
|
50
|
+
sources: candidates.map((c) => `${c.source}:${c.platform}`), chosen: chosen.length, others: others.length, unreadable: unreadable.length,
|
|
51
|
+
});
|
|
52
|
+
if (!chosen.length) {
|
|
53
|
+
out.log('가져올 텔레그램·디스코드 설정을 찾지 못했습니다(OpenClaw · Hermes · 이전 엘라누스). 직접 넣으려면: elanous nexus channel-bot setup telegram');
|
|
54
|
+
return { exitCode: 0, imported: [] };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const current = deps.currentToken ?? defaultCurrentToken;
|
|
58
|
+
const todo: ImportCandidate[] = [];
|
|
59
|
+
out.log('이미 쓰던 채널 설정을 찾았습니다:');
|
|
60
|
+
for (const c of chosen) {
|
|
61
|
+
const existing = await current(c.platform);
|
|
62
|
+
if (existing && existing === c.token.reveal()) { out.log(`· ${previewLine(c)} — 이미 같은 토큰을 쓰고 있습니다`); continue; }
|
|
63
|
+
out.log(`· ${previewLine(c)}${existing ? ' — ⚠️ 지금 쓰는 토큰을 바꿉니다' : ''}`);
|
|
64
|
+
todo.push(c);
|
|
65
|
+
}
|
|
66
|
+
for (const c of others) out.log(` (${NAME[c.platform]} 토큰이 ${SOURCE_LABEL[c.source]}에도 있습니다 — 다른 토큰이라 위의 것만 가져옵니다)`);
|
|
67
|
+
if (!todo.length) return { exitCode: 0, imported: [] };
|
|
68
|
+
|
|
69
|
+
const question = `가져올까요? 토큰은 엘라누스 비밀 저장소에만 넣고 화면·로그에는 남기지 않습니다. [Y/n] `;
|
|
70
|
+
const agreed = deps.yes || (deps.ask ? await deps.ask(question) : false);
|
|
71
|
+
if (!agreed) {
|
|
72
|
+
out.log(deps.ask || deps.yes ? '가져오지 않았습니다.' : '가져오려면: elanous nexus channel-bot import --yes');
|
|
73
|
+
debug.log('channel-import', 'declined', { count: todo.length });
|
|
74
|
+
return { exitCode: 0, imported: [] };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const store = deps.store ?? defaultStore;
|
|
78
|
+
const probe = deps.probe ?? defaultProbe;
|
|
79
|
+
const imported: ChannelImportOutcome['imported'] = [];
|
|
80
|
+
let failed = 0;
|
|
81
|
+
for (const c of todo) {
|
|
82
|
+
try {
|
|
83
|
+
await store(c.platform, c.token.reveal(), c.allowedUsers.length ? c.allowedUsers : undefined);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
failed++;
|
|
86
|
+
// Store errors are our own (lock, invalid ids) — print the class only, never the message, in case it carries input.
|
|
87
|
+
const reason = error instanceof Error && error.message === 'invalid-allowed-users' ? '허용 사용자 형식이 맞지 않습니다' : '저장하지 못했습니다';
|
|
88
|
+
out.error(`✗ ${NAME[c.platform]}: ${reason}`);
|
|
89
|
+
debug.log('channel-import', 'store-failed', { platform: c.platform, source: c.source }, { level: 'warn' });
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const check = await probe(c.platform, c.token.reveal()).catch(() => ({ ok: false } as { ok: boolean; botName?: string }));
|
|
93
|
+
imported.push({ platform: c.platform, source: c.source, connected: check.ok, ...(check.botName ? { botName: check.botName } : {}) });
|
|
94
|
+
debug.log('channel-import', 'stored', { platform: c.platform, source: c.source, connected: check.ok });
|
|
95
|
+
out.log(check.ok
|
|
96
|
+
? `✓ ${NAME[c.platform]}: 저장했고 연결을 확인했습니다 — @${check.botName}`
|
|
97
|
+
: `△ ${NAME[c.platform]}: 저장했지만 연결 확인에 실패했습니다 — 토큰이 만료됐거나 네트워크 문제일 수 있습니다(다시 넣기: elanous nexus channel-bot setup ${c.platform})`);
|
|
98
|
+
}
|
|
99
|
+
if (imported.length) out.log('봇은 다음 넥서스 시작 때 켜집니다.');
|
|
100
|
+
return { exitCode: failed ? 1 : 0, imported };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** One line for onboarding — null when there is nothing to import. Never reveals a value. */
|
|
104
|
+
export function channelImportHint(home = homedir()): string | null {
|
|
105
|
+
try {
|
|
106
|
+
const { chosen } = pickPerPlatform(detectChannelImports(home).candidates);
|
|
107
|
+
if (!chosen.length) return null;
|
|
108
|
+
const what = chosen.map((c) => `${NAME[c.platform]}(${SOURCE_LABEL[c.source]})`).join(' · ');
|
|
109
|
+
return `쓰던 채널 설정 찾음: ${what} — 가져오려면: elanous nexus channel-bot import`;
|
|
110
|
+
} catch { return null; }
|
|
111
|
+
}
|
package/src/chat/chat-cli.ts
CHANGED
|
@@ -49,5 +49,8 @@ export function toDevChatOpts(opts: ChatCliOpts): DevChatOpts {
|
|
|
49
49
|
export async function runChatCliCommand(text: string, opts: ChatCliOpts, deps: ChatCliDeps): Promise<void> {
|
|
50
50
|
const spec = buildChatDevSpec(text, toDevChatOpts(opts));
|
|
51
51
|
const run = deps.runDevPipeline ?? runDevPipeline;
|
|
52
|
-
await run(spec, {
|
|
52
|
+
await run(spec, {
|
|
53
|
+
runChatTurn: deps.runChatTurn,
|
|
54
|
+
...(opts.json ? { progress: (message: string) => { process.stderr.write(`${message}\n`); } } : {}),
|
|
55
|
+
});
|
|
53
56
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// B1(UX · 0.2.7) — 짧은 «물음»은 도구 없이 빠른 경로로(첫 글자 ≤2초 · 내부 문서 `EVAL-bot-chat-vs-grok-2026-09-30`).
|
|
2
|
+
// 허용 방식(골 ASK-bot-short-question-fast-path-b1b): 아래가 «모두» 참일 때만 fast — 하나라도 거짓이면 fast 아님(보수 쪽).
|
|
3
|
+
// ⓐ 한 줄 · 80자 이하 · 첨부 없음 ⓑ 인사이거나 «물음 꼴» ⓒ 요청·작업 표지 · 경로·URL·코드 꼴 · 실시간 낱말이 없다.
|
|
4
|
+
// 거부 목록으로 변형을 다 막을 수는 없다 — 오분류 비용은 «확인해 볼까요?» 한 줄로 막힌다(도구가 안 실릴 뿐 대화는 이어진다).
|
|
5
|
+
// 판정 표본 = src/chat/short-question.test.ts 의 고정 표 20개(⊕ 리뷰가 든 변형).
|
|
6
|
+
|
|
7
|
+
export interface ShortQuestionContext {
|
|
8
|
+
hasAttachments?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface ShortQuestionVerdict {
|
|
11
|
+
fast: boolean;
|
|
12
|
+
reason: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const GREETING = /^(?:안녕(?:하세요|하십니까)?|반가워(?:요)?|고마워(?:요)?|감사합니다|하이|hi|hello|hey|thanks|thank you)[!?.~\s]*$/i;
|
|
16
|
+
// «한 줄로» 같은 짧은 꼬리를 떼고 본다.
|
|
17
|
+
const TAIL = /\s*(?:한\s*줄로|짧게|간단히|briefly|in one line)[.!]?\s*$/i;
|
|
18
|
+
const KO_QUESTION_END = /(?:\?|까|니|나요|냐|야|어때|뭐|몇|언제|어디|누구|왜|인가|일까)[?.!~]*$/;
|
|
19
|
+
const EN_QUESTION = /^(?:what|why|how|who|where|when|which|is|are|does|do)\b.*\?$/i;
|
|
20
|
+
const REQUEST_KO = /(?:해\s*줘|해\s*주세요|줘(?:요)?(?=[?.!\s]|$)|주세요|줄래|할래|볼래|줄\s*수\s*있|돌려|실행|고쳐|만들어\s*줘|찾아|검색|확인해|열어|보내|올려|지워|써\s*줘|작성해|요약해|정리해|분석해)/;
|
|
21
|
+
const REQUEST_EN = /\b(?:please|can you|could you|would you|run|test|tests|fix|build|open|search|check|send|create|write|deploy|install|summarize)\b/i;
|
|
22
|
+
const TASK = /(?:작업|파일|폴더|디렉터리|레포|저장소|커밋|브랜치|\bPR\b|코드|스크립트|테스트|빌드|배포|설치|설정|명령어|로그|버그|이슈)/i;
|
|
23
|
+
const PATH_OR_CODE = /(?:https?:\/\/|www\.|[\w.-]+\/[\w./-]+|\.(?:ts|tsx|js|py|md|json|yaml|swift|kt)\b|[`{};=<>])/i;
|
|
24
|
+
const LIVE = /(?:오늘\s*(?:날짜|며칠|무슨\s*요일)|지금\s*몇\s*시|최신|뉴스|속보|주가|시세|환율|날씨|today'?s|latest|news|stock|weather|right now)/i;
|
|
25
|
+
|
|
26
|
+
export function classifyShortQuestion(text: string, ctx: ShortQuestionContext = {}): ShortQuestionVerdict {
|
|
27
|
+
const q = text.trim();
|
|
28
|
+
if (!q) return { fast: false, reason: 'empty' };
|
|
29
|
+
if (ctx.hasAttachments) return { fast: false, reason: 'attachment' };
|
|
30
|
+
if (/[\r\n\u2028\u2029]/.test(q)) return { fast: false, reason: 'multiline' };
|
|
31
|
+
if ([...q].length > 80) return { fast: false, reason: 'long' };
|
|
32
|
+
if (PATH_OR_CODE.test(q)) return { fast: false, reason: 'path-or-code' };
|
|
33
|
+
if (LIVE.test(q)) return { fast: false, reason: 'live' };
|
|
34
|
+
if (REQUEST_KO.test(q) || REQUEST_EN.test(q)) return { fast: false, reason: 'request' };
|
|
35
|
+
if (TASK.test(q)) return { fast: false, reason: 'task' };
|
|
36
|
+
if (GREETING.test(q)) return { fast: true, reason: 'greeting' };
|
|
37
|
+
const core = q.replace(TAIL, '').trim();
|
|
38
|
+
if (KO_QUESTION_END.test(core) || EN_QUESTION.test(core)) return { fast: true, reason: 'question' };
|
|
39
|
+
return { fast: false, reason: 'not-a-question' };
|
|
40
|
+
}
|
package/src/cli/agent-cli.ts
CHANGED
|
@@ -393,8 +393,8 @@ tierCmd
|
|
|
393
393
|
* When `json` is true, the function suppresses the streaming text +
|
|
394
394
|
* ui.info trailer and instead emits exactly ONE JSON line on stdout
|
|
395
395
|
* at end-of-turn:
|
|
396
|
-
* {sessionId, provider, model, reply,
|
|
397
|
-
*
|
|
396
|
+
* {sessionId, provider, model, reply, finalReply, transcript, logPath,
|
|
397
|
+
* budget, durationMs, ts}
|
|
398
398
|
* Stable shape so LLMs can self-spawn `elanous chat` for follow-ups
|
|
399
399
|
* without parsing human-readable terminal output. */
|
|
400
400
|
export async function runChatTurnCli(opts: {
|
|
@@ -442,12 +442,14 @@ export async function runChatTurnCli(opts: {
|
|
|
442
442
|
dbg.setAmbientSessionId?.(session.id);
|
|
443
443
|
} catch { /* debug module unavailable — fine */ }
|
|
444
444
|
const replyChunks: string[] = [];
|
|
445
|
-
//
|
|
445
|
+
// Tool calls delimit assistant messages. finalReply retains its last non-empty-message meaning.
|
|
446
446
|
let segmentChunks: string[] = [];
|
|
447
447
|
let lastSegment = '';
|
|
448
|
+
let finalMessage = '';
|
|
449
|
+
let sawToolCall = false;
|
|
448
450
|
const closeSegment = (): void => {
|
|
449
|
-
|
|
450
|
-
if (
|
|
451
|
+
finalMessage = segmentChunks.join('').trim();
|
|
452
|
+
if (finalMessage) lastSegment = finalMessage;
|
|
451
453
|
segmentChunks = [];
|
|
452
454
|
};
|
|
453
455
|
if (!opts.json) {
|
|
@@ -528,15 +530,17 @@ export async function runChatTurnCli(opts: {
|
|
|
528
530
|
userText: opts.userText,
|
|
529
531
|
systemPrompt: agentSystemPrompt,
|
|
530
532
|
onDelta: (d) => {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
+
replyChunks.push(d);
|
|
534
|
+
segmentChunks.push(d);
|
|
535
|
+
if (!opts.json && !opts.enableTools) process.stdout.write(d);
|
|
533
536
|
},
|
|
534
537
|
tools: cliToolSpecs,
|
|
535
538
|
dispatchTool,
|
|
536
539
|
...(opts.goalLoop ? { goalLoop: true } : {}),
|
|
537
540
|
onToolCall: (call) => {
|
|
538
|
-
//
|
|
539
|
-
|
|
541
|
+
// The next delta belongs to the next assistant message.
|
|
542
|
+
sawToolCall = true;
|
|
543
|
+
closeSegment();
|
|
540
544
|
if (!opts.json) {
|
|
541
545
|
process.stdout.write(`\n ⏺ ${call.name}(${truncateArgsForLog(call.args)})\n`);
|
|
542
546
|
}
|
|
@@ -555,6 +559,12 @@ export async function runChatTurnCli(opts: {
|
|
|
555
559
|
}
|
|
556
560
|
throw err;
|
|
557
561
|
}
|
|
562
|
+
if (!sawToolCall && replyChunks.length === 0 && result.text) {
|
|
563
|
+
replyChunks.push(result.text);
|
|
564
|
+
segmentChunks.push(result.text);
|
|
565
|
+
if (!opts.json && !opts.enableTools) process.stdout.write(result.text);
|
|
566
|
+
}
|
|
567
|
+
closeSegment();
|
|
558
568
|
if (opts.json) {
|
|
559
569
|
let logPath: string | null = null;
|
|
560
570
|
try {
|
|
@@ -566,11 +576,9 @@ export async function runChatTurnCli(opts: {
|
|
|
566
576
|
sessionId: session.id,
|
|
567
577
|
provider: result.provider,
|
|
568
578
|
model: result.model ?? null,
|
|
569
|
-
reply:
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
// `finalReply` = 마지막 어시스턴트 메시지 본문 — 「최종 답만」이 필요한 소비자용(뒤호환 추가 칸).
|
|
573
|
-
finalReply: (closeSegment(), lastSegment),
|
|
579
|
+
reply: finalMessage,
|
|
580
|
+
transcript: replyChunks.join(''),
|
|
581
|
+
finalReply: lastSegment,
|
|
574
582
|
budget: sessionBudget(session.id),
|
|
575
583
|
durationMs: Date.now() - startedAt,
|
|
576
584
|
logPath,
|
|
@@ -579,7 +587,8 @@ export async function runChatTurnCli(opts: {
|
|
|
579
587
|
await writeStdoutJson(JSON.stringify(out) + '\n');
|
|
580
588
|
return;
|
|
581
589
|
}
|
|
582
|
-
process.stdout.write(
|
|
590
|
+
if (opts.enableTools) process.stdout.write(`${finalMessage}\n`);
|
|
591
|
+
else process.stdout.write('\n');
|
|
583
592
|
ui.info(`[session ${session.id.slice(0, 8)} ${result.provider}${result.model ? '/' + result.model : ''} ${sessionBudget(session.id)}]`);
|
|
584
593
|
}
|
|
585
594
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { decideProviderForConfig, getProviderForConfig, streamLLM, type LLMMessage } from '../llm.js';
|
|
2
|
+
import { getUserConfig } from '../user-config.js';
|
|
3
|
+
import { DEFAULT_TOKEN_BUDGET, trimToBudget } from '../tokens.js';
|
|
4
|
+
|
|
5
|
+
export interface AskBareDeps {
|
|
6
|
+
getUserConfig: typeof getUserConfig;
|
|
7
|
+
decideProviderForConfig: typeof decideProviderForConfig;
|
|
8
|
+
getProviderForConfig: typeof getProviderForConfig;
|
|
9
|
+
streamLLM: typeof streamLLM;
|
|
10
|
+
trimToBudget: typeof trimToBudget;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function askBare({ text, provider, model, deps }: {
|
|
14
|
+
text: string;
|
|
15
|
+
provider?: string;
|
|
16
|
+
model?: string;
|
|
17
|
+
deps: AskBareDeps;
|
|
18
|
+
}): Promise<{ provider: string; model: string; reply: string }> {
|
|
19
|
+
const messages: LLMMessage[] = [{ role: 'user', content: text }];
|
|
20
|
+
if (deps.trimToBudget(messages, DEFAULT_TOKEN_BUDGET).used > DEFAULT_TOKEN_BUDGET) {
|
|
21
|
+
throw new Error(`ask --bare: input exceeds the ${DEFAULT_TOKEN_BUDGET}-token context budget`);
|
|
22
|
+
}
|
|
23
|
+
const cfg = deps.getUserConfig();
|
|
24
|
+
const selectedConfig = provider === undefined ? cfg : {
|
|
25
|
+
...cfg,
|
|
26
|
+
llm: {
|
|
27
|
+
...cfg.llm,
|
|
28
|
+
provider: provider as typeof cfg.llm.provider,
|
|
29
|
+
model: model ?? (provider === cfg.llm.provider ? cfg.llm.model : undefined),
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
const selected = deps.decideProviderForConfig(selectedConfig, model);
|
|
33
|
+
const chosenProvider = deps.getProviderForConfig(selectedConfig, model);
|
|
34
|
+
let resolvedProvider = chosenProvider.name;
|
|
35
|
+
let resolvedModel = selected.model;
|
|
36
|
+
const reply = await deps.streamLLM(messages, () => {}, {
|
|
37
|
+
...(provider === undefined ? { initialProvider: chosenProvider } : { provider: chosenProvider }),
|
|
38
|
+
...(selected.model === '(none)' ? {} : { model: selected.model }),
|
|
39
|
+
onResolvedProvider: (name, activeModel) => { resolvedProvider = name; resolvedModel = activeModel; },
|
|
40
|
+
});
|
|
41
|
+
return { provider: resolvedProvider, model: resolvedModel, reply };
|
|
42
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { HarnessCliInputError } from '../harness/harness-cli-command.js';
|
|
2
|
+
|
|
3
|
+
const cliUserErrorBrand = Symbol.for('elanous.cli.CliUserError');
|
|
4
|
+
const harnessCliInputErrorBrand = Symbol.for('elanous.cli.HarnessCliInputError');
|
|
5
|
+
|
|
6
|
+
export class CliUserError extends Error {
|
|
7
|
+
constructor(message: string, public readonly hint?: string) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = 'CliUserError';
|
|
10
|
+
Object.defineProperty(this, cliUserErrorBrand, { value: true });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isCliUserError(err: unknown): err is Error & { hint?: string } {
|
|
15
|
+
return err instanceof HarnessCliInputError || err instanceof CliUserError || (
|
|
16
|
+
err instanceof Error && (
|
|
17
|
+
(err.name === 'CliUserError' && Object.getOwnPropertyDescriptor(err, cliUserErrorBrand)?.value === true) ||
|
|
18
|
+
(err.name === 'HarnessCliInputError' && Object.getOwnPropertyDescriptor(err, harnessCliInputErrorBrand)?.value === true)
|
|
19
|
+
)
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function formatCliUserError(err: Error & { hint?: string }): string {
|
|
24
|
+
return `❌ ${err.message}${err.hint ? `\n ↳ ${err.hint}` : ''}`;
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Assembles the codex credit plan from disk for `provider codex status` and `usage` — policy file, the last
|
|
2
|
+
// measured per-account balances and the balance history. No network.
|
|
3
|
+
import { codexCreditPlan, readCreditHistory, type CodexCreditPlan } from '../budget/codex-credit-plan.js';
|
|
4
|
+
import { quotaSignalDir } from '../budget/codex-reset-credit-state.js';
|
|
5
|
+
import { inspectCodexRotation } from '../oauth/codex-account-store.js';
|
|
6
|
+
import { loadLlmPolicy } from '../policy/llm-policy.js';
|
|
7
|
+
|
|
8
|
+
export function codexCreditPlanFromDisk(now: number = Date.now()): CodexCreditPlan {
|
|
9
|
+
const s = inspectCodexRotation(process.env, { now });
|
|
10
|
+
const seen = new Set<string>();
|
|
11
|
+
const balances: Array<{ name: string; balance?: number }> = [];
|
|
12
|
+
const push = (key: string, name: string, balance: number | undefined) => {
|
|
13
|
+
if (seen.has(key)) return;
|
|
14
|
+
seen.add(key);
|
|
15
|
+
balances.push({ name, ...(balance === undefined ? {} : { balance }) });
|
|
16
|
+
};
|
|
17
|
+
push(s.current.storeKey, s.current.name, s.currentCreditBalance);
|
|
18
|
+
for (const c of s.candidates) push(c.storeKey, c.name, c.creditBalance);
|
|
19
|
+
return codexCreditPlan({
|
|
20
|
+
now,
|
|
21
|
+
balances,
|
|
22
|
+
credits: loadLlmPolicy({ now: new Date(now) }).policy.credits,
|
|
23
|
+
history: readCreditHistory(quotaSignalDir()),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -41,6 +41,9 @@ export const TEST_SYNC_AUX_FILES: readonly string[] = [
|
|
|
41
41
|
'llm-fallback.json', // 모델 폴백 체인
|
|
42
42
|
'auth.json', // oauth 자격 (LLM provider)
|
|
43
43
|
'identity.json', // self identity — 무해·자기인식 일관
|
|
44
|
+
// POL1 (10-01): the LLM policy (credit use · pace · grants). Without it a tree's test universe resolved «fallback»
|
|
45
|
+
// while production said «credits», and every harness run from a work tree went to grok.
|
|
46
|
+
'policy/llm.yaml',
|
|
44
47
|
];
|
|
45
48
|
|
|
46
49
|
/** 의도적 제외 (복사 금지 — 이유 명시. 목록 변경 시 반드시 사유와 함께). */
|
|
@@ -201,6 +204,7 @@ export function syncTestConfig(testDir: string, srcDir: string = prodConfigDir()
|
|
|
201
204
|
// 목록 뒤의 `auth.json`(codex 로그인)이 복사되지 않아 격리 우주의 자식이 codex 를 못 썼다.
|
|
202
205
|
// ⇒ 이미 같은 바이트면 건너뛴다(멱등) — 다르면 종전처럼 복사한다(실패는 그대로 드러난다).
|
|
203
206
|
if (existsSync(to) && readFileSync(from).equals(readFileSync(to))) { chmodSync(to, 0o600); skippedIdentical.push(f); continue; }
|
|
207
|
+
mkdirSync(dirname(to), { recursive: true });
|
|
204
208
|
copyFileSync(from, to);
|
|
205
209
|
chmodSync(to, 0o600);
|
|
206
210
|
copied.push(f);
|
package/src/cli/decisions-cli.ts
CHANGED
|
@@ -9,6 +9,12 @@ function date(raw?: string): string | undefined {
|
|
|
9
9
|
if (!Number.isFinite(parsed.getTime())) throw new Error(`invalid since: ${raw}`);
|
|
10
10
|
return parsed.toISOString();
|
|
11
11
|
}
|
|
12
|
+
/** `+6h` → now + 6 hours; otherwise a UTC ISO timestamp (validated by the ledger). */
|
|
13
|
+
function dueAt(raw: string): string {
|
|
14
|
+
const hours = /^\+(\d+(?:\.\d+)?)h$/.exec(raw.trim());
|
|
15
|
+
if (hours) return new Date(Date.now() + Number(hours[1]) * 3600000).toISOString();
|
|
16
|
+
return raw.trim();
|
|
17
|
+
}
|
|
12
18
|
const kst = (at: string) => new Intl.DateTimeFormat('sv-SE', { timeZone: 'Asia/Seoul', dateStyle: 'short', timeStyle: 'medium' }).format(new Date(at)) + ' KST';
|
|
13
19
|
const version = (e: DecisionEntry['version']) => e ? `released=${e.released ?? '-'} · dev=${e.dev ?? '-'}${e.codename ? ` «${e.codename}»` : ''}` : '판 미상';
|
|
14
20
|
function agent(o?: string): string {
|
|
@@ -30,6 +36,7 @@ export function formatDecisionDetail(e: DecisionEntry): string {
|
|
|
30
36
|
'선택지 | 결과', ...(e.options.length ? e.options.map(o => `${o.key}: ${o.label} | ${o.consequence}`) : ['원문에 선택지 없음 · 확인 필요']),
|
|
31
37
|
'skipped' in e.recommendation ? `권고 생략: ${e.recommendation.reason}` : `권고: ${e.recommendation.option} — ${e.recommendation.why}`,
|
|
32
38
|
`올림: ${e.raisedAt ? kst(e.raisedAt) : '시각 미상'} · ${e.raisedBy.agent} · ${version(e.version)}`,
|
|
39
|
+
...(e.dueAt ? [`기한: ${kst(e.dueAt)}`] : []),
|
|
33
40
|
...(e.importedAt ? [`가져옴: ${kst(e.importedAt)}`] : []),
|
|
34
41
|
...(e.status === 'decided' ? [`결정: ${e.decidedAt ? kst(e.decidedAt) : '시각 미상'} · ${e.decidedBy?.kind === 'auto' ? `AUTO ${e.decidedBy.agent} (${e.decidedBy.delegation})` : e.decidedBy?.kind === 'human' ? 'human' : '주체 미상'} · ${e.choice ?? '선택 키 미상'} · ${e.versionAtDecision ? version(e.versionAtDecision) : '결정 당시 판 미상'}`,
|
|
35
42
|
...(e.note ? [`메모: ${e.note}`] : [])] : []),
|
|
@@ -48,14 +55,17 @@ export function registerDecisionsCommands(program: Command, config: DecisionLedg
|
|
|
48
55
|
.requiredOption('--option <key=label:consequence>', '선택지 (두 번 이상)', repeat, [] as string[])
|
|
49
56
|
.option('--recommend <key>').option('--why <text>').option('--skip-recommend <reason>')
|
|
50
57
|
.option('--track <track>').option('--agent <agent>').option('--session <id>')
|
|
51
|
-
.option('--ref <url>', '참조 (반복)', repeat, [] as string[])
|
|
52
|
-
.
|
|
58
|
+
.option('--ref <url>', '참조 (반복)', repeat, [] as string[])
|
|
59
|
+
.option('--due <when>', '기한 — UTC ISO(2026-10-04T09:00:00Z) 또는 +Nh(지금부터 N시간) · 기한 2시간 전 텔레그램·디스코드로 다시 알린다')
|
|
60
|
+
.option('--json')
|
|
61
|
+
.action((o: { due?: string; title: string; category: DecisionCategory; s: string; c: string; q?: string; a?: string; option: string[]; recommend?: string; why?: string; skipRecommend?: string; track?: DecisionTrack; agent?: string; session?: string; ref: string[]; json?: boolean }) => fail(() => {
|
|
53
62
|
if (o.skipRecommend !== undefined && (o.recommend !== undefined || o.why !== undefined)) throw new Error('choose recommendation or skip, not both');
|
|
54
63
|
if (o.skipRecommend === undefined && (!o.recommend || !o.why)) throw new Error('--recommend and --why required, or --skip-recommend <reason>');
|
|
55
64
|
const options: DecisionOption[] = o.option.map(parseOption);
|
|
56
65
|
const entry = new DecisionLedger(config).raise({ title: o.title, category: o.category, scqa: { s: o.s, c: o.c, ...(o.q ? { q: o.q } : {}), ...(o.a ? { a: o.a } : {}) }, options,
|
|
57
66
|
recommendation: o.skipRecommend !== undefined ? { skipped: true, reason: o.skipRecommend } : { option: o.recommend!, why: o.why! },
|
|
58
|
-
raisedBy: { agent: agent(o.agent), ...(o.track ? { track: o.track } : {}), ...(o.session ? { session: o.session } : {}) }, ...(o.ref.length ? { refs: o.ref } : {})
|
|
67
|
+
raisedBy: { agent: agent(o.agent), ...(o.track ? { track: o.track } : {}), ...(o.session ? { session: o.session } : {}) }, ...(o.ref.length ? { refs: o.ref } : {}),
|
|
68
|
+
...(o.due ? { dueAt: dueAt(o.due) } : {}) });
|
|
59
69
|
emit(entry, o.json, `올림: ${formatDecisionRow(entry)}`);
|
|
60
70
|
}));
|
|
61
71
|
root.command('list').description('결정 목록 (기본 열린 것)')
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import { elanousStateRoot } from '../autopilot/state-paths.js';
|
|
5
|
+
import type { Command } from 'commander';
|
|
6
|
+
|
|
7
|
+
/** Attribution is evidence about the executor, not about the person who requested the work. */
|
|
8
|
+
export type DependenceAttribution = 'claude-code' | 'elanous' | 'other' | 'unknown';
|
|
9
|
+
export type DependenceAxis = 'positions' | 'executions' | 'changes' | 'decisions';
|
|
10
|
+
export interface DependenceObservation {
|
|
11
|
+
at?: string;
|
|
12
|
+
runtime?: string;
|
|
13
|
+
agent?: string;
|
|
14
|
+
track?: string;
|
|
15
|
+
/** A direct attribution supplied by a source record; never inferred from the author. */
|
|
16
|
+
actor?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface DependenceSource {
|
|
19
|
+
observations: readonly DependenceObservation[];
|
|
20
|
+
/** A missing source is different from an observed empty source. */
|
|
21
|
+
unmeasurable?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface DependenceAxisResult {
|
|
24
|
+
status: 'measured' | 'unmeasurable';
|
|
25
|
+
total: number;
|
|
26
|
+
covered: number;
|
|
27
|
+
claudeCode: number;
|
|
28
|
+
elanous: number;
|
|
29
|
+
other: number;
|
|
30
|
+
unknown: number;
|
|
31
|
+
/** Records whose timestamp cannot place them inside or outside the requested window. Not in total. */
|
|
32
|
+
undated: number;
|
|
33
|
+
reason?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface DependenceReport {
|
|
36
|
+
since: string;
|
|
37
|
+
until: string;
|
|
38
|
+
axes: Record<DependenceAxis, DependenceAxisResult>;
|
|
39
|
+
}
|
|
40
|
+
export interface DependenceGaugeDeps {
|
|
41
|
+
now?: () => Date;
|
|
42
|
+
readPositions?: () => DependenceSource;
|
|
43
|
+
readExecutions?: () => DependenceSource;
|
|
44
|
+
readChanges?: (window: { since: string; until: string }) => DependenceSource;
|
|
45
|
+
readDecisions?: () => DependenceSource;
|
|
46
|
+
out?: Pick<Console, 'log'>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const axes = ['positions', 'executions', 'changes', 'decisions'] as const;
|
|
50
|
+
const identity = (value: unknown): DependenceAttribution => {
|
|
51
|
+
if (typeof value !== 'string' || !value.trim()) return 'unknown';
|
|
52
|
+
const normalized = value.trim().toLowerCase();
|
|
53
|
+
if (normalized === 'claude-code' || normalized === 'claude code') return 'claude-code';
|
|
54
|
+
if (normalized === 'elanous') return 'elanous';
|
|
55
|
+
if (normalized === 'unknown') return 'unknown';
|
|
56
|
+
return 'other';
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** Explicit per-event runtime wins over an agent label, which wins over the seat default. */
|
|
60
|
+
export function attributeDependence(row: DependenceObservation, positions: ReadonlyMap<string, DependenceAttribution>): DependenceAttribution {
|
|
61
|
+
if (row.actor?.trim()) return identity(row.actor);
|
|
62
|
+
if (row.runtime?.trim()) return identity(row.runtime);
|
|
63
|
+
if (row.agent?.trim()) return identity(row.agent);
|
|
64
|
+
return row.track ? (positions.get(row.track) ?? 'unknown') : 'unknown';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function instant(raw: string, label: string): Date {
|
|
68
|
+
// Date.parse silently normalizes impossible days (e.g. February 30), including timestamps.
|
|
69
|
+
const calendarDate = /^(\d{4}-\d{2}-\d{2})(?:T|$)/.exec(raw)?.[1];
|
|
70
|
+
if (calendarDate) {
|
|
71
|
+
const day = new Date(`${calendarDate}T00:00:00.000Z`);
|
|
72
|
+
if (!Number.isFinite(day.getTime()) || day.toISOString().slice(0, 10) !== calendarDate) {
|
|
73
|
+
throw new Error(`invalid ${label}: ${raw}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const date = new Date(calendarDate === raw ? `${raw}T00:00:00.000Z` : raw);
|
|
77
|
+
if (!Number.isFinite(date.getTime())) throw new Error(`invalid ${label}: ${raw}`);
|
|
78
|
+
return date;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function observationTime(raw: string | undefined): number | undefined {
|
|
82
|
+
if (!raw) return undefined;
|
|
83
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.exec(raw);
|
|
84
|
+
if (!match) return undefined;
|
|
85
|
+
const day = new Date(Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3])));
|
|
86
|
+
if (day.toISOString().slice(0, 10) !== raw.slice(0, 10)) return undefined;
|
|
87
|
+
const time = Date.parse(raw);
|
|
88
|
+
return Number.isFinite(time) ? time : undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function dependenceWindow(since: string | undefined, until: string | undefined, now: Date): { since: string; until: string } {
|
|
92
|
+
const end = until ? instant(until, '--until') : now;
|
|
93
|
+
if (!Number.isFinite(end.getTime())) throw new Error('invalid current date');
|
|
94
|
+
// Date-only --until includes that entire UTC day; relative lookback is anchored to its end.
|
|
95
|
+
const inclusiveEnd = until && /^\d{4}-\d{2}-\d{2}$/.test(until)
|
|
96
|
+
? new Date(end.getTime() + 86_400_000 - 1) : end;
|
|
97
|
+
const match = /^(\d+)d$/.exec(since ?? '7d');
|
|
98
|
+
const start = match ? new Date(inclusiveEnd.getTime() - Number(match[1]) * 86_400_000)
|
|
99
|
+
: instant(since ?? '7d', '--since');
|
|
100
|
+
if (!Number.isFinite(start.getTime()) || start > inclusiveEnd) throw new Error('invalid --since: start must be at or before --until');
|
|
101
|
+
return { since: start.toISOString(), until: inclusiveEnd.toISOString() };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function readSeatRegistry(): DependenceSource {
|
|
105
|
+
try {
|
|
106
|
+
const raw = JSON.parse(readFileSync(resolve(import.meta.dir, '../../scripts/coord-tracks.json'), 'utf8')) as { tracks?: unknown };
|
|
107
|
+
if (!Array.isArray(raw.tracks)) return { observations: [], unmeasurable: 'seat registry has no tracks' };
|
|
108
|
+
return { observations: raw.tracks.filter((track): track is { id: string; title: string; runtime?: string } =>
|
|
109
|
+
typeof track === 'object' && track !== null && typeof track.id === 'string' && typeof track.title === 'string')
|
|
110
|
+
.map(track => ({ track: track.id, runtime: track.runtime })) };
|
|
111
|
+
} catch (error) {
|
|
112
|
+
return { observations: [], unmeasurable: `seat registry unreadable: ${error instanceof Error ? error.message : String(error)}` };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function unknownSource(description: string): DependenceSource {
|
|
117
|
+
return { observations: [], unmeasurable: description };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function stringField(value: unknown): string | undefined {
|
|
121
|
+
return typeof value === 'string' && value.trim() ? value : undefined;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function object(value: unknown): Record<string, unknown> {
|
|
125
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function readRunExecutions(directory = join(elanousStateRoot(), 'run-ledger')): DependenceSource {
|
|
129
|
+
try {
|
|
130
|
+
if (!existsSync(directory)) return unknownSource('run-ledger directory absent');
|
|
131
|
+
const observations: DependenceObservation[] = [];
|
|
132
|
+
for (const name of readdirSync(directory).filter(name => name.endsWith('.jsonl')).sort()) {
|
|
133
|
+
const lines = readFileSync(join(directory, name), 'utf8').split('\n').filter(Boolean);
|
|
134
|
+
let launch: DependenceObservation | undefined;
|
|
135
|
+
for (const line of lines) {
|
|
136
|
+
const entry = object(JSON.parse(line));
|
|
137
|
+
if (entry.event === 'run-origin') {
|
|
138
|
+
const data = object(entry.data);
|
|
139
|
+
launch = { at: stringField(entry.timestamp), runtime: stringField(data.runtime),
|
|
140
|
+
agent: stringField(data.agent), track: stringField(data.track) };
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
if (!launch) launch = { at: stringField(entry.timestamp) };
|
|
144
|
+
}
|
|
145
|
+
if (launch) observations.push(launch);
|
|
146
|
+
}
|
|
147
|
+
return { observations };
|
|
148
|
+
} catch (error) {
|
|
149
|
+
return unknownSource(`run-ledger unreadable: ${error instanceof Error ? error.message : String(error)}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** 이 저장소의 실제 표지 — `Co-Authored-By:` 트레일러(작성자·커미터는 전부 같은 사람이라 쓰지 않는다).
|
|
154
|
+
* 하니스 착지(`elanous pod child`) > codex > Claude 순 — 한 병합에 여러 표지면 실제로 코드를 쓴 쪽이 앞선다. */
|
|
155
|
+
export function runtimeFromCoAuthors(body: string): string | undefined {
|
|
156
|
+
const names = [...body.matchAll(/^co-authored-by:[^\S\r\n]*([^<\r\n]*)/gim)].map(m => m[1]!.trim().toLowerCase());
|
|
157
|
+
if (names.some(n => n.includes('elanous'))) return 'elanous';
|
|
158
|
+
if (names.some(n => n.includes('codex'))) return 'codex';
|
|
159
|
+
if (names.some(n => n.includes('claude'))) return 'claude-code';
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function readChangeCommits(window: { since: string; until: string }, repo = resolve(import.meta.dir, '../..')): DependenceSource {
|
|
164
|
+
// Git object reads only: author and committer are deliberately NOT treated as the executor.
|
|
165
|
+
// 착지 = main 의 첫 부모 줄(스쿼시 병합 하나 = 착지 하나) — `--all` 은 가지의 중간 커밋까지 세어 부풀린다.
|
|
166
|
+
const result = spawnSync('git', ['log', 'origin/main', '--first-parent', '--format=%cI%x1f%B%x1e', `--since-as-filter=${window.since}`, `--until=${window.until}`], {
|
|
167
|
+
cwd: repo, encoding: 'utf8', timeout: 15_000, maxBuffer: 16 * 1024 * 1024,
|
|
168
|
+
});
|
|
169
|
+
if (result.status !== 0 || result.error) return unknownSource(`git history unreadable: ${result.error?.message ?? result.stderr ?? result.status}`);
|
|
170
|
+
if (result.stdout.length >= 16 * 1024 * 1024 - 1024) return unknownSource('git history exceeds gauge read limit');
|
|
171
|
+
const observations = result.stdout.split('\x1e').filter(record => record.includes('\x1f')).map(record => {
|
|
172
|
+
const [at, body] = record.trim().split('\x1f', 2);
|
|
173
|
+
const attribution = /(?:^|\n)(?:Executed-By|Agent-Runtime|Runtime):[^\S\r\n]*([^\r\n]*)/im.exec(body ?? '');
|
|
174
|
+
return { at, runtime: attribution?.[1]?.trim() ?? runtimeFromCoAuthors(body ?? '') };
|
|
175
|
+
});
|
|
176
|
+
return { observations };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** No mutation of ledgers or state; absent attribution data is reported, never guessed from an author or requester. */
|
|
180
|
+
export function measureDependence(deps: DependenceGaugeDeps = {}, options: { since?: string; until?: string } = {}): DependenceReport {
|
|
181
|
+
const window = dependenceWindow(options.since, options.until, (deps.now ?? (() => new Date()))());
|
|
182
|
+
const sources: Record<DependenceAxis, DependenceSource> = {
|
|
183
|
+
positions: (deps.readPositions ?? readSeatRegistry)(),
|
|
184
|
+
executions: (deps.readExecutions ?? readRunExecutions)(),
|
|
185
|
+
changes: (deps.readChanges ?? readChangeCommits)(window),
|
|
186
|
+
decisions: (deps.readDecisions ?? (() => unknownSource('decision actor cannot be measured from the available data')))(),
|
|
187
|
+
};
|
|
188
|
+
const positions = new Map<string, DependenceAttribution>();
|
|
189
|
+
for (const row of sources.positions.observations) {
|
|
190
|
+
if (row.track) positions.set(row.track, identity(row.runtime));
|
|
191
|
+
}
|
|
192
|
+
const result = {} as Record<DependenceAxis, DependenceAxisResult>;
|
|
193
|
+
for (const axis of axes) {
|
|
194
|
+
const source = sources[axis];
|
|
195
|
+
const counts: DependenceAxisResult = { status: source.unmeasurable ? 'unmeasurable' : 'measured', total: 0, covered: 0, claudeCode: 0, elanous: 0, other: 0, unknown: 0, undated: 0,
|
|
196
|
+
...(source.unmeasurable ? { reason: source.unmeasurable } : {}) };
|
|
197
|
+
for (const row of source.observations) {
|
|
198
|
+
if (axis !== 'positions') {
|
|
199
|
+
const at = observationTime(row.at);
|
|
200
|
+
if (at === undefined) { counts.undated++; continue; }
|
|
201
|
+
if (at < Date.parse(window.since) || at > Date.parse(window.until)) continue;
|
|
202
|
+
}
|
|
203
|
+
counts.total++;
|
|
204
|
+
const attribution = attributeDependence(row, positions);
|
|
205
|
+
if (attribution === 'unknown') counts.unknown++;
|
|
206
|
+
else {
|
|
207
|
+
counts.covered++;
|
|
208
|
+
if (attribution === 'claude-code') counts.claudeCode++;
|
|
209
|
+
else if (attribution === 'elanous') counts.elanous++;
|
|
210
|
+
else counts.other++;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
result[axis] = counts;
|
|
214
|
+
}
|
|
215
|
+
return { ...window, axes: result };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function formatDependenceReport(report: DependenceReport): string {
|
|
219
|
+
return [
|
|
220
|
+
`Dependence · ${report.since} — ${report.until}`,
|
|
221
|
+
...axes.map(axis => {
|
|
222
|
+
const row = report.axes[axis];
|
|
223
|
+
return `${axis}: ${row.status}${row.reason ? ` (${row.reason})` : ''} · coverage ${row.covered}/${row.total} · claude-code ${row.claudeCode} · elanous ${row.elanous} · other ${row.other} · unknown ${row.unknown} · undated ${row.undated}`;
|
|
224
|
+
}),
|
|
225
|
+
].join('\n');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Register below the existing `self` command rather than creating a second top-level command. */
|
|
229
|
+
export function registerDependenceCommand(self: Command, deps: DependenceGaugeDeps = {}): void {
|
|
230
|
+
self.command('dependence').description('Read-only attribution gauge (unknown is not zero)')
|
|
231
|
+
.option('--since <date>', 'UTC date or lookback period (default 7d)', '7d')
|
|
232
|
+
.option('--until <date>', 'UTC date or timestamp').option('--json', 'JSON output')
|
|
233
|
+
.action((options: { since?: string; until?: string; json?: boolean }) => {
|
|
234
|
+
const report = measureDependence(deps, options);
|
|
235
|
+
(deps.out ?? console).log(options.json ? JSON.stringify(report) : formatDependenceReport(report));
|
|
236
|
+
});
|
|
237
|
+
}
|