elanous 0.2.5 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +10 -7
- package/README.md +9 -4
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5391-8793f88b03b30161.js → 2375-cb6debea56cce4fb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{1751.50b96ea9c37b49b6.js → 2415.ed6428ff334c7ce2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +25 -0
- package/apps/pwa/out/_next/static/chunks/{4daa2798-1363d725c79f3593.js → 2ea96958-e8c7e112de50b067.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +5 -0
- package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +1 -0
- package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8617.465ca22a319c95b3.js → 4323.c5dfb6f503304775.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4836-2eba6079f31b88d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8918-482b30ce2a8ebf4c.js → 5080-1613752891fcadb4.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5348-bbf838e119661eee.js → 5791-bb82743fa6299562.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{9704-3221c102623c8d73.js → 6062-73e68343a9814f6a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{4440-18bb160d95819f4e.js → 6090-85a3f05f954e5697.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{f6f3850d-7f324c6566002113.js → 8046cadf-211ca3c0e00f2b72.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8067-f74bf14d67ad98f1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8519-f6b5d8a5833a7603.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8894-e92491a164192cc1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9454-a3e0953307216c58.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9740-67051141115ce775.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9804-64c02c3c127d5014.js +1 -0
- package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-d5ebde04844689e6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-f89e9a303515a033.js → page-10ed24f413ed66e9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/{page-4f403cd060442c95.js → page-211810b6c93ab66a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-0ac5d053f0444d21.js → page-624f7a2f89b60c8f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-5a5fb385a4df0ffe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-80497e93a878ddc5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-7457f52683562161.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-cee86394de407aa6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-4ff8edab44196b8c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-92e4f44428d5e290.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-b7dcf5d48a854b76.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-51480757e7d68b92.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-2a05dfe80c8bdf16.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-d9979633aa82f8e6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-30533117beb53625.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-f7857826c2f97ad7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-07267f1391b96aba.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-bccce0babceae30f.js → page-55d50e9b0006a91d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-9699b1786be6171d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-43dd772aee8af75f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-a7e37af7d0399cb8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-973ab7af59c718b7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a9f8d3865b91e436.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-15e5a15db84c56aa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a2e572e4511f2a16.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-dd107bf1c7d52a90.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-d42b99cdfb9f27c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-2764ff40dad5a995.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-c5f1152db116ff96.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-f933bca81ea38a6f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-c1c95739c0bff3b8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-a7aaffcf771662d8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-71d18eec33b9c667.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-56adbe1a1f1379bd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-2c4e59079ee357ac.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-a81f113f3ef89d72.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-5d422f38c052967e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-fc27b00e20733146.js +1 -0
- package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{webpack-2bf59c720fe9542c.js → webpack-e5bea36f183b25e6.js} +1 -1
- package/apps/pwa/out/_next/static/css/f537bdb19c69306a.css +1 -0
- package/apps/pwa/out/_next/static/keajW7DDyghEs1y8oJe6T/_buildManifest.js +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +22 -22
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +22 -22
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +22 -22
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +21 -21
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +21 -21
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +21 -21
- package/apps/pwa/out/consult/index.html +1 -0
- package/apps/pwa/out/consult/index.txt +32 -0
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +22 -22
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +22 -22
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +22 -22
- package/apps/pwa/out/exec/index.html +1 -0
- package/apps/pwa/out/exec/index.txt +32 -0
- package/apps/pwa/out/field/index.html +1 -0
- package/apps/pwa/out/field/index.txt +32 -0
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/icons/elanous-16.png +0 -0
- package/apps/pwa/out/icons/elanous-180.png +0 -0
- package/apps/pwa/out/icons/elanous-192.png +0 -0
- package/apps/pwa/out/icons/elanous-32.png +0 -0
- package/apps/pwa/out/icons/elanous-512.png +0 -0
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +21 -21
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +22 -22
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +22 -22
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +21 -21
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +21 -21
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +22 -22
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +22 -22
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +22 -22
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +22 -22
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +21 -21
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +22 -22
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +22 -22
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +22 -22
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +22 -22
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +22 -22
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +21 -21
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +22 -22
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +22 -22
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +22 -22
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +22 -22
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +22 -22
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +22 -22
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +22 -22
- package/bin/elanous.cjs +107 -0
- package/graphs/field/field-feed.yaml +24 -0
- package/graphs/field/recipes.yaml +12 -0
- package/graphs/landing/landing-heal.yaml +3 -1
- package/graphs/landing/recipes.yaml +3 -0
- package/graphs/release/recipes.yaml +13 -0
- package/graphs/release/release-loop.yaml +13 -4
- package/package.json +5 -5
- package/plugins/card-followup/README.md +42 -0
- package/plugins/card-followup/examples/card-sample.json +7 -0
- package/plugins/card-followup/graphs/card-followup.yaml +18 -0
- package/plugins/card-followup/graphs/recipes.yaml +16 -0
- package/plugins/card-followup/graphs/run-step.ts +276 -0
- package/plugins/card-followup/graphs/sales-strategy.ts +173 -0
- package/plugins/card-followup/plugin.json +25 -0
- package/plugins/doc-draft/README.md +13 -0
- package/plugins/doc-draft/examples/brief-sample.json +8 -0
- package/plugins/doc-draft/graphs/doc-draft.yaml +14 -0
- package/plugins/doc-draft/graphs/recipes.yaml +9 -0
- package/plugins/doc-draft/graphs/run-step.ts +113 -0
- package/plugins/doc-draft/plugin.json +24 -0
- package/plugins/geo-check/README.md +15 -0
- package/plugins/geo-check/examples/brand-sample.json +8 -0
- package/plugins/geo-check/graphs/geo-check.yaml +20 -0
- package/plugins/geo-check/graphs/recipes.yaml +18 -0
- package/plugins/geo-check/graphs/run-step.ts +198 -0
- package/plugins/geo-check/plugin.json +24 -0
- package/plugins/job-coach/.codex-plugin/plugin.json +1 -1
- package/plugins/job-coach/README.md +31 -1
- package/plugins/job-coach/graphs/run-step.ts +2 -2
- package/plugins/job-coach/plugin.json +1 -1
- package/plugins/job-coach/plugin.ts +1 -1
- package/scripts/affinity/endcard.sh +3 -2
- package/scripts/affinity/reframe.sh +3 -1
- package/scripts/ci-public-export-leak-gate.ts +48 -0
- package/scripts/codex-quota-alert.ts +122 -258
- package/scripts/coord-tracks.json +4 -4
- package/scripts/daemon-port-baseline.txt +1 -2
- package/scripts/field-feed/deliver-node.ts +52 -0
- package/scripts/field-feed/feed-node.ts +67 -0
- package/scripts/field-feed/lib.ts +162 -0
- package/scripts/field-feed/reel-node.ts +24 -0
- package/scripts/field-feed/start.ts +62 -0
- package/scripts/fixtures/vflow-sample.ndjson +6 -0
- package/scripts/install.sh +92 -13
- package/scripts/landing-heal/clean-checkout.ts +114 -0
- package/scripts/landing-heal/run-mission.ts +38 -3
- package/scripts/lib/android-test-device.ts +32 -0
- package/scripts/lib/pwa-scenarios/scenarios.ts +2 -1
- package/scripts/public-export.ts +8 -4
- package/scripts/release-loop/baseline-from-logs.ts +92 -0
- package/scripts/release-loop/checklist-gate-node.ts +35 -0
- package/scripts/release-loop/env-known-failures.ts +31 -0
- package/scripts/release-loop/gate-diff.ts +29 -0
- package/scripts/release-loop/gate-node.ts +124 -29
- package/scripts/release-loop/known-issues-node.ts +159 -0
- package/scripts/release-loop/npm-publish-node.ts +118 -0
- package/scripts/release-loop/ops-upgrade-node.ts +87 -0
- package/scripts/release-loop/release-readiness.ts +58 -0
- package/scripts/release-loop/shard-plan.ts +47 -0
- package/scripts/release-loop/unattended-release.ts +93 -0
- package/scripts/ux/measure-first-text.ts +102 -0
- package/scripts/ux/record-page.ts +4 -1
- package/scripts/vflow-collect.ts +20 -7
- package/scripts/vflow-search.ts +73 -0
- package/scripts/video-free-line-test-walker.ts +36 -0
- package/scripts/video-pipeline.ts +2 -2
- package/skills/explainer-video/SKILL.md +24 -3
- package/skills/explainer-video/engine/brand/elanous-mark-on-dark.svg +1 -1
- package/skills/explainer-video/engine/build.mjs +2 -2
- package/skills/explainer-video/engine/reel.mjs +235 -0
- package/skills/explainer-video/engine/reel.sh +9 -0
- package/skills/explainer-video/engine/vo.ts +58 -16
- package/skills/explainer-video/examples/elanous-0.2.5/script.json +1 -1
- package/skills/grill-me/SKILL.md +1 -1
- package/skills/omni-digest/docs/diagrams/noncode-terminal-promotion-path-2026-08-13.mmd +1 -1
- package/src/a2a/server.ts +2 -0
- package/src/acp/elanous-aware-peers.ts +30 -0
- package/src/acp/server.ts +91 -15
- package/src/agent/monad-agent-turn.ts +25 -6
- package/src/agent/shared-app-tools.ts +5 -3
- package/src/agent-substrate/review-intent.ts +6 -2
- package/src/agent-substrate/shard-boundary-intent.ts +70 -0
- package/src/auth/github-app-token.ts +4 -1
- package/src/autopilot/mission-origin.ts +20 -4
- package/src/autopilot/mission-phase-track.ts +11 -1
- package/src/autopilot/track-agent-shadow.ts +151 -0
- package/src/autopilot/track-agent.ts +26 -6
- package/src/autopilot/track-courier.ts +2 -2
- package/src/boot/acp-server-help.ts +16 -0
- package/src/boot/daemon-prompt-turn.ts +31 -10
- package/src/boot/daemon-tools/index.ts +7 -1
- package/src/budget/codex-credit-pace.ts +84 -0
- package/src/budget/codex-credit-plan.ts +154 -0
- package/src/budget/codex-reset-credit-state.ts +2 -0
- package/src/chat/chat-cli.ts +4 -1
- package/src/chat/short-question.ts +40 -0
- package/src/cli/agent-cli.ts +24 -15
- package/src/cli/ask-bare.ts +42 -0
- package/src/cli/cli-user-error.ts +25 -0
- package/src/cli/codex-credit-plan-view.ts +25 -0
- package/src/cli/dependence-gauge.ts +237 -0
- package/src/cli/doctor-cli.ts +5 -0
- package/src/cli/doctor-modes.ts +48 -0
- package/src/cli/intake-cli.ts +12 -2
- package/src/cli/intake-source-cli.ts +58 -0
- package/src/cli/machine-cli.ts +23 -0
- package/src/cli/msg-cli.ts +137 -0
- package/src/cli/nexus-ios-bind.ts +25 -8
- package/src/cli/phone-link.ts +117 -0
- package/src/cli/plugin-cli.ts +52 -0
- package/src/cli/pr-cli.ts +36 -0
- package/src/cli/private-ledger-commands.ts +31 -0
- package/src/cli/provider-cli.ts +5 -0
- package/src/cli/release-cli.ts +56 -12
- package/src/cli/release-run-if-ready.ts +48 -0
- package/src/cli/setup-cli.ts +27 -6
- package/src/cli/setup-recommend.ts +8 -0
- package/src/cli/usage-cli.ts +11 -1
- package/src/codex/setup.ts +27 -8
- package/src/dashboard/index.ts +1 -1
- package/src/discord.ts +56 -6
- package/src/domains/core-tools.ts +37 -14
- package/src/domains/outbound-alert.ts +55 -16
- package/src/exec-requests/planner.ts +140 -0
- package/src/exec-requests/runner.ts +338 -0
- package/src/exec-requests/store.ts +76 -0
- package/src/field/field-media.ts +250 -0
- package/src/field/field-reel.ts +196 -0
- package/src/grok/credential.ts +7 -5
- package/src/harness/aside-run-status.ts +33 -0
- package/src/harness/browser-verify.ts +48 -17
- package/src/harness/harness-cli-command.ts +25 -3
- package/src/harness/image-load-state.ts +21 -0
- package/src/harness/process-garbage.ts +45 -0
- package/src/index.ts +104 -38
- package/src/instance/leader.ts +13 -1
- package/src/intake-plane/collect-github.ts +5 -3
- package/src/intake-plane/collect-telegram-saved.ts +15 -3
- package/src/intake-plane/discord-seat-work.ts +56 -0
- package/src/intake-plane/intake-sources.ts +202 -0
- package/src/intake-plane/items.ts +28 -12
- package/src/intake-plane/seat-doc-route.ts +207 -0
- package/src/intake-plane/submit-intake-work.ts +9 -1
- package/src/intake-plane/telegram-seat-work.ts +72 -0
- package/src/llm/model-defaults.ts +24 -1
- package/src/llm.ts +5 -4
- package/src/machines/machine-ledger.ts +127 -0
- package/src/market/pack-internal-refs.ts +28 -0
- package/src/market/publish.ts +31 -4
- package/src/maturity/feature-maturity.ts +65 -0
- package/src/model-tier/turn-tier.ts +24 -0
- package/src/msg/msg-store.ts +149 -0
- package/src/native-tool-catalog.ts +2 -1
- package/src/nexus/api/chat-fast-path-config.ts +28 -0
- package/src/nexus/api/consult-requests.ts +117 -0
- package/src/nexus/api/discord-trigger-bot.ts +3 -0
- package/src/nexus/api/exec-requests.ts +51 -0
- package/src/nexus/api/field-uploads.ts +180 -0
- package/src/nexus/api/graph-approvals-feed.ts +140 -0
- package/src/nexus/api/graph-approvals.ts +120 -4
- package/src/nexus/api/harness-api.ts +219 -5
- package/src/nexus/api/http-server.ts +68 -5
- package/src/nexus/api/seat-requests.ts +254 -0
- package/src/nexus/api/trace-run-scan.ts +37 -0
- package/src/nexus/api/trace.ts +19 -3
- package/src/nexus/index.ts +3 -0
- package/src/nexus/setup-status.ts +4 -5
- package/src/notify/llm-budget-alert.ts +125 -0
- package/src/oauth/codex-account-rotation.ts +26 -4
- package/src/oauth/codex-account-store.ts +20 -2
- package/src/oauth/codex-browser-login.ts +173 -0
- package/src/oauth/codex.ts +4 -1
- package/src/oauth/fallback-chain.ts +5 -5
- package/src/oauth/store.ts +42 -5
- package/src/onboarding/continue-here.ts +29 -0
- package/src/onboarding/entry-hints.ts +8 -0
- package/src/onboarding/full-screen-io.ts +35 -3
- package/src/onboarding/help/skills.md +1 -1
- package/src/onboarding/non-interactive.ts +1 -0
- package/src/onboarding/subscription-hint.ts +27 -0
- package/src/onboarding/web-first.ts +83 -0
- package/src/onboarding.ts +230 -23
- package/src/plugins/maker/node-install.ts +92 -0
- package/src/plugins/maker/node-maker.ts +139 -0
- package/src/plugins/maker/plugin-maker.ts +220 -0
- package/src/policy/llm-policy.ts +268 -0
- package/src/prompt-library/session-guidance.ts +7 -1
- package/src/prompt-library/universal-preamble.ts +2 -2
- package/src/relay/channel.ts +184 -0
- package/src/relay/mux.ts +182 -0
- package/src/relay/relay-cli.ts +20 -0
- package/src/relay/relay-server.ts +220 -0
- package/src/relay/test-vectors.json +29 -0
- package/src/release-loop/checklist.ts +36 -9
- package/src/seat-address/seat-address.ts +34 -0
- package/src/self-dev/decompose.ts +1 -0
- package/src/self-dev/dev-pipeline.ts +6 -3
- package/src/self-dev/run-record-mirror.ts +44 -0
- package/src/self-dev/run-store.ts +12 -3
- package/src/self-implement/harness-policy-gates.ts +4 -1
- package/src/self-implement/report-origin.ts +12 -1
- package/src/self-implement/run-ledger-compact.ts +1 -1
- package/src/session/chat.ts +17 -5
- package/src/session/index.ts +23 -10
- package/src/skills/shared-agent-skill-roots.ts +25 -0
- package/src/steward/failure-streak.ts +78 -0
- package/src/steward/steward-cards.ts +24 -0
- package/src/steward/triage-budget.ts +127 -0
- package/src/steward/triage-plan.ts +75 -0
- package/src/steward/triage.ts +244 -42
- package/src/task-cards/card-store.ts +1 -1
- package/src/task-orchestrator/surfaces/pod-bun-cache.ts +18 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +12 -3
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +11 -5
- package/src/task-orchestrator/surfaces/pod-ledger-events.ts +68 -0
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +78 -4
- package/src/telegram.ts +145 -2
- package/src/tool-runtime/elanous-skills-list-runtime.ts +3 -3
- package/src/tool-runtime/self-cognition-runtimes.ts +160 -2
- package/src/user-config.ts +84 -4
- package/src/version/packed-revision.json +1 -1
- package/src/video-pipeline/recipes/character.ts +42 -5
- package/src/video-pipeline/recipes/explainer.ts +213 -0
- package/src/video-pipeline/recipes/ffmpeg.ts +1 -1
- package/src/video-pipeline/recipes/free-line.ts +16 -3
- package/src/video-pipeline/recipes/remote.ts +7 -1
- package/src/video-pipeline/vflow-reference.ts +109 -0
- package/src/video-pipeline/walk-line.ts +3 -2
- package/apps/pwa/out/_next/static/chunks/06ef2eba-d1b99ac6d2ea663c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1413-7179a49332db5a80.js +0 -1
- package/apps/pwa/out/_next/static/chunks/150-25c277137a9638ab.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1640-bc99ec23bbe29d12.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2300-de059d0ab2a16630.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2684-05b0a81e75298c16.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3026-2ed7e317d9b02102.js +0 -25
- package/apps/pwa/out/_next/static/chunks/3611-77496ada38cd6358.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3717-68957c4dc9ceec90.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3929-46ba9af6f9d895cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4015-62c5a3744698a8da.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4217-3c4317a7905f9eeb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4240-3230f230fae2d7d5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4271-e53e075ebd48593b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4377.3b125fef79576dfd.js +0 -5
- package/apps/pwa/out/_next/static/chunks/4853-35fdbe6bdbcdc0fc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5007-6a3b30e23ff68a6e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5244-12b424c42070f5ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5611-df00af3e4e4a61bb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5890-dca2321773be829a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6123-37c9dc57ddaa7ab0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6588-b7975304a302ab91.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6604-c03961962b6273b6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7109-cf8c87e968e83a65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7178-30f77877525a2efb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7262-871563fc9663730b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7294-f0eeebb92aa93459.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7429-881af44deed6958f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7527.943753b6ed829f09.js +0 -1
- package/apps/pwa/out/_next/static/chunks/764-a01e858525a190c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7868-e35d5f7985ecd3f1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8320-51484af7b01fdfa5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9131-52f5fad51cceca04.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9238.2cd5f47f4bf7a2c3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-70c404c7bb6d7f0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-3ffd80166b1050aa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-4bcc63e272ae8205.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-880734bd07d62882.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-72bd384898d88955.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-1018bbc9586ecae5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-de86f8c38076dd7b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-70c404c7bb6d7f0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-cd6447196d8bafe6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-e733630c47fa102b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-d1c1a8357b161875.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-60c514128e81d787.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8b637b7e325e13c1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-63301d11dbe73836.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-a8cf0dc472060db2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-02fba54881ccfa49.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-d5e8e90f0038f8de.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-b1ef6824f494466a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-d589bb0f233b1009.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-c9bbaebef4cda059.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-3639ce7916b92fed.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-7450ae7478955653.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-70c404c7bb6d7f0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-b41ca4abfd618452.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-b333dd79ebee3fa9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-8be1227492793655.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-6e03e9371b552e0d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-911411e9fca18d03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-792170e649b37088.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-c40463466937bc91.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-509c798b80384536.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-1cac2aa21d77c417.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-9626799d9b4a2cd1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-84cd419494e16b0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-361487d2da603917.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-607f5beb94cb2e07.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-d0222d219e29dddf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-a26980a31126decc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-edb2b37decf23e4d.js +0 -1
- package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +0 -1
- package/apps/pwa/out/_next/static/ktMqfrBbf8BMGac0jqLrb/_buildManifest.js +0 -1
- /package/apps/pwa/out/_next/static/{ktMqfrBbf8BMGac0jqLrb → keajW7DDyghEs1y8oJe6T}/_ssgManifest.js +0 -0
|
@@ -4,14 +4,17 @@ import { dirname, join } from 'node:path';
|
|
|
4
4
|
import { randomUUID } from 'node:crypto';
|
|
5
5
|
import { debug } from '../debug/log.js';
|
|
6
6
|
import { releaseLedgerRoot } from '../instance/resolve.js';
|
|
7
|
+
import { CliUserError } from '../cli/cli-user-error.js';
|
|
7
8
|
|
|
8
9
|
export type ChecklistStatus = 'green' | 'yellow' | 'red' | 'done';
|
|
10
|
+
export type ChecklistDisposition = 'move' | 'known-issue' | 'block';
|
|
9
11
|
export interface ChecklistItem {
|
|
10
12
|
id: string;
|
|
11
13
|
title: string;
|
|
12
14
|
status: ChecklistStatus;
|
|
13
15
|
owner?: string;
|
|
14
16
|
evidence?: string;
|
|
17
|
+
disposition?: ChecklistDisposition;
|
|
15
18
|
updatedAt: string;
|
|
16
19
|
updatedBy: string;
|
|
17
20
|
}
|
|
@@ -52,7 +55,7 @@ function releasedVersion(): string {
|
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
function pathFor(v: string): string {
|
|
55
|
-
if (!/^\d+\.\d+\.\d+(?:-(?:rc|alpha|beta)\.\d+)?$/.test(v)) throw new
|
|
58
|
+
if (!/^\d+\.\d+\.\d+(?:-(?:rc|alpha|beta)\.\d+)?$/.test(v)) throw new CliUserError(`체크리스트 판이 아니다: ${v}`);
|
|
56
59
|
return join(releaseLedgerRoot(), 'release', v, 'checklist.json');
|
|
57
60
|
}
|
|
58
61
|
|
|
@@ -108,10 +111,10 @@ function change(data: Checklist, id: string, field: string, from: unknown, to: u
|
|
|
108
111
|
function refresh(data: Checklist): void { data.released = releasedVersion(); data.dev = devVersion(); }
|
|
109
112
|
|
|
110
113
|
export function addItem(v: string, input: { id: string; title: string; owner?: string }): Checklist {
|
|
111
|
-
if (!input.id.trim()) throw new
|
|
112
|
-
if (!input.title.trim()) throw new
|
|
114
|
+
if (!input.id.trim()) throw new CliUserError('칸 id 가 비었다');
|
|
115
|
+
if (!input.title.trim()) throw new CliUserError('칸 제목이 비었다');
|
|
113
116
|
return mutate(v, (data) => {
|
|
114
|
-
if (data.items.some((item) => item.id === input.id)) throw new
|
|
117
|
+
if (data.items.some((item) => item.id === input.id)) throw new CliUserError(`이미 있는 칸: ${input.id}`, 'set <id> 로 고친다');
|
|
115
118
|
const by = process.env.ELANOUS_TRACK || 'cli';
|
|
116
119
|
const at = new Date().toISOString();
|
|
117
120
|
const item: ChecklistItem = { id: input.id, title: input.title, status: 'yellow', ...(input.owner !== undefined ? { owner: input.owner } : {}), updatedAt: at, updatedBy: by };
|
|
@@ -121,12 +124,13 @@ export function addItem(v: string, input: { id: string; title: string; owner?: s
|
|
|
121
124
|
});
|
|
122
125
|
}
|
|
123
126
|
|
|
124
|
-
export function setItem(v: string, id: string, patch: { status?: ChecklistStatus; evidence?: string; owner?: string }, by: string): Checklist {
|
|
127
|
+
export function setItem(v: string, id: string, patch: { status?: ChecklistStatus; evidence?: string; owner?: string; disposition?: ChecklistDisposition }, by: string): Checklist {
|
|
125
128
|
return mutate(v, (data) => {
|
|
126
129
|
const item = data.items.find((i) => i.id === id);
|
|
127
|
-
if (!item) throw new
|
|
128
|
-
if (patch.status !== undefined && !['green', 'yellow', 'red', 'done'].includes(patch.status)) throw new
|
|
129
|
-
|
|
130
|
+
if (!item) throw new CliUserError(`없는 칸: ${id}`, 'list 로 칸 목록을 본다');
|
|
131
|
+
if (patch.status !== undefined && !['green', 'yellow', 'red', 'done'].includes(patch.status)) throw new CliUserError(`잘못된 상태: ${patch.status}`);
|
|
132
|
+
if (patch.disposition !== undefined && !['move', 'known-issue', 'block'].includes(patch.disposition)) throw new CliUserError(`잘못된 처분: ${patch.disposition}`);
|
|
133
|
+
const fields = (['evidence', 'owner', 'status', 'disposition'] as const).filter((field) => patch[field] !== undefined && patch[field] !== item[field]);
|
|
130
134
|
if (fields.length === 0) return false;
|
|
131
135
|
const at = new Date().toISOString();
|
|
132
136
|
for (const field of fields) {
|
|
@@ -144,7 +148,7 @@ export function setItem(v: string, id: string, patch: { status?: ChecklistStatus
|
|
|
144
148
|
export function removeItem(v: string, id: string, by: string): Checklist {
|
|
145
149
|
return mutate(v, (data) => {
|
|
146
150
|
const index = data.items.findIndex((i) => i.id === id);
|
|
147
|
-
if (index < 0) throw new
|
|
151
|
+
if (index < 0) throw new CliUserError(`없는 칸: ${id}`, 'list 로 칸 목록을 본다');
|
|
148
152
|
const at = new Date().toISOString();
|
|
149
153
|
const [item] = data.items.splice(index, 1);
|
|
150
154
|
change(data, id, 'remove', item, null, by, at);
|
|
@@ -169,6 +173,29 @@ export function summarizeChecklist(data: Checklist): ChecklistSummary {
|
|
|
169
173
|
return summary;
|
|
170
174
|
}
|
|
171
175
|
|
|
176
|
+
export interface ChecklistGate {
|
|
177
|
+
ok: boolean;
|
|
178
|
+
red: string[];
|
|
179
|
+
undecided: string[];
|
|
180
|
+
blocked: string[];
|
|
181
|
+
moved: string[];
|
|
182
|
+
knownIssues: Array<{ id: string; title: string; evidence: string }>;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function checklistGate(v: string): ChecklistGate {
|
|
186
|
+
const result: ChecklistGate = { ok: true, red: [], undecided: [], blocked: [], moved: [], knownIssues: [] };
|
|
187
|
+
for (const item of listChecklist(v).items) {
|
|
188
|
+
if (item.status === 'red') result.red.push(item.id);
|
|
189
|
+
if (item.status !== 'yellow') continue;
|
|
190
|
+
if (item.disposition === 'block') result.blocked.push(item.id);
|
|
191
|
+
else if (item.disposition === 'move') result.moved.push(item.id);
|
|
192
|
+
else if (item.disposition === 'known-issue') result.knownIssues.push({ id: item.id, title: item.title, evidence: item.evidence ?? '' });
|
|
193
|
+
else result.undecided.push(item.id);
|
|
194
|
+
}
|
|
195
|
+
result.ok = result.red.length === 0 && result.undecided.length === 0 && result.blocked.length === 0;
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
|
|
172
199
|
export function seedFromRoadmap(v: string, markdown: string): Checklist {
|
|
173
200
|
return mutate(v, (data) => {
|
|
174
201
|
let added = false;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import trackData from '../../scripts/coord-tracks.json';
|
|
2
|
+
|
|
3
|
+
export type SeatEntry = {
|
|
4
|
+
id: string;
|
|
5
|
+
title?: string;
|
|
6
|
+
alias?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type SeatAddress = { seats: string[]; body: string };
|
|
10
|
+
|
|
11
|
+
const registry: readonly SeatEntry[] = trackData.tracks.filter((entry) => 'title' in entry);
|
|
12
|
+
const addressLine = /^@([A-Za-z][A-Za-z0-9_-]*(?:,[A-Za-z][A-Za-z0-9_-]*)*)(?:[ \t]+|\r?\n|(?![\s\S]))/gm;
|
|
13
|
+
|
|
14
|
+
/** Parse an address only when its @ begins a line; leave the instruction text intact. */
|
|
15
|
+
export function parseSeatAddress(text: string): SeatAddress | null {
|
|
16
|
+
const match = addressLine.exec(text);
|
|
17
|
+
addressLine.lastIndex = 0;
|
|
18
|
+
if (!match) return null;
|
|
19
|
+
|
|
20
|
+
const seats = match[1]!.split(',');
|
|
21
|
+
const body = `${text.slice(0, match.index)}${text.slice(match.index + match[0].length)}`;
|
|
22
|
+
return { seats, body };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Resolve against the seat registry, preferring titles to ids to legacy aliases. */
|
|
26
|
+
export function resolveSeat(address: string, seats: readonly SeatEntry[] = registry): SeatEntry | undefined {
|
|
27
|
+
const needle = address.replace(/^@/, '').toLocaleLowerCase();
|
|
28
|
+
if (!needle) return undefined;
|
|
29
|
+
for (const field of ['title', 'id', 'alias'] as const) {
|
|
30
|
+
const found = seats.find((seat) => seat[field]?.toLocaleLowerCase() === needle);
|
|
31
|
+
if (found) return found;
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
@@ -341,6 +341,7 @@ export function buildSelfDevDecomposePrompt(feature: string, maxTasks = DEFAULT_
|
|
|
341
341
|
'## Rules',
|
|
342
342
|
`- Emit at most ${maxTasks} sub-features. Fewer is better — do NOT over-split, except when a sub-feature mixes ${SELF_DEV_CONCERN_SPLIT_THRESHOLD} or more concern classes — ${SELF_DEV_CONCERN_CLASSES.join(' / ')} — split it into sibling sub-features.`,
|
|
343
343
|
'- SEPARATE DEFINE FROM WIRE: adding a new function/type/export is a SEPARATE task from wiring it into an existing runtime call site. A task that both defines a new symbol AND integrates it across modules tends to leave dead-code (defined but never called) that fails integration gates. Pattern: (a) add the function + its unit test; then (b) a dependent task that wires it into the specific existing call site — name the exact `file.ts:function` to modify.',
|
|
344
|
+
'- Exception to SEPARATE DEFINE FROM WIRE: Keep a definition and its registration/wiring in the same sub-feature — a handler with its route, a command with its CLI registration, a node with its graph/recipe entry.',
|
|
344
345
|
'- Each sub-feature must be a self-contained, mergeable unit of work.',
|
|
345
346
|
'- `dependsOn`: list the `id`s of sub-features that MUST land first (e.g. a shared',
|
|
346
347
|
' type/interface before its consumers). Independent sub-features share NO dependency',
|
|
@@ -1871,7 +1871,10 @@ async function runDevPipelineDispatch(
|
|
|
1871
1871
|
observeDevSelection('completion', plan.completion, plan.completionSource, plan.completionSource === 'request' ? spec.completion : undefined, entryRoute, spec.runId, plan.entrance, plan.entranceUnstamped);
|
|
1872
1872
|
observeDevSelection('autoReview', plan.autoReview, plan.autoReviewSource, plan.autoReviewSource === 'request' ? spec.autoReview : undefined, entryRoute, spec.runId, plan.entrance, plan.entranceUnstamped);
|
|
1873
1873
|
assertDispatchHonorsOptions(plan); // 미이행 옵션 거부(수락 후 무시 금지)
|
|
1874
|
-
|
|
1874
|
+
// CHAT1 — a plain chat turn (`interactive`) is not a dev run: its completion/base lines are noise to a person.
|
|
1875
|
+
// The selection is still observed above; only the human-facing lines are dev-run-only.
|
|
1876
|
+
const devRunLines = plan.dispatch !== 'interactive';
|
|
1877
|
+
if (devRunLines) emitHumanProgress(completionAnnouncement(plan.completion), spec.humanReadableOutput !== false, deps.progress);
|
|
1875
1878
|
if (spec.notice) emitHumanProgress(spec.notice, spec.humanReadableOutput !== false, deps.progress);
|
|
1876
1879
|
|
|
1877
1880
|
if (plan.dispatch === 'parallel') {
|
|
@@ -1899,8 +1902,8 @@ async function runDevPipelineDispatch(
|
|
|
1899
1902
|
rule: plan.baseSelection?.rule ?? 'unspecified',
|
|
1900
1903
|
evidence: plan.baseSelection?.evidence ?? 'not applicable',
|
|
1901
1904
|
});
|
|
1902
|
-
emitHumanProgress(formatDevBaseSelectionAnnouncement(plan), spec.humanReadableOutput !== false, deps.progress);
|
|
1903
|
-
const nonDefaultBaseWarning = formatDevNonDefaultBaseWarning(plan);
|
|
1905
|
+
if (devRunLines) emitHumanProgress(formatDevBaseSelectionAnnouncement(plan), spec.humanReadableOutput !== false, deps.progress);
|
|
1906
|
+
const nonDefaultBaseWarning = devRunLines ? formatDevNonDefaultBaseWarning(plan) : undefined;
|
|
1904
1907
|
if (nonDefaultBaseWarning) {
|
|
1905
1908
|
emitHumanProgress(nonDefaultBaseWarning, spec.humanReadableOutput !== false, deps.progress);
|
|
1906
1909
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import { debug } from '../debug/log.js';
|
|
5
|
+
import { releaseLedgerRoot } from '../instance/resolve.js';
|
|
6
|
+
import type { SelfDevRunState } from './run-store.js';
|
|
7
|
+
|
|
8
|
+
export function mirrorDir(): string {
|
|
9
|
+
return join(releaseLedgerRoot(), 'self-dev-runs');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function mirrorPath(runId: string, dir: string): string {
|
|
13
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(runId)) throw new Error(`invalid self-dev run ID: ${runId}`);
|
|
14
|
+
return join(dir, `${runId}.json`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Best-effort atomic copy in the machine ledger; never disrupts the primary checkpoint. */
|
|
18
|
+
export function mirrorRunRecord(state: SelfDevRunState, primaryDir: string): void {
|
|
19
|
+
let temp: string | undefined;
|
|
20
|
+
try {
|
|
21
|
+
const dir = mirrorDir();
|
|
22
|
+
if (resolve(primaryDir) === resolve(dir)) return;
|
|
23
|
+
const path = mirrorPath(state.runId, dir);
|
|
24
|
+
mkdirSync(dir, { recursive: true });
|
|
25
|
+
temp = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
26
|
+
writeFileSync(temp, JSON.stringify(state, null, 2), { mode: 0o600 });
|
|
27
|
+
renameSync(temp, path);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (temp) { try { unlinkSync(temp); } catch { /* no temporary file to remove */ } }
|
|
30
|
+
try { debug.log('self-dev.run-store', 'mirror-failed', { runId: state.runId, reason: String(error) }); } catch { /* keep the primary save fail-soft */ }
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function loadMirroredRunRecord(runId: string): SelfDevRunState | null {
|
|
35
|
+
try {
|
|
36
|
+
const record = JSON.parse(readFileSync(mirrorPath(runId, mirrorDir()), 'utf8')) as SelfDevRunState | null;
|
|
37
|
+
return record && record.runId === runId
|
|
38
|
+
&& typeof record.createdAt === 'number' && Number.isFinite(record.createdAt)
|
|
39
|
+
&& typeof record.updatedAt === 'number' && Number.isFinite(record.updatedAt)
|
|
40
|
+
&& Array.isArray(record.results) ? record : null;
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -27,6 +27,7 @@ import type { RunIdSource } from '../harness/harness-space.js';
|
|
|
27
27
|
import { withFileLockSync } from '../storage/file-lock.js';
|
|
28
28
|
import type { SelfDevJobResult, SelfDevGoal } from './orchestrate.js';
|
|
29
29
|
import type { SupervisorStopReason } from './run-supervisor.js';
|
|
30
|
+
import { loadMirroredRunRecord, mirrorRunRecord } from './run-record-mirror.js';
|
|
30
31
|
|
|
31
32
|
export interface SelfDevRunParticipant {
|
|
32
33
|
id: string;
|
|
@@ -151,18 +152,26 @@ export function saveSelfDevRun(state: SelfDevRunState, dir = selfDevRunsDir()):
|
|
|
151
152
|
withFileLockSync(join(dir, `${state.runId}.lock`), () => {
|
|
152
153
|
const current = loadSelfDevRunFromPath(runPath(state.runId, dir));
|
|
153
154
|
const participants = mergeParticipants(current?.participants, state.participants);
|
|
154
|
-
|
|
155
|
+
const saved = {
|
|
155
156
|
...state,
|
|
156
157
|
...(participants === undefined ? {} : { participants }),
|
|
157
158
|
...(current?.parkedResolution && !state.parkedResolution ? { parkedResolution: current.parkedResolution } : {}),
|
|
158
|
-
}
|
|
159
|
+
};
|
|
160
|
+
writeSelfDevRun(saved, dir);
|
|
161
|
+
mirrorRunRecord(saved, dir);
|
|
159
162
|
});
|
|
160
163
|
} catch { /* fail-soft — a lost checkpoint just means no resume, not a crash */ }
|
|
161
164
|
}
|
|
162
165
|
|
|
163
166
|
/** Load a run checkpoint by id, or null if absent/corrupt. */
|
|
164
167
|
export function loadSelfDevRun(runId: string, dir = selfDevRunsDir()): SelfDevRunState | null {
|
|
165
|
-
|
|
168
|
+
const primary = loadSelfDevRunFromPath(runPath(runId, dir));
|
|
169
|
+
if (primary) return primary;
|
|
170
|
+
const mirrored = loadMirroredRunRecord(runId);
|
|
171
|
+
if (mirrored) {
|
|
172
|
+
try { debug.log('self-dev.run-store', 'read-from-mirror', { runId }); } catch { /* preserve fail-soft reads */ }
|
|
173
|
+
}
|
|
174
|
+
return mirrored;
|
|
166
175
|
}
|
|
167
176
|
|
|
168
177
|
/** Add one participant without losing registrations made by another caller for this run. */
|
|
@@ -4,8 +4,9 @@ import { runIsolationHardcodeGate } from '../../scripts/ci-isolation-hardcode-ga
|
|
|
4
4
|
import { runMockModuleRestoreGate } from '../../scripts/ci-mock-module-restore-gate.js';
|
|
5
5
|
import { runModelHardcodeGate } from '../../scripts/ci-model-hardcode-gate.js';
|
|
6
6
|
import { runDaemonPortGate } from '../../scripts/ci-daemon-port-gate.js';
|
|
7
|
+
import { runPublicExportLeakGate } from '../../scripts/ci-public-export-leak-gate.js';
|
|
7
8
|
|
|
8
|
-
type GateName = 'isolation-gate' | 'mock-module-restore-gate' | 'model-hardcode-gate' | 'daemon-port-gate';
|
|
9
|
+
type GateName = 'isolation-gate' | 'mock-module-restore-gate' | 'model-hardcode-gate' | 'daemon-port-gate' | 'public-export-leak';
|
|
9
10
|
type GateOutput = { args: string[]; cwd: string; log: (line: string) => void; error: (line: string) => void };
|
|
10
11
|
type GateRunner = (out: GateOutput) => number;
|
|
11
12
|
|
|
@@ -27,6 +28,8 @@ export function runHarnessPolicyGates(input: {
|
|
|
27
28
|
['mock-module-restore-gate', input.gates?.['mock-module-restore-gate'] ?? runMockModuleRestoreGate],
|
|
28
29
|
['model-hardcode-gate', input.gates?.['model-hardcode-gate'] ?? runModelHardcodeGate],
|
|
29
30
|
['daemon-port-gate', input.gates?.['daemon-port-gate'] ?? ((out) => runDaemonPortGate({ log: out.log, error: out.error, cwd: out.cwd, args: [] }))],
|
|
31
|
+
// LEAK1 — the check release prepare fails on, before the harness lands (0.2.7: a mark in a test title).
|
|
32
|
+
['public-export-leak', input.gates?.['public-export-leak'] ?? runPublicExportLeakGate],
|
|
30
33
|
];
|
|
31
34
|
const failures: Array<{ gate: GateName; lines: string[] }> = [];
|
|
32
35
|
for (const [gate, run] of runners) {
|
|
@@ -2,12 +2,23 @@ import type { MissionOrigin } from '../autopilot/mission-origin.js';
|
|
|
2
2
|
|
|
3
3
|
export const REPORT_ORIGIN_ENV = 'ELANOUS_REPORT_ORIGIN';
|
|
4
4
|
|
|
5
|
-
const CHANNELS = new Set<MissionOrigin['channel']>(['telegram', 'pwa', 'voice', 'cli', 'api', 'tui']);
|
|
5
|
+
const CHANNELS = new Set<MissionOrigin['channel']>(['telegram', 'pwa', 'voice', 'cli', 'api', 'tui', 'discord']);
|
|
6
|
+
const SNOWFLAKE = /^\d{1,20}$/;
|
|
6
7
|
|
|
7
8
|
function isReportOrigin(value: unknown): value is MissionOrigin {
|
|
8
9
|
if (value === null || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
9
10
|
const origin = value as Record<string, unknown>;
|
|
10
11
|
if (!CHANNELS.has(origin.channel as MissionOrigin['channel'])) return false;
|
|
12
|
+
// Discord carries its own reply target (channel ⊕ optional thread snowflakes) — same shape as MissionOrigin.
|
|
13
|
+
if (origin.channel === 'discord') {
|
|
14
|
+
for (const key of Object.keys(origin)) {
|
|
15
|
+
if (!['channel', 'channelId', 'discordThreadId', 'botId', 'hitlMessageId'].includes(key)) return false;
|
|
16
|
+
}
|
|
17
|
+
return typeof origin.channelId === 'string' && SNOWFLAKE.test(origin.channelId)
|
|
18
|
+
&& (origin.discordThreadId === undefined || (typeof origin.discordThreadId === 'string' && SNOWFLAKE.test(origin.discordThreadId)))
|
|
19
|
+
&& (origin.botId === undefined || (typeof origin.botId === 'string' && origin.botId.length > 0))
|
|
20
|
+
&& (origin.hitlMessageId === undefined || Number.isSafeInteger(origin.hitlMessageId));
|
|
21
|
+
}
|
|
11
22
|
if (origin.channel === 'telegram' && (!Number.isSafeInteger(origin.chatId) || origin.chatId === 0)) return false;
|
|
12
23
|
for (const key of Object.keys(origin)) {
|
|
13
24
|
if (!['channel', 'chatId', 'botId', 'threadId', 'hitlMessageId'].includes(key)) return false;
|
|
@@ -280,7 +280,7 @@ function recordedString(entries: readonly RunLedgerEntry[], field: string): stri
|
|
|
280
280
|
return undefined;
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
-
function summarizeTerminalLedger(runId: string, entries: readonly RunLedgerEntry[]): RunLedgerSummary {
|
|
283
|
+
export function summarizeTerminalLedger(runId: string, entries: readonly RunLedgerEntry[]): RunLedgerSummary {
|
|
284
284
|
const last = entries.at(-1)?.timestamp;
|
|
285
285
|
if (!last || !Number.isFinite(Date.parse(last)) || !hasTerminalRunStatus(entries)) throw new Error('ledger changed since plan');
|
|
286
286
|
const terminator = [...entries].reverse().find((entry) => entry.event === 'human-stop' || entry.event === 'terminal'
|
package/src/session/chat.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
import { buildMemoryInjection, buildMemoryInjectionLLM } from '../memory.js';
|
|
31
31
|
import { getUserConfig } from '../user-config.js';
|
|
32
32
|
import { budgetModel } from '../llm/model-defaults.js';
|
|
33
|
+
import { explicitTurnTier } from '../model-tier/turn-tier.js';
|
|
33
34
|
import { recordTurn } from '../status/metrics.js';
|
|
34
35
|
import {
|
|
35
36
|
buildTerminalCapableTurn,
|
|
@@ -375,7 +376,18 @@ export async function runTurn(opts: RunTurnOpts): Promise<RunTurnResult> {
|
|
|
375
376
|
return trimToBudget(wire, opts.maxTokens ?? DEFAULT_TOKEN_BUDGET);
|
|
376
377
|
};
|
|
377
378
|
|
|
378
|
-
const
|
|
379
|
+
const explicitTier = opts.llmOpts?.model ? undefined : explicitTurnTier(opts.userConfig, opts.sessionId);
|
|
380
|
+
const llmOpts = explicitTier ? {
|
|
381
|
+
...(opts.llmOpts ?? {}), model: explicitTier.model,
|
|
382
|
+
...(explicitTier.reasoningEffort && !opts.llmOpts?.reasoningEffort ? { reasoningEffort: explicitTier.reasoningEffort } : {}),
|
|
383
|
+
} : opts.llmOpts;
|
|
384
|
+
if (explicitTier) {
|
|
385
|
+
debug.log('llm.tier', 'turn-tier-applied', {
|
|
386
|
+
sessionId: opts.sessionId, tier: explicitTier.tier, source: explicitTier.source, model: explicitTier.model,
|
|
387
|
+
reasoningEffort: llmOpts?.reasoningEffort ?? null,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
const provider = opts.provider ?? getProviderForConfig(opts.userConfig, llmOpts?.model);
|
|
379
391
|
let kept: LLMMessage[] = [];
|
|
380
392
|
let dropped = 0;
|
|
381
393
|
let used = 0;
|
|
@@ -394,7 +406,7 @@ export async function runTurn(opts: RunTurnOpts): Promise<RunTurnResult> {
|
|
|
394
406
|
specs: opts.tools,
|
|
395
407
|
dispatch: rawDispatchTool,
|
|
396
408
|
systemPromptParts: effectiveSystemPrompt ? [effectiveSystemPrompt] : [],
|
|
397
|
-
...(
|
|
409
|
+
...(llmOpts ? { llmOpts } : {}),
|
|
398
410
|
};
|
|
399
411
|
const terminal = hasPtyShellTool
|
|
400
412
|
? isTerminalCapableTurn(terminalBase)
|
|
@@ -409,7 +421,7 @@ export async function runTurn(opts: RunTurnOpts): Promise<RunTurnResult> {
|
|
|
409
421
|
if (terminal) {
|
|
410
422
|
effectiveSystemPrompt = terminal.systemPromptParts.join('\n\n');
|
|
411
423
|
}
|
|
412
|
-
const turnLlmOpts = terminal ? terminal.llmOpts :
|
|
424
|
+
const turnLlmOpts = terminal ? terminal.llmOpts : llmOpts;
|
|
413
425
|
({ kept, dropped, used } = assembleTurnMessages(effectiveSystemPrompt));
|
|
414
426
|
// Durable tool telemetry (추적성) — pair each call's args (onToolCall) with its
|
|
415
427
|
// result (onToolResult) and persist a role:'tool' message so later "어떤 도구
|
|
@@ -477,7 +489,7 @@ export async function runTurn(opts: RunTurnOpts): Promise<RunTurnResult> {
|
|
|
477
489
|
}
|
|
478
490
|
} else {
|
|
479
491
|
({ kept, dropped, used } = assembleTurnMessages(effectiveSystemPrompt));
|
|
480
|
-
const plainOpts = opts.signal ? { ...(
|
|
492
|
+
const plainOpts = opts.signal ? { ...(llmOpts ?? {}), signal: opts.signal } : (llmOpts ?? {});
|
|
481
493
|
const stream = provider.streamChat
|
|
482
494
|
? provider.streamChat(kept, plainOpts)
|
|
483
495
|
: wrapTextOnly(provider.chat(kept, plainOpts));
|
|
@@ -529,7 +541,7 @@ export async function runTurn(opts: RunTurnOpts): Promise<RunTurnResult> {
|
|
|
529
541
|
// status bar (CTX bar · $ · 🚀) reflects the latest numbers. Uses
|
|
530
542
|
// rough estimates — providers that expose real `usage` fields can
|
|
531
543
|
// override via opts.llmOpts in a later phase.
|
|
532
|
-
const resolvedModel =
|
|
544
|
+
const resolvedModel = llmOpts?.model ?? provider.defaultModel;
|
|
533
545
|
recordTurn({
|
|
534
546
|
model: resolvedModel,
|
|
535
547
|
estimatedPromptText: [...messages, userMsg].map(m =>
|
package/src/session/index.ts
CHANGED
|
@@ -434,12 +434,20 @@ function readIndex(root: string = sessionRoot()): SessionMeta[] {
|
|
|
434
434
|
}
|
|
435
435
|
}
|
|
436
436
|
|
|
437
|
-
|
|
437
|
+
// 여러 프로세스(`elanous ask` 동시 실행 등)가 같은 목록을 «읽고-고치고-통째로 쓰기» 한다.
|
|
438
|
+
// 쓰기 직전 잠금 안에서 디스크의 최신 목록을 다시 읽어 id 로 합치고, 임시 파일 이름은 프로세스마다 다르게 한다
|
|
439
|
+
// (10-01 MK 재현: 고정 `index.json.tmp` → 한쪽 ENOENT · 덮어쓰기로 남의 세션이 목록에서 사라짐).
|
|
440
|
+
function writeIndex(entries: SessionMeta[], root: string = sessionRoot(), removed: Iterable<string> = []): void {
|
|
438
441
|
ensureRoots(root);
|
|
439
442
|
const p = indexPath(root);
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
+
withDirLock(join(root, '.index.lock'), INDEX_LOCK_TIMEOUT_MS, () => {
|
|
444
|
+
const mine = new Set(entries.map((m) => m.id));
|
|
445
|
+
const gone = new Set(removed);
|
|
446
|
+
const peers = readIndex(root).filter((m) => !mine.has(m.id) && !gone.has(m.id));
|
|
447
|
+
const tmp = `${p}.${process.pid}.${randomUUID().slice(0, 8)}.tmp`;
|
|
448
|
+
writeFileSync(tmp, JSON.stringify([...peers, ...entries], null, 2) + '\n', 'utf-8');
|
|
449
|
+
renameSync(tmp, p);
|
|
450
|
+
});
|
|
443
451
|
}
|
|
444
452
|
|
|
445
453
|
// ── Tier 1 Phase 3 — listener primitive ──────────────────────────────
|
|
@@ -661,7 +669,7 @@ export function rewriteSessionMessages(
|
|
|
661
669
|
const body = messages
|
|
662
670
|
.map(m => JSON.stringify(m.turn_id !== undefined ? m : { ...m, turn_id: mintTurnUri() }))
|
|
663
671
|
.join('\n');
|
|
664
|
-
const tmp = file
|
|
672
|
+
const tmp = `${file}.${process.pid}.${randomUUID().slice(0, 8)}.tmp`;
|
|
665
673
|
writeFileSync(tmp, body ? body + '\n' : '', 'utf-8');
|
|
666
674
|
renameSync(tmp, file);
|
|
667
675
|
|
|
@@ -800,8 +808,13 @@ function residentSessionLockPath(root: string): string {
|
|
|
800
808
|
|
|
801
809
|
function withResidentSessionLock<T>(root: string, action: () => T): T {
|
|
802
810
|
ensureRoots(root);
|
|
803
|
-
|
|
804
|
-
|
|
811
|
+
return withDirLock(residentSessionLockPath(root), RESIDENT_SESSION_LOCK_TIMEOUT_MS, action);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
const INDEX_LOCK_TIMEOUT_MS = 10_000;
|
|
815
|
+
|
|
816
|
+
function withDirLock<T>(lock: string, timeoutMs: number, action: () => T): T {
|
|
817
|
+
const deadline = Date.now() + timeoutMs;
|
|
805
818
|
for (;;) {
|
|
806
819
|
try {
|
|
807
820
|
mkdirSync(lock);
|
|
@@ -814,7 +827,7 @@ function withResidentSessionLock<T>(root: string, action: () => T): T {
|
|
|
814
827
|
continue;
|
|
815
828
|
}
|
|
816
829
|
} catch { continue; }
|
|
817
|
-
if (Date.now() >= deadline) throw new Error(`timed out acquiring
|
|
830
|
+
if (Date.now() >= deadline) throw new Error(`timed out acquiring lock ${lock}`);
|
|
818
831
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
|
|
819
832
|
void error;
|
|
820
833
|
}
|
|
@@ -855,7 +868,7 @@ export function deleteSession(id: string, root: string = sessionRoot()): boolean
|
|
|
855
868
|
const before = idx.length;
|
|
856
869
|
const next = idx.filter(m => m.id !== id);
|
|
857
870
|
if (next.length === before) return false;
|
|
858
|
-
writeIndex(next, root);
|
|
871
|
+
writeIndex(next, root, [id]);
|
|
859
872
|
// Clear active marker if it pointed at the deleted session.
|
|
860
873
|
if (getActiveSessionId() === id) clearActiveSessionId();
|
|
861
874
|
return true;
|
|
@@ -876,7 +889,7 @@ export function deleteSessions(ids: string[], root: string = sessionRoot()): num
|
|
|
876
889
|
const idx = readIndex(root);
|
|
877
890
|
const next = idx.filter(m => !set.has(m.id));
|
|
878
891
|
const removed = idx.length - next.length;
|
|
879
|
-
if (removed > 0) writeIndex(next, root);
|
|
892
|
+
if (removed > 0) writeIndex(next, root, set);
|
|
880
893
|
const active = getActiveSessionId();
|
|
881
894
|
if (active && set.has(active)) clearActiveSessionId();
|
|
882
895
|
return removed;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { statSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
export interface SharedAgentSkillRootsOptions {
|
|
6
|
+
home?: string;
|
|
7
|
+
/** Directory-existence probe; defaults to a read-only filesystem stat. */
|
|
8
|
+
exists?: (dir: string) => boolean;
|
|
9
|
+
includeSharedAgentSkills?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Discover the shared user skill root without creating or modifying it. */
|
|
13
|
+
export function sharedAgentSkillRoots({
|
|
14
|
+
home = homedir(),
|
|
15
|
+
exists = (dir: string) => statSync(dir).isDirectory(),
|
|
16
|
+
includeSharedAgentSkills = true,
|
|
17
|
+
}: SharedAgentSkillRootsOptions): string[] {
|
|
18
|
+
if (!includeSharedAgentSkills) return [];
|
|
19
|
+
const root = join(home, '.agents', 'skills');
|
|
20
|
+
try {
|
|
21
|
+
return exists(root) ? [root] : [];
|
|
22
|
+
} catch {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { redactSecretText } from '../debug/log.js';
|
|
4
|
+
|
|
5
|
+
export type StewardStage = 'sync' | 'triage' | 'schedule' | 'report';
|
|
6
|
+
export interface FailureStreak {
|
|
7
|
+
consecutive: number;
|
|
8
|
+
lastStage: StewardStage;
|
|
9
|
+
lastReason: string;
|
|
10
|
+
lastAt: string;
|
|
11
|
+
alertedAt?: string;
|
|
12
|
+
open?: { stage: StewardStage; startedAt: string };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const stages: readonly string[] = ['sync', 'triage', 'schedule', 'report'];
|
|
16
|
+
const empty = (): FailureStreak => ({ consecutive: 0, lastStage: 'sync', lastReason: '', lastAt: '' });
|
|
17
|
+
const pathFor = (root: string): string => join(root, 'steward', 'streak.json');
|
|
18
|
+
|
|
19
|
+
export function readFailureStreak(root: string): FailureStreak {
|
|
20
|
+
try {
|
|
21
|
+
const value: unknown = JSON.parse(readFileSync(pathFor(root), 'utf8'));
|
|
22
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return empty();
|
|
23
|
+
const s = value as Record<string, unknown>;
|
|
24
|
+
if (!Number.isSafeInteger(s.consecutive) || (s.consecutive as number) < 0 ||
|
|
25
|
+
!stages.includes(s.lastStage as string) || typeof s.lastReason !== 'string' || typeof s.lastAt !== 'string' ||
|
|
26
|
+
(s.alertedAt !== undefined && typeof s.alertedAt !== 'string')) return empty();
|
|
27
|
+
if (s.open !== undefined && (!s.open || typeof s.open !== 'object' || Array.isArray(s.open) ||
|
|
28
|
+
!stages.includes((s.open as Record<string, unknown>).stage as string) ||
|
|
29
|
+
typeof (s.open as Record<string, unknown>).startedAt !== 'string')) return empty();
|
|
30
|
+
return s as unknown as FailureStreak;
|
|
31
|
+
} catch { return empty(); }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function writeFailureStreak(root: string, state: FailureStreak): void {
|
|
35
|
+
const dir = join(root, 'steward');
|
|
36
|
+
mkdirSync(dir, { recursive: true });
|
|
37
|
+
const path = pathFor(root);
|
|
38
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
39
|
+
writeFileSync(tmp, JSON.stringify(state));
|
|
40
|
+
renameSync(tmp, path);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function markStageStart(root: string, stage: StewardStage, now: Date = new Date()): FailureStreak {
|
|
44
|
+
const state = readFailureStreak(root);
|
|
45
|
+
if (state.open) {
|
|
46
|
+
state.consecutive += 1;
|
|
47
|
+
state.lastStage = state.open.stage;
|
|
48
|
+
state.lastReason = `killed or timed out: ${state.open.stage}`;
|
|
49
|
+
state.lastAt = now.toISOString();
|
|
50
|
+
}
|
|
51
|
+
state.open = { stage, startedAt: now.toISOString() };
|
|
52
|
+
writeFailureStreak(root, state);
|
|
53
|
+
return state;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function markStageEnd(root: string, stage: StewardStage, ok: boolean, reason = '', now: Date = new Date()): FailureStreak {
|
|
57
|
+
const state = readFailureStreak(root);
|
|
58
|
+
delete state.open;
|
|
59
|
+
if (ok) {
|
|
60
|
+
state.consecutive = 0;
|
|
61
|
+
delete state.alertedAt;
|
|
62
|
+
} else {
|
|
63
|
+
state.consecutive += 1;
|
|
64
|
+
}
|
|
65
|
+
state.lastStage = stage;
|
|
66
|
+
state.lastReason = ok ? '' : redactSecretText(reason);
|
|
67
|
+
state.lastAt = now.toISOString();
|
|
68
|
+
writeFailureStreak(root, state);
|
|
69
|
+
return state;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function shouldAlert(state: FailureStreak, threshold: number): boolean {
|
|
73
|
+
return state.consecutive >= threshold && !state.alertedAt;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function failureAlertText(state: FailureStreak): string {
|
|
77
|
+
return `스튜어드 연속 실패 ${state.consecutive}회 · 마지막 ${state.lastStage} · ${redactSecretText(state.lastReason).slice(0, 120)} · 로그 /tmp/elanous-loop-steward.log`;
|
|
78
|
+
}
|
|
@@ -43,3 +43,27 @@ export function recordTriageOnCards(decisions: ScheduledDecision[], issues: Tria
|
|
|
43
43
|
if (!deps.store) store.close();
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
+
|
|
47
|
+
function recordOnCard(section: 'launch' | 'outcome', issue: Pick<TriageIssue, 'identifier' | 'title'>, value: object, deps: StewardCardDeps): void {
|
|
48
|
+
const store = deps.store ?? new CardStore(deps.root);
|
|
49
|
+
try {
|
|
50
|
+
const content = redactSecrets(JSON.stringify(value));
|
|
51
|
+
const key = `${section}:${createHash('sha256').update(content).digest('hex')}`;
|
|
52
|
+
const card = store.createCard({ goalId: `linear:${issue.identifier}`, title: redactSecrets(issue.title) });
|
|
53
|
+
if (!card.sections.some(item => item.key === key)) {
|
|
54
|
+
store.appendSection(card.id, { key, owner: 'steward', content });
|
|
55
|
+
}
|
|
56
|
+
} finally {
|
|
57
|
+
if (!deps.store) store.close();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Append a launch revision to the issue card; an identical retry keeps its original section. */
|
|
62
|
+
export function recordLaunchOnCard(issue: Pick<TriageIssue, 'identifier' | 'title'>, launch: object, deps: StewardCardDeps = {}): void {
|
|
63
|
+
recordOnCard('launch', issue, launch, deps);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Append an outcome revision to the issue card without replacing earlier sections. */
|
|
67
|
+
export function recordOutcomeOnCard(issue: Pick<TriageIssue, 'identifier' | 'title'>, outcome: object, deps: StewardCardDeps = {}): void {
|
|
68
|
+
recordOnCard('outcome', issue, outcome, deps);
|
|
69
|
+
}
|