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,220 @@
|
|
|
1
|
+
import { createPublicKey, randomBytes, verify } from 'node:crypto';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import type { ServerWebSocket } from 'bun';
|
|
4
|
+
import { MAX_FRAME_BYTES } from './channel.js';
|
|
5
|
+
|
|
6
|
+
const ED25519_SPKI_PREFIX = Buffer.from('302a300506032b6570032100', 'hex');
|
|
7
|
+
const MAX_CLIENTS_PER_SERVER = 8;
|
|
8
|
+
const BYTES_PER_SECOND = 1024 * 1024;
|
|
9
|
+
const BURST_BYTES = 2 * BYTES_PER_SECOND;
|
|
10
|
+
const PING_INTERVAL_MS = 30_000;
|
|
11
|
+
const DEAD_PEER_MS = 90_000;
|
|
12
|
+
const AUTH_TIMEOUT_MS = 10_000;
|
|
13
|
+
|
|
14
|
+
type SocketData = { role: 'host' | 'client'; serverId: string; clientId?: number; challenge?: string; authenticated: boolean; lastSeen: number; tokens: number; refillAt: number; authTimer?: ReturnType<typeof setTimeout> };
|
|
15
|
+
type Socket = ServerWebSocket<SocketData>;
|
|
16
|
+
type Room = { host?: Socket; clients: Map<number, Socket>; nextId: number; bytes: number; failures: Record<string, number> };
|
|
17
|
+
|
|
18
|
+
export interface RelayServerOptions {
|
|
19
|
+
port?: number;
|
|
20
|
+
hostname?: string;
|
|
21
|
+
/** Ed25519 signing public keys (base64url raw 32 bytes), pinned by X25519-derived serverId. */
|
|
22
|
+
trustedHostKeys?: ReadonlyMap<string, string>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Load operator-provisioned pins, never keys claimed by connecting hosts. */
|
|
26
|
+
export function loadTrustedHostKeys(file: string): ReadonlyMap<string, string> {
|
|
27
|
+
const raw: unknown = JSON.parse(readFileSync(file, 'utf8'));
|
|
28
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) throw new Error('invalid relay trusted host keys');
|
|
29
|
+
const result = new Map<string, string>();
|
|
30
|
+
for (const [id, key] of Object.entries(raw)) {
|
|
31
|
+
if (!/^[a-f0-9]{64}$/.test(id) || typeof key !== 'string' || !decodeKey(key)) {
|
|
32
|
+
throw new Error('invalid relay trusted host key entry');
|
|
33
|
+
}
|
|
34
|
+
result.set(id, key);
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function challengeMessage(serverId: string, nonce: string): Uint8Array {
|
|
40
|
+
return Buffer.from(`elanous-relay-v1:${serverId}:${nonce}`, 'utf8');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function decodeKey(value: string): Buffer | undefined {
|
|
44
|
+
if (!/^[A-Za-z0-9_-]{43}$/.test(value)) return undefined;
|
|
45
|
+
const decoded = Buffer.from(value, 'base64url');
|
|
46
|
+
return decoded.length === 32 && decoded.toString('base64url') === value ? decoded : undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function encodeClientId(id: number, payload: Uint8Array): Uint8Array {
|
|
50
|
+
const result = new Uint8Array(payload.length + 4);
|
|
51
|
+
new DataView(result.buffer).setUint32(0, id, false);
|
|
52
|
+
result.set(payload, 4);
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The relay never parses or logs encrypted channel payloads. Host binary frames carry a
|
|
57
|
+
* four-byte big-endian clientId prefix; phone binary frames are raw ciphertext. */
|
|
58
|
+
export function startRelayServer(options: RelayServerOptions = {}) {
|
|
59
|
+
if (!options.trustedHostKeys?.size) throw new Error('relay trusted host keys required');
|
|
60
|
+
const trustedHostKeys = new Map(options.trustedHostKeys);
|
|
61
|
+
for (const [id, key] of trustedHostKeys) {
|
|
62
|
+
if (!/^[a-f0-9]{64}$/.test(id) || !decodeKey(key)) throw new Error('invalid relay trusted host key entry');
|
|
63
|
+
}
|
|
64
|
+
const rooms = new Map<string, Room>();
|
|
65
|
+
const sockets = new Set<Socket>();
|
|
66
|
+
const roomFor = (id: string): Room => {
|
|
67
|
+
let room = rooms.get(id);
|
|
68
|
+
if (!room) { room = { clients: new Map(), nextId: 1, bytes: 0, failures: {} }; rooms.set(id, room); }
|
|
69
|
+
return room;
|
|
70
|
+
};
|
|
71
|
+
const fail = (room: Room, reason: string) => { room.failures[reason] = (room.failures[reason] ?? 0) + 1; };
|
|
72
|
+
const sendControl = (ws: Socket, value: object) => { if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(value)); };
|
|
73
|
+
const drop = (ws: Socket) => {
|
|
74
|
+
const data = ws.data;
|
|
75
|
+
if (data.authTimer) clearTimeout(data.authTimer);
|
|
76
|
+
sockets.delete(ws);
|
|
77
|
+
const room = rooms.get(data.serverId);
|
|
78
|
+
if (!room) return;
|
|
79
|
+
if (!data.authenticated) {
|
|
80
|
+
if (!room.host && room.clients.size === 0 &&
|
|
81
|
+
!Array.from(sockets).some(peer => peer.data.serverId === data.serverId)) rooms.delete(data.serverId);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (data.role === 'host' && room.host === ws) {
|
|
85
|
+
room.host = undefined;
|
|
86
|
+
const clients = [...room.clients.values()];
|
|
87
|
+
room.clients.clear();
|
|
88
|
+
for (const client of clients) client.close(1012, 'host disconnected');
|
|
89
|
+
} else if (data.role === 'client' && data.clientId !== undefined && room.clients.get(data.clientId) === ws) {
|
|
90
|
+
room.clients.delete(data.clientId);
|
|
91
|
+
if (room.host) sendControl(room.host, { type: 'client-disconnected', clientId: data.clientId });
|
|
92
|
+
}
|
|
93
|
+
if (!room.host && room.clients.size === 0 &&
|
|
94
|
+
!Array.from(sockets).some(peer => peer.data.serverId === data.serverId)) rooms.delete(data.serverId);
|
|
95
|
+
};
|
|
96
|
+
const capacity = (ws: Socket, length: number): boolean => {
|
|
97
|
+
const data = ws.data;
|
|
98
|
+
const now = Date.now();
|
|
99
|
+
data.tokens = Math.min(BURST_BYTES, data.tokens + (now - data.refillAt) * BYTES_PER_SECOND / 1000);
|
|
100
|
+
data.refillAt = now;
|
|
101
|
+
if (length > data.tokens) return false;
|
|
102
|
+
data.tokens -= length;
|
|
103
|
+
return true;
|
|
104
|
+
};
|
|
105
|
+
const server = Bun.serve<SocketData>({
|
|
106
|
+
hostname: options.hostname ?? '127.0.0.1', port: options.port ?? 0,
|
|
107
|
+
fetch(req, server) {
|
|
108
|
+
const url = new URL(req.url);
|
|
109
|
+
if (url.pathname !== '/host' && url.pathname !== '/client') return new Response('not found', { status: 404 });
|
|
110
|
+
const serverId = url.searchParams.get('serverId');
|
|
111
|
+
if (!serverId || !/^[a-f0-9]{64}$/.test(serverId)) return new Response('invalid serverId', { status: 400 });
|
|
112
|
+
if (!trustedHostKeys.has(serverId)) return new Response('unknown serverId', { status: 403 });
|
|
113
|
+
if (req.headers.get('upgrade')?.toLowerCase() !== 'websocket') return new Response('upgrade required', { status: 426 });
|
|
114
|
+
const role = url.pathname === '/host' ? 'host' : 'client';
|
|
115
|
+
const now = Date.now();
|
|
116
|
+
if (!server.upgrade(req, { data: { role, serverId, authenticated: false, lastSeen: now, tokens: BURST_BYTES, refillAt: now } })) {
|
|
117
|
+
return new Response('upgrade required', { status: 426 });
|
|
118
|
+
}
|
|
119
|
+
return undefined;
|
|
120
|
+
},
|
|
121
|
+
websocket: {
|
|
122
|
+
maxPayloadLength: MAX_FRAME_BYTES + 4,
|
|
123
|
+
open(ws) {
|
|
124
|
+
sockets.add(ws);
|
|
125
|
+
const data = ws.data;
|
|
126
|
+
const room = roomFor(data.serverId);
|
|
127
|
+
if (data.role === 'host') {
|
|
128
|
+
if (room.host) { fail(room, 'duplicate-host'); ws.close(1008, 'host already connected'); return; }
|
|
129
|
+
data.challenge = randomBytes(32).toString('base64url');
|
|
130
|
+
sendControl(ws, { type: 'challenge', nonce: data.challenge });
|
|
131
|
+
} else {
|
|
132
|
+
if (!room.host || room.clients.size >= MAX_CLIENTS_PER_SERVER) {
|
|
133
|
+
fail(room, !room.host ? 'host-offline' : 'client-limit');
|
|
134
|
+
ws.close(1008, !room.host ? 'host offline' : 'client limit reached');
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const id = room.nextId++;
|
|
138
|
+
data.clientId = id;
|
|
139
|
+
data.authenticated = true;
|
|
140
|
+
room.clients.set(id, ws);
|
|
141
|
+
sendControl(ws, { type: 'connected', clientId: id });
|
|
142
|
+
sendControl(room.host, { type: 'client-connected', clientId: id });
|
|
143
|
+
}
|
|
144
|
+
if (!data.authenticated) data.authTimer = setTimeout(() => ws.close(1008, 'authentication timeout'), AUTH_TIMEOUT_MS);
|
|
145
|
+
},
|
|
146
|
+
message(ws, message) {
|
|
147
|
+
const data = ws.data;
|
|
148
|
+
const room = rooms.get(data.serverId);
|
|
149
|
+
if (!room) { ws.close(1008, 'room missing'); return; }
|
|
150
|
+
data.lastSeen = Date.now();
|
|
151
|
+
if (!data.authenticated) {
|
|
152
|
+
if (data.role !== 'host' || typeof message !== 'string') { fail(room, 'invalid-auth'); ws.close(1008, 'authentication required'); return; }
|
|
153
|
+
let auth: unknown;
|
|
154
|
+
try { auth = JSON.parse(message); } catch { /* reject below */ }
|
|
155
|
+
const value = auth as { type?: unknown; publicKey?: unknown; signature?: unknown } | undefined;
|
|
156
|
+
const key = typeof value?.publicKey === 'string' ? decodeKey(value.publicKey) : undefined;
|
|
157
|
+
const signature = typeof value?.signature === 'string' && /^[A-Za-z0-9_-]{86}$/.test(value.signature)
|
|
158
|
+
? Buffer.from(value.signature, 'base64url') : undefined;
|
|
159
|
+
const expected = trustedHostKeys.get(data.serverId);
|
|
160
|
+
let valid = false;
|
|
161
|
+
if (value?.type === 'auth' && key && signature?.length === 64 && data.challenge &&
|
|
162
|
+
value.publicKey === expected) {
|
|
163
|
+
try {
|
|
164
|
+
valid = verify(null, challengeMessage(data.serverId, data.challenge),
|
|
165
|
+
createPublicKey({ key: Buffer.concat([ED25519_SPKI_PREFIX, key]), format: 'der', type: 'spki' }), signature);
|
|
166
|
+
} catch { /* invalid public key */ }
|
|
167
|
+
}
|
|
168
|
+
if (!valid || (room.host && room.host !== ws)) { fail(room, 'invalid-auth'); ws.close(1008, 'invalid host signature'); return; }
|
|
169
|
+
data.authenticated = true;
|
|
170
|
+
data.challenge = undefined;
|
|
171
|
+
if (data.authTimer) clearTimeout(data.authTimer);
|
|
172
|
+
room.host = ws;
|
|
173
|
+
sendControl(ws, { type: 'authenticated' });
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (typeof message === 'string') { fail(room, 'plaintext-data'); ws.close(1003, 'binary ciphertext required'); return; }
|
|
177
|
+
const bytes = new Uint8Array(message);
|
|
178
|
+
if (bytes.length > MAX_FRAME_BYTES + (data.role === 'host' ? 4 : 0) || bytes.length < (data.role === 'host' ? 5 : 1)) {
|
|
179
|
+
fail(room, 'invalid-frame'); ws.close(1009, 'invalid frame size'); return;
|
|
180
|
+
}
|
|
181
|
+
if (!capacity(ws, bytes.length)) { fail(room, 'rate-limit'); ws.close(1008, 'rate limit exceeded'); return; }
|
|
182
|
+
if (data.role === 'client') {
|
|
183
|
+
if (!room.host || data.clientId === undefined) { ws.close(1012, 'host offline'); return; }
|
|
184
|
+
room.bytes += bytes.length;
|
|
185
|
+
room.host.send(encodeClientId(data.clientId, bytes));
|
|
186
|
+
} else {
|
|
187
|
+
if (room.host !== ws) { ws.close(1008, 'host replaced'); return; }
|
|
188
|
+
const clientId = new DataView(bytes.buffer, bytes.byteOffset, 4).getUint32(0, false);
|
|
189
|
+
const client = room.clients.get(clientId);
|
|
190
|
+
if (!client) { fail(room, 'unknown-client'); return; }
|
|
191
|
+
room.bytes += bytes.length - 4;
|
|
192
|
+
client.send(bytes.subarray(4));
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
pong(ws) { ws.data.lastSeen = Date.now(); },
|
|
196
|
+
close(ws) { drop(ws); },
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
const heartbeat = setInterval(() => {
|
|
200
|
+
for (const ws of sockets) {
|
|
201
|
+
if (Date.now() - ws.data.lastSeen > DEAD_PEER_MS) ws.close(1001, 'heartbeat timeout');
|
|
202
|
+
else ws.ping();
|
|
203
|
+
}
|
|
204
|
+
}, PING_INTERVAL_MS);
|
|
205
|
+
heartbeat.unref();
|
|
206
|
+
return {
|
|
207
|
+
server,
|
|
208
|
+
get port() { return server.port; },
|
|
209
|
+
stats(serverId: string) {
|
|
210
|
+
const room = rooms.get(serverId);
|
|
211
|
+
return room ? { host: !!room.host, clients: room.clients.size, bytes: room.bytes, failures: { ...room.failures } } :
|
|
212
|
+
{ host: false, clients: 0, bytes: 0, failures: {} as Record<string, number> };
|
|
213
|
+
},
|
|
214
|
+
stop() {
|
|
215
|
+
clearInterval(heartbeat);
|
|
216
|
+
for (const ws of sockets) ws.close(1001, 'relay stopping');
|
|
217
|
+
server.stop(true);
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
}
|
|
@@ -4,9 +4,12 @@ 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';
|
|
9
10
|
export type ChecklistDisposition = 'move' | 'known-issue' | 'block';
|
|
11
|
+
/** `screen` = a five-surface feature cell — its evidence carries a `짝:` line (MANUAL-five-surface-parity §A). */
|
|
12
|
+
export type ChecklistKind = 'screen';
|
|
10
13
|
export interface ChecklistItem {
|
|
11
14
|
id: string;
|
|
12
15
|
title: string;
|
|
@@ -14,6 +17,7 @@ export interface ChecklistItem {
|
|
|
14
17
|
owner?: string;
|
|
15
18
|
evidence?: string;
|
|
16
19
|
disposition?: ChecklistDisposition;
|
|
20
|
+
kind?: ChecklistKind;
|
|
17
21
|
updatedAt: string;
|
|
18
22
|
updatedBy: string;
|
|
19
23
|
}
|
|
@@ -54,7 +58,7 @@ function releasedVersion(): string {
|
|
|
54
58
|
}
|
|
55
59
|
|
|
56
60
|
function pathFor(v: string): string {
|
|
57
|
-
if (!/^\d+\.\d+\.\d+(?:-(?:rc|alpha|beta)\.\d+)?$/.test(v)) throw new
|
|
61
|
+
if (!/^\d+\.\d+\.\d+(?:-(?:rc|alpha|beta)\.\d+)?$/.test(v)) throw new CliUserError(`체크리스트 판이 아니다: ${v}`);
|
|
58
62
|
return join(releaseLedgerRoot(), 'release', v, 'checklist.json');
|
|
59
63
|
}
|
|
60
64
|
|
|
@@ -109,27 +113,29 @@ function change(data: Checklist, id: string, field: string, from: unknown, to: u
|
|
|
109
113
|
|
|
110
114
|
function refresh(data: Checklist): void { data.released = releasedVersion(); data.dev = devVersion(); }
|
|
111
115
|
|
|
112
|
-
export function addItem(v: string, input: { id: string; title: string; owner?: string }): Checklist {
|
|
113
|
-
if (!input.id.trim()) throw new
|
|
114
|
-
if (!input.title.trim()) throw new
|
|
116
|
+
export function addItem(v: string, input: { id: string; title: string; owner?: string; kind?: ChecklistKind }): Checklist {
|
|
117
|
+
if (!input.id.trim()) throw new CliUserError('칸 id 가 비었다');
|
|
118
|
+
if (!input.title.trim()) throw new CliUserError('칸 제목이 비었다');
|
|
119
|
+
if (input.kind !== undefined && input.kind !== 'screen') throw new CliUserError(`잘못된 종류: ${input.kind}`, 'screen');
|
|
115
120
|
return mutate(v, (data) => {
|
|
116
|
-
if (data.items.some((item) => item.id === input.id)) throw new
|
|
121
|
+
if (data.items.some((item) => item.id === input.id)) throw new CliUserError(`이미 있는 칸: ${input.id}`, 'set <id> 로 고친다');
|
|
117
122
|
const by = process.env.ELANOUS_TRACK || 'cli';
|
|
118
123
|
const at = new Date().toISOString();
|
|
119
|
-
const item: ChecklistItem = { id: input.id, title: input.title, status: 'yellow', ...(input.owner !== undefined ? { owner: input.owner } : {}), updatedAt: at, updatedBy: by };
|
|
124
|
+
const item: ChecklistItem = { id: input.id, title: input.title, status: 'yellow', ...(input.owner !== undefined ? { owner: input.owner } : {}), ...(input.kind !== undefined ? { kind: input.kind } : {}), updatedAt: at, updatedBy: by };
|
|
120
125
|
data.items.push(item);
|
|
121
126
|
change(data, item.id, 'add', null, item, by, at);
|
|
122
127
|
return true;
|
|
123
128
|
});
|
|
124
129
|
}
|
|
125
130
|
|
|
126
|
-
export function setItem(v: string, id: string, patch: { status?: ChecklistStatus; evidence?: string; owner?: string; disposition?: ChecklistDisposition }, by: string): Checklist {
|
|
131
|
+
export function setItem(v: string, id: string, patch: { status?: ChecklistStatus; evidence?: string; owner?: string; disposition?: ChecklistDisposition; kind?: ChecklistKind }, by: string): Checklist {
|
|
127
132
|
return mutate(v, (data) => {
|
|
128
133
|
const item = data.items.find((i) => i.id === id);
|
|
129
|
-
if (!item) throw new
|
|
130
|
-
if (patch.status !== undefined && !['green', 'yellow', 'red', 'done'].includes(patch.status)) throw new
|
|
131
|
-
if (patch.disposition !== undefined && !['move', 'known-issue', 'block'].includes(patch.disposition)) throw new
|
|
132
|
-
|
|
134
|
+
if (!item) throw new CliUserError(`없는 칸: ${id}`, 'list 로 칸 목록을 본다');
|
|
135
|
+
if (patch.status !== undefined && !['green', 'yellow', 'red', 'done'].includes(patch.status)) throw new CliUserError(`잘못된 상태: ${patch.status}`);
|
|
136
|
+
if (patch.disposition !== undefined && !['move', 'known-issue', 'block'].includes(patch.disposition)) throw new CliUserError(`잘못된 처분: ${patch.disposition}`);
|
|
137
|
+
if (patch.kind !== undefined && patch.kind !== 'screen') throw new CliUserError(`잘못된 종류: ${patch.kind}`, 'screen');
|
|
138
|
+
const fields = (['evidence', 'owner', 'status', 'disposition', 'kind'] as const).filter((field) => patch[field] !== undefined && patch[field] !== item[field]);
|
|
133
139
|
if (fields.length === 0) return false;
|
|
134
140
|
const at = new Date().toISOString();
|
|
135
141
|
for (const field of fields) {
|
|
@@ -147,7 +153,7 @@ export function setItem(v: string, id: string, patch: { status?: ChecklistStatus
|
|
|
147
153
|
export function removeItem(v: string, id: string, by: string): Checklist {
|
|
148
154
|
return mutate(v, (data) => {
|
|
149
155
|
const index = data.items.findIndex((i) => i.id === id);
|
|
150
|
-
if (index < 0) throw new
|
|
156
|
+
if (index < 0) throw new CliUserError(`없는 칸: ${id}`, 'list 로 칸 목록을 본다');
|
|
151
157
|
const at = new Date().toISOString();
|
|
152
158
|
const [item] = data.items.splice(index, 1);
|
|
153
159
|
change(data, id, 'remove', item, null, by, at);
|
|
@@ -179,11 +185,29 @@ export interface ChecklistGate {
|
|
|
179
185
|
blocked: string[];
|
|
180
186
|
moved: string[];
|
|
181
187
|
knownIssues: Array<{ id: string; title: string; evidence: string }>;
|
|
188
|
+
/** Screen cells whose `짝:` line is missing or has an untracked ⏳ — a warning; it never changes `ok`. */
|
|
189
|
+
parity?: Array<{ id: string; why: string }>;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const PARITY_LINE = /짝:\s*PWA\s*(.+?)\s*·\s*데스크톱\s*(.+?)\s*·\s*폴드\s*(.+?)\s*·\s*아이폰\s*(.+?)\s*·\s*아이패드\s*(.+?)\s*$/m;
|
|
193
|
+
const PARITY_SURFACES = ['PWA', '데스크톱', '폴드', '아이폰', '아이패드'] as const;
|
|
194
|
+
|
|
195
|
+
/** Why a screen cell's evidence fails the five-surface rule, or null when it passes. */
|
|
196
|
+
export function parityGap(evidence: string | undefined): string | null {
|
|
197
|
+
if (!evidence || !/짝:/.test(evidence)) return '근거에 짝: 줄이 없다';
|
|
198
|
+
const match = PARITY_LINE.exec(evidence);
|
|
199
|
+
if (!match) return '짝: 줄에 다섯 열(PWA · 데스크톱 · 폴드 · 아이폰 · 아이패드)이 다 없다';
|
|
200
|
+
const untracked = PARITY_SURFACES.filter((_, i) => match[i + 1]!.includes('⏳') && !/\(칸 /.test(match[i + 1]!));
|
|
201
|
+
return untracked.length ? `⏳ 에 (칸 …) 번호가 없다: ${untracked.join(', ')}` : null;
|
|
182
202
|
}
|
|
183
203
|
|
|
184
204
|
export function checklistGate(v: string): ChecklistGate {
|
|
185
|
-
const result: ChecklistGate = { ok: true, red: [], undecided: [], blocked: [], moved: [], knownIssues: [] };
|
|
205
|
+
const result: ChecklistGate = { ok: true, red: [], undecided: [], blocked: [], moved: [], knownIssues: [], parity: [] };
|
|
186
206
|
for (const item of listChecklist(v).items) {
|
|
207
|
+
if (item.kind === 'screen') {
|
|
208
|
+
const why = parityGap(item.evidence);
|
|
209
|
+
if (why) result.parity!.push({ id: item.id, why });
|
|
210
|
+
}
|
|
187
211
|
if (item.status === 'red') result.red.push(item.id);
|
|
188
212
|
if (item.status !== 'yellow') continue;
|
|
189
213
|
if (item.disposition === 'block') result.blocked.push(item.id);
|
|
@@ -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
|
}
|
|
@@ -987,6 +987,23 @@ async function deleteLabelledJobs(runId: string, selectedTargets: readonly { con
|
|
|
987
987
|
return counts.reduce((sum, count) => sum + count, 0);
|
|
988
988
|
}
|
|
989
989
|
|
|
990
|
+
/** `self-dev-runs/<runId>/pid.json` — what `harness stop <runId>` reads (start time guards PID reuse).
|
|
991
|
+
* HS1: written as soon as the run ID exists, not only when the jobs start — pod planning and image sync can
|
|
992
|
+
* take minutes before that, and a stop in that window found «no process» (10-01 · three runs). */
|
|
993
|
+
export function writeRunPidRecord(runId: string, runsDir: string = selfDevRunsDir()): string {
|
|
994
|
+
const pidPath = join(runsDir, runId, 'pid.json');
|
|
995
|
+
mkdirSync(join(runsDir, runId), { recursive: true });
|
|
996
|
+
const temporary = `${pidPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
997
|
+
const fd = openSync(temporary, 'wx', 0o600);
|
|
998
|
+
try {
|
|
999
|
+
try {
|
|
1000
|
+
writeFileSync(fd, JSON.stringify({ pid: process.pid, startedAt: Date.now() - Math.floor(process.uptime() * 1000), argv0: process.argv0 }));
|
|
1001
|
+
} finally { closeSync(fd); }
|
|
1002
|
+
renameSync(temporary, pidPath);
|
|
1003
|
+
} catch (error) { unlinkSync(temporary); throw error; }
|
|
1004
|
+
return pidPath;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
990
1007
|
/**
|
|
991
1008
|
* Run N self-dev jobs in parallel under the TOX dispatcher and resolve
|
|
992
1009
|
* once every job reaches a terminal status.
|
|
@@ -1419,17 +1436,7 @@ export function orchestrateSelfDev(opts: OrchestrateSelfDevOptions): Promise<Sel
|
|
|
1419
1436
|
signals.on('SIGTERM', onTerm);
|
|
1420
1437
|
signals.on('SIGINT', onInt);
|
|
1421
1438
|
try {
|
|
1422
|
-
if (pidPath)
|
|
1423
|
-
mkdirSync(join(opts.runsDir ?? selfDevRunsDir(), runId!), { recursive: true });
|
|
1424
|
-
const temporary = `${pidPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
1425
|
-
const fd = openSync(temporary, 'wx', 0o600);
|
|
1426
|
-
try {
|
|
1427
|
-
try {
|
|
1428
|
-
writeFileSync(fd, JSON.stringify({ pid: process.pid, startedAt: Date.now() - Math.floor(process.uptime() * 1000), argv0: process.argv0 }));
|
|
1429
|
-
} finally { closeSync(fd); }
|
|
1430
|
-
renameSync(temporary, pidPath);
|
|
1431
|
-
} catch (error) { unlinkSync(temporary); throw error; }
|
|
1432
|
-
}
|
|
1439
|
+
if (pidPath) writeRunPidRecord(runId!, opts.runsDir ?? selfDevRunsDir());
|
|
1433
1440
|
} catch (error) { cleanup(); reject(error); return; }
|
|
1434
1441
|
|
|
1435
1442
|
// Current full result set: resumed-done goals + each running task's
|
|
@@ -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;
|
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 =>
|