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
|
@@ -3,7 +3,9 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { debug } from '../../debug/log.js';
|
|
4
4
|
import { decideGraphApproval, lastJsonObject, type GraphRunState } from '../../graph-runner/runner.js';
|
|
5
5
|
import { effectiveInstanceRoot } from '../../instance/resolve.js';
|
|
6
|
+
import { getElanousConfigDir } from '../../elanous-config-dir.js';
|
|
6
7
|
import { jsonResponse } from './json-response.js';
|
|
8
|
+
import { applyFeedEdit, feedFolderFor, isFeedPreviewMessage, mediaContentType, readFeedDraft, resolveFeedMedia, writeFeedDraft, type FeedDraft } from './graph-approvals-feed.js';
|
|
7
9
|
|
|
8
10
|
export const GRAPH_APPROVALS_PATH = '/v1/graph-approvals';
|
|
9
11
|
const SEGMENT = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
@@ -16,15 +18,66 @@ interface GraphApprovalItem {
|
|
|
16
18
|
since: string;
|
|
17
19
|
path: string[];
|
|
18
20
|
recent: Array<{ nodeId: string; ok: boolean; outcome?: string; summary?: string }>;
|
|
21
|
+
/** EV10d — present only for a `{"kind":"feed-preview"}` approval whose run folder holds a valid draft. */
|
|
22
|
+
feed?: Omit<FeedDraft, 'folder'>;
|
|
19
23
|
}
|
|
20
24
|
|
|
25
|
+
/** One decision this daemon applied through `POST /v1/graph-approvals/:graphId/:runId`. */
|
|
26
|
+
interface GraphApprovalDecided {
|
|
27
|
+
graphId: string;
|
|
28
|
+
runId: string;
|
|
29
|
+
nodeId: string;
|
|
30
|
+
decision: 'approved' | 'rejected';
|
|
31
|
+
decidedAt: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const GRAPH_DECISION_RING_CAPACITY = 100;
|
|
35
|
+
export const GRAPH_DECISION_RING_MAX_AGE_MS = 30 * 60 * 1000;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Recent decisions, in memory. The undecided list drops a run the moment it is decided, so
|
|
39
|
+
* another device (Fold ↔ iPhone) could only see «gone» — this ring lets it say «승인됨/거절됨».
|
|
40
|
+
* ⚠️ In-memory: a daemon restart forgets it, and a decision made outside this process
|
|
41
|
+
* (`elanous graph approve` CLI) is not recorded — clients keep a «decided elsewhere» fallback.
|
|
42
|
+
*/
|
|
43
|
+
export function createGraphDecisionRing(opts: { capacity?: number; maxAgeMs?: number; now?: () => number } = {}) {
|
|
44
|
+
const capacity = opts.capacity ?? GRAPH_DECISION_RING_CAPACITY;
|
|
45
|
+
const maxAgeMs = opts.maxAgeMs ?? GRAPH_DECISION_RING_MAX_AGE_MS;
|
|
46
|
+
const now = opts.now ?? Date.now;
|
|
47
|
+
let entries: Array<{ atMs: number; record: GraphApprovalDecided }> = [];
|
|
48
|
+
const prune = () => {
|
|
49
|
+
const cutoff = now() - maxAgeMs;
|
|
50
|
+
entries = entries.filter((entry) => entry.atMs > cutoff).slice(-capacity);
|
|
51
|
+
};
|
|
52
|
+
return {
|
|
53
|
+
record(decision: Omit<GraphApprovalDecided, 'decidedAt'>): void {
|
|
54
|
+
const atMs = now();
|
|
55
|
+
entries.push({ atMs, record: { ...decision, decidedAt: new Date(atMs).toISOString() } });
|
|
56
|
+
prune();
|
|
57
|
+
},
|
|
58
|
+
/** Newest first, pruned by age and capacity. */
|
|
59
|
+
list(): GraphApprovalDecided[] {
|
|
60
|
+
prune();
|
|
61
|
+
return entries.map((entry) => entry.record).reverse();
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type GraphDecisionRing = ReturnType<typeof createGraphDecisionRing>;
|
|
67
|
+
|
|
68
|
+
const defaultDecisionRing = createGraphDecisionRing();
|
|
69
|
+
|
|
21
70
|
interface GraphApprovalsDeps {
|
|
71
|
+
/** Injected in tests; production shares one per-process ring. */
|
|
72
|
+
decisions?: GraphDecisionRing;
|
|
22
73
|
authorize?: (request: Request) => boolean;
|
|
23
74
|
/** Credential the request carries, checked independently of same-origin (`bearerCredential`).
|
|
24
75
|
* A decision needs a bearer token: same-origin alone is not trusted because behind
|
|
25
76
|
* `tailscale serve` every tailnet peer (Pod included) arrives from loopback. */
|
|
26
77
|
authReason?: (request: Request) => string | undefined;
|
|
27
78
|
root?: string;
|
|
79
|
+
/** Config dir whose `field/<slug>` folders may hold feed drafts (EV10d). */
|
|
80
|
+
configDir?: string;
|
|
28
81
|
}
|
|
29
82
|
|
|
30
83
|
const DECISION_AUTH_REASONS = new Set(['bearer-match', 'temp-token', 'noauth']);
|
|
@@ -49,7 +102,16 @@ function undecided(state: GraphRunState, root: string): boolean {
|
|
|
49
102
|
&& Array.isArray(state.path) && !existsSync(join(root, 'graph-runs', state.graphId, `${state.runId}.json.${state.path.length}.decision.json`));
|
|
50
103
|
}
|
|
51
104
|
|
|
52
|
-
function
|
|
105
|
+
function feedFor(state: GraphRunState, configDir: string): Omit<FeedDraft, 'folder'> | undefined {
|
|
106
|
+
if (!isFeedPreviewMessage(state.pending?.message ?? '')) return undefined;
|
|
107
|
+
const folder = feedFolderFor(state, configDir);
|
|
108
|
+
const draft = folder ? readFeedDraft(folder) : null;
|
|
109
|
+
if (!draft) return undefined;
|
|
110
|
+
const { folder: _folder, ...rest } = draft;
|
|
111
|
+
return rest;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function list(root: string, configDir: string): GraphApprovalItem[] {
|
|
53
115
|
const dir = join(root, 'graph-runs');
|
|
54
116
|
let graphs: Dirent[];
|
|
55
117
|
try { graphs = readdirSync(dir, { withFileTypes: true }); }
|
|
@@ -66,6 +128,7 @@ function list(root: string): GraphApprovalItem[] {
|
|
|
66
128
|
const state = readRun(root, graph.name, runId);
|
|
67
129
|
if (!state || !undecided(state, root) || !Array.isArray(state.nodes)) continue;
|
|
68
130
|
const pending = state.pending!;
|
|
131
|
+
const feed = feedFor(state, configDir);
|
|
69
132
|
items.push({
|
|
70
133
|
graphId: state.graphId, runId: state.runId, nodeId: pending.nodeId, message: pending.message,
|
|
71
134
|
since: pending.since, path: state.path,
|
|
@@ -77,6 +140,7 @@ function list(root: string): GraphApprovalItem[] {
|
|
|
77
140
|
...(typeof output?.summary === 'string' ? { summary: output.summary } : {}),
|
|
78
141
|
};
|
|
79
142
|
}),
|
|
143
|
+
...(feed ? { feed } : {}),
|
|
80
144
|
});
|
|
81
145
|
}
|
|
82
146
|
}
|
|
@@ -84,13 +148,17 @@ function list(root: string): GraphApprovalItem[] {
|
|
|
84
148
|
}
|
|
85
149
|
|
|
86
150
|
export async function handleGraphApprovals(req: Request, deps: GraphApprovalsDeps = {}): Promise<Response> {
|
|
87
|
-
const
|
|
151
|
+
const url = new URL(req.url);
|
|
152
|
+
const pathname = url.pathname;
|
|
153
|
+
const sub = /^\/v1\/graph-approvals\/([^/]+)\/([^/]+)\/(feed-draft|media)$/.exec(pathname);
|
|
88
154
|
const match = /^\/v1\/graph-approvals\/([^/]+)\/([^/]+)$/.exec(pathname);
|
|
89
|
-
if (pathname !== GRAPH_APPROVALS_PATH && !match) return jsonResponse({ error: 'not-found' }, 404);
|
|
155
|
+
if (pathname !== GRAPH_APPROVALS_PATH && !match && !sub) return jsonResponse({ error: 'not-found' }, 404);
|
|
90
156
|
if (!deps.authorize?.(req)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
157
|
+
if (sub) return handleFeed(req, url, sub[1]!, sub[2]!, sub[3] as 'feed-draft' | 'media', deps);
|
|
91
158
|
if (pathname === GRAPH_APPROVALS_PATH) {
|
|
92
159
|
if (req.method !== 'GET') return jsonResponse({ error: 'method-not-allowed' }, 405);
|
|
93
|
-
|
|
160
|
+
// ⭐ `items` is unchanged; `decided` is additive (newest first) for other devices' cards.
|
|
161
|
+
return jsonResponse({ items: list(deps.root ?? effectiveInstanceRoot(), deps.configDir ?? getElanousConfigDir()), decided: (deps.decisions ?? defaultDecisionRing).list() });
|
|
94
162
|
}
|
|
95
163
|
if (req.method !== 'POST') return jsonResponse({ error: 'method-not-allowed' }, 405);
|
|
96
164
|
const authReason = deps.authReason?.(req);
|
|
@@ -120,6 +188,54 @@ export async function handleGraphApprovals(req: Request, deps: GraphApprovalsDep
|
|
|
120
188
|
if (error instanceof Error && error.message.startsWith('run is not awaiting an undecided approval:')) return jsonResponse({ error: 'already-decided' }, 409);
|
|
121
189
|
throw error;
|
|
122
190
|
}
|
|
191
|
+
(deps.decisions ?? defaultDecisionRing).record({ graphId, runId, nodeId, decision });
|
|
123
192
|
debug.log('approvals.graph', 'decided', { graphId, runId, nodeId, decision, by: `pwa:${authReason}` });
|
|
124
193
|
return jsonResponse({ graphId, runId, decision });
|
|
125
194
|
}
|
|
195
|
+
|
|
196
|
+
/** EV10d — `PUT …/:graphId/:runId/feed-draft` (사람 수정 저장) · `GET …/:graphId/:runId/media?path=` (초안 미디어). */
|
|
197
|
+
async function handleFeed(req: Request, url: URL, rawGraphId: string, rawRunId: string, kind: 'feed-draft' | 'media', deps: GraphApprovalsDeps): Promise<Response> {
|
|
198
|
+
let graphId: string;
|
|
199
|
+
let runId: string;
|
|
200
|
+
try { graphId = decodeURIComponent(rawGraphId); runId = decodeURIComponent(rawRunId); }
|
|
201
|
+
catch { return jsonResponse({ error: 'not-found' }, 404); }
|
|
202
|
+
if (!validSegment(graphId) || !validSegment(runId)) return jsonResponse({ error: 'not-found' }, 404);
|
|
203
|
+
const root = deps.root ?? effectiveInstanceRoot();
|
|
204
|
+
const state = readRun(root, graphId, runId);
|
|
205
|
+
const folder = state && isFeedPreviewMessage(state.pending?.message ?? '') ? feedFolderFor(state, deps.configDir ?? getElanousConfigDir()) : null;
|
|
206
|
+
if (!state || !folder) {
|
|
207
|
+
debug.log('approvals.feed', 'refused', { graphId, reason: 'not-feed' });
|
|
208
|
+
return jsonResponse({ error: 'not-found' }, 404);
|
|
209
|
+
}
|
|
210
|
+
if (kind === 'media') {
|
|
211
|
+
if (req.method !== 'GET') return jsonResponse({ error: 'method-not-allowed' }, 405);
|
|
212
|
+
const file = resolveFeedMedia(folder, url.searchParams.get('path') ?? '');
|
|
213
|
+
if (!file) {
|
|
214
|
+
debug.log('approvals.feed', 'refused', { graphId, reason: 'media-path' });
|
|
215
|
+
return jsonResponse({ error: 'not-found' }, 404);
|
|
216
|
+
}
|
|
217
|
+
debug.log('approvals.feed', 'media-served', { graphId });
|
|
218
|
+
return new Response(Bun.file(file), { headers: { 'content-type': mediaContentType(file), 'cache-control': 'private, max-age=60' } });
|
|
219
|
+
}
|
|
220
|
+
if (req.method !== 'PUT') return jsonResponse({ error: 'method-not-allowed' }, 405);
|
|
221
|
+
const authReason = deps.authReason?.(req);
|
|
222
|
+
if (!authReason || !DECISION_AUTH_REASONS.has(authReason)) {
|
|
223
|
+
debug.log('approvals.feed', 'refused', { graphId, reason: 'pairing-required' });
|
|
224
|
+
return jsonResponse({ error: 'pairing-required' }, 403);
|
|
225
|
+
}
|
|
226
|
+
if (!undecided(state, root)) return jsonResponse({ error: 'already-decided' }, 409);
|
|
227
|
+
const current = readFeedDraft(folder);
|
|
228
|
+
if (!current) return jsonResponse({ error: 'not-found' }, 404);
|
|
229
|
+
let body: unknown;
|
|
230
|
+
try { body = await req.json(); }
|
|
231
|
+
catch { return jsonResponse({ error: 'bad_request' }, 400); }
|
|
232
|
+
const result = applyFeedEdit(current, body);
|
|
233
|
+
if (!result.ok) {
|
|
234
|
+
debug.log('approvals.feed', 'refused', { graphId, reason: result.error });
|
|
235
|
+
return jsonResponse({ error: result.error }, 400);
|
|
236
|
+
}
|
|
237
|
+
writeFeedDraft(folder, result.draft);
|
|
238
|
+
debug.log('approvals.feed', 'draft-saved', { graphId, revision: result.draft.revision });
|
|
239
|
+
const { folder: _folder, ...feed } = result.draft;
|
|
240
|
+
return jsonResponse({ graphId, runId, feed });
|
|
241
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { relative } from 'node:path';
|
|
1
|
+
import { readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join, relative } from 'node:path';
|
|
3
|
+
import { hasTerminalRunStatus, loadRunLedger, runLedgerDir, runLedgerPath, type RunLedgerEntry } from '../../self-implement/run-ledger.js';
|
|
4
|
+
import { summarizeTerminalLedger } from '../../self-implement/run-ledger-compact.js';
|
|
3
5
|
import { debug } from '../../debug/log.js';
|
|
4
6
|
import { enqueueSoftStop } from '../../harness/control-inbox.js';
|
|
5
7
|
import { listHarnessScreens, readHarnessScreenTail } from '../../harness/harness-screen.js';
|
|
@@ -103,6 +105,10 @@ export interface HarnessApiDeps {
|
|
|
103
105
|
readonly createAcceptanceId?: () => string;
|
|
104
106
|
readonly log?: (event: string, data: Record<string, unknown>) => void;
|
|
105
107
|
readonly createFeedbackEmitter?: (acceptanceId: string) => (env: FeedbackEnvelope) => void | Promise<void>;
|
|
108
|
+
/** 시험 심 — `?finishedSince=` 가 읽는 원장 디렉터리(기본 `runLedgerDir()`). */
|
|
109
|
+
readonly ledgerDir?: string;
|
|
110
|
+
/** 시험 심 — `?finishedSince=` 의 `landed`(gh 실행기·저장소·시간 초과). */
|
|
111
|
+
readonly landed?: LandedPrsDeps;
|
|
106
112
|
}
|
|
107
113
|
|
|
108
114
|
function json(body: unknown, status = 200): Response {
|
|
@@ -338,10 +344,218 @@ export function handleHarnessAskStatusGet(req: Request, _metaApi: HarnessMetaApi
|
|
|
338
344
|
});
|
|
339
345
|
}
|
|
340
346
|
|
|
341
|
-
/**
|
|
342
|
-
export
|
|
347
|
+
/** One terminated run for `GET /v1/harness/runs?finishedSince=` (iPhone «오늘 끝난 일» · 2026-09-30). */
|
|
348
|
+
export interface HarnessFinishedRun {
|
|
349
|
+
runId: string;
|
|
350
|
+
status: string;
|
|
351
|
+
endedAt: string;
|
|
352
|
+
/** 마지막 `run-status` 의 `stage`(예: `merged`·`pr-declined`) — 칩을 더 정확히 고를 때. */
|
|
353
|
+
stage?: string;
|
|
354
|
+
objective?: string;
|
|
355
|
+
prUrl?: string;
|
|
356
|
+
/** `merged` 이벤트의 값 — 이벤트가 없으면 칸이 없다(«병합 안 됨»으로 접지 않는다). */
|
|
357
|
+
merged?: boolean;
|
|
358
|
+
/** 마지막 `merge-decision` 의 `decision`(`auto`·`hitl`) — 병합 «결정»이지 병합 «사실»이 아니다. */
|
|
359
|
+
mergeDecision?: string;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export const FINISHED_RUNS_LIMIT = 20;
|
|
363
|
+
export const FINISHED_RUNS_MAX_FILES = 300;
|
|
364
|
+
export const FINISHED_RUNS_MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
365
|
+
const FINISHED_OBJECTIVE_MAX_CHARS = 160;
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* 원장 디렉터리에서 `sinceMs` 이후에 쓰인 «끝난» 런을 요약한다. 읽기는 묶여 있다 —
|
|
369
|
+
* mtime 최신 {@link FINISHED_RUNS_MAX_FILES} 개만 보고, {@link FINISHED_RUNS_MAX_FILE_BYTES} 를 넘는 파일은 건너뛴다.
|
|
370
|
+
*/
|
|
371
|
+
export function collectFinishedRuns(dir: string, sinceMs: number): { runs: HarnessFinishedRun[]; scannedFiles: number; skippedFiles: number } {
|
|
372
|
+
let names: string[];
|
|
373
|
+
try { names = readdirSync(dir); }
|
|
374
|
+
catch { return { runs: [], scannedFiles: 0, skippedFiles: 0 }; }
|
|
375
|
+
const candidates: Array<{ runId: string; mtimeMs: number; size: number }> = [];
|
|
376
|
+
for (const name of names) {
|
|
377
|
+
if (!name.endsWith('.jsonl')) continue;
|
|
378
|
+
try {
|
|
379
|
+
const st = statSync(join(dir, name));
|
|
380
|
+
if (st.isFile() && st.mtimeMs >= sinceMs) candidates.push({ runId: name.slice(0, -'.jsonl'.length), mtimeMs: st.mtimeMs, size: st.size });
|
|
381
|
+
} catch { /* 사이에 지워졌다 */ }
|
|
382
|
+
}
|
|
383
|
+
candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
384
|
+
const scanned = candidates.slice(0, FINISHED_RUNS_MAX_FILES);
|
|
385
|
+
let skippedFiles = candidates.length - scanned.length;
|
|
386
|
+
const runs: HarnessFinishedRun[] = [];
|
|
387
|
+
for (const file of scanned) {
|
|
388
|
+
if (file.size > FINISHED_RUNS_MAX_FILE_BYTES) { skippedFiles += 1; continue; }
|
|
389
|
+
let entries: RunLedgerEntry[] | null;
|
|
390
|
+
try { entries = loadRunLedger(file.runId, dir); }
|
|
391
|
+
catch { skippedFiles += 1; continue; }
|
|
392
|
+
if (!entries?.length || !hasTerminalRunStatus(entries)) continue;
|
|
393
|
+
let summary: ReturnType<typeof summarizeTerminalLedger>;
|
|
394
|
+
try { summary = summarizeTerminalLedger(file.runId, entries); }
|
|
395
|
+
catch { skippedFiles += 1; continue; }
|
|
396
|
+
if (!(Date.parse(summary.endedAt) >= sinceMs)) continue;
|
|
397
|
+
const feature = entries.find((entry) => entry.event === 'start' && typeof entry.data.feature === 'string')?.data.feature as string | undefined;
|
|
398
|
+
const objective = feature?.trim().slice(0, FINISHED_OBJECTIVE_MAX_CHARS);
|
|
399
|
+
const mergedEvent = [...entries].reverse().find((entry) => entry.event === 'merged' && typeof entry.data.merged === 'boolean');
|
|
400
|
+
const decision = [...entries].reverse().find((entry) => entry.event === 'merge-decision' && typeof entry.data.decision === 'string');
|
|
401
|
+
const statusEntry = [...entries].reverse().find((entry) => entry.event === 'run-status' && typeof entry.data.stage === 'string');
|
|
402
|
+
runs.push({
|
|
403
|
+
runId: summary.runId,
|
|
404
|
+
status: summary.status,
|
|
405
|
+
endedAt: summary.endedAt,
|
|
406
|
+
...(statusEntry ? { stage: statusEntry.data.stage as string } : {}),
|
|
407
|
+
...(objective ? { objective } : {}),
|
|
408
|
+
...(summary.prUrl ? { prUrl: summary.prUrl } : {}),
|
|
409
|
+
...(mergedEvent ? { merged: mergedEvent.data.merged as boolean } : {}),
|
|
410
|
+
...(decision ? { mergeDecision: decision.data.decision as string } : {}),
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
runs.sort((a, b) => Date.parse(b.endedAt) - Date.parse(a.endedAt));
|
|
414
|
+
return { runs: runs.slice(0, FINISHED_RUNS_LIMIT), scannedFiles: scanned.length, skippedFiles };
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/** 도는 런의 목표 — 그 런 원장의 첫 `start.data.feature`(≤160자). 원장 경로는 그 항목이 준 디렉터리들만 본다(런 수만큼만 읽는다). */
|
|
418
|
+
export function runObjectiveFromLedgers(runId: string, directories: readonly string[]): string | undefined {
|
|
419
|
+
for (const dir of directories) {
|
|
420
|
+
try {
|
|
421
|
+
if (statSync(runLedgerPath(runId, dir)).size > FINISHED_RUNS_MAX_FILE_BYTES) continue;
|
|
422
|
+
const feature = loadRunLedger(runId, dir)?.find((entry) => entry.event === 'start' && typeof entry.data.feature === 'string')?.data.feature;
|
|
423
|
+
const objective = typeof feature === 'string' ? feature.trim().slice(0, FINISHED_OBJECTIVE_MAX_CHARS) : '';
|
|
424
|
+
if (objective) return objective;
|
|
425
|
+
} catch { /* 없거나 못 읽음 — 다음 디렉터리 */ }
|
|
426
|
+
}
|
|
427
|
+
return undefined;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// ── 오늘 착지(= main 에 병합된 PR) — 원장은 Pod·시험 우주의 런을 못 보므로 GitHub 이 정본이다 ──
|
|
431
|
+
|
|
432
|
+
/** `landed` 한 줄 — `gh pr list --json number,title,url,mergedAt` 모양 그대로. */
|
|
433
|
+
export interface HarnessLandedPr {
|
|
434
|
+
number: number;
|
|
435
|
+
title: string;
|
|
436
|
+
url: string;
|
|
437
|
+
mergedAt: string;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export const LANDED_PRS_LIMIT = 30;
|
|
441
|
+
export const LANDED_PRS_TIMEOUT_MS = 4_000;
|
|
442
|
+
const LANDED_CACHE_MS = 60_000;
|
|
443
|
+
const landedCache = new Map<string, { at: number; prs: HarnessLandedPr[]; truncated: boolean }>();
|
|
444
|
+
let cachedRepoSlug: string | null | undefined;
|
|
445
|
+
|
|
446
|
+
export function _resetLandedPrsCacheForTest(): void {
|
|
447
|
+
landedCache.clear();
|
|
448
|
+
cachedRepoSlug = undefined;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
export interface LandedPrsDeps {
|
|
452
|
+
/** gh 실행기(기본 = `git-fs/gh-cli` 의 재시도 게이트웨이 · 비동기). */
|
|
453
|
+
readonly runGh?: (args: string[]) => Promise<{ ok: boolean; exitCode: number; stdout: Buffer | string; stderr: Buffer | string }>;
|
|
454
|
+
/** 저장소 `owner/name` — 기본은 데몬이 아는 저장소 뿌리(`harness.defaultRepo` → cwd)의 origin. */
|
|
455
|
+
readonly repoSlug?: () => Promise<string | null>;
|
|
456
|
+
readonly timeoutMs?: number;
|
|
457
|
+
readonly now?: () => number;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
async function defaultRepoSlug(): Promise<string | null> {
|
|
461
|
+
if (cachedRepoSlug !== undefined) return cachedRepoSlug;
|
|
462
|
+
const [{ resolveWorktreesRepoRoot }, { slugFromRemote }] = await Promise.all([import('./worktrees.js'), import('./live-shipped.js')]);
|
|
463
|
+
const root = resolveWorktreesRepoRoot();
|
|
464
|
+
if (!root) return (cachedRepoSlug = null);
|
|
465
|
+
const proc = Bun.spawn(['git', '-C', root, 'remote', 'get-url', 'origin'], { stdout: 'pipe', stderr: 'ignore' });
|
|
466
|
+
const [out, code] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
|
|
467
|
+
return (cachedRepoSlug = code === 0 ? slugFromRemote(out) : null);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
async function defaultRunGh(args: string[]) {
|
|
471
|
+
const { runGhCliWithResultAsync } = await import('../../git-fs/gh-cli.js');
|
|
472
|
+
return runGhCliWithResultAsync(args);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function isLandedPr(value: unknown): value is HarnessLandedPr {
|
|
476
|
+
const v = value as Partial<HarnessLandedPr> | null;
|
|
477
|
+
return !!v && typeof v.number === 'number' && typeof v.title === 'string' && typeof v.url === 'string' && typeof v.mergedAt === 'string';
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/** `sinceMs` 이후 main 에 병합된 PR(최신순 ≤30). 실패·시간 초과는 던지지 않고 `error` 로 돌려준다. 성공만 60초 캐시한다. */
|
|
481
|
+
export async function listLandedPrs(sinceMs: number, deps: LandedPrsDeps = {}): Promise<{ prs: HarnessLandedPr[]; error?: string; cached?: boolean; truncated?: boolean }> {
|
|
482
|
+
const now = deps.now?.() ?? Date.now();
|
|
483
|
+
const sinceIso = new Date(Math.floor(sinceMs / 60_000) * 60_000).toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
484
|
+
const hit = landedCache.get(sinceIso);
|
|
485
|
+
if (hit && now - hit.at < LANDED_CACHE_MS) return { prs: hit.prs, cached: true, ...(hit.truncated ? { truncated: true } : {}) };
|
|
486
|
+
const timeoutMs = deps.timeoutMs ?? LANDED_PRS_TIMEOUT_MS;
|
|
487
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
488
|
+
const timeout = new Promise<{ prs: HarnessLandedPr[]; error: string }>((resolve) => {
|
|
489
|
+
timer = setTimeout(() => resolve({ prs: [], error: `timeout ${timeoutMs}ms` }), timeoutMs);
|
|
490
|
+
});
|
|
491
|
+
const work = (async (): Promise<{ prs: HarnessLandedPr[]; error?: string; truncated?: boolean }> => {
|
|
492
|
+
try {
|
|
493
|
+
const slug = await (deps.repoSlug ?? defaultRepoSlug)();
|
|
494
|
+
if (!slug) return { prs: [], error: 'no-repository' };
|
|
495
|
+
const result = await (deps.runGh ?? defaultRunGh)([
|
|
496
|
+
'pr', 'list', '--repo', slug, '--state', 'merged', '--base', 'main',
|
|
497
|
+
'--search', `merged:>=${sinceIso}`,
|
|
498
|
+
'--json', 'number,title,url,mergedAt', '--limit', String(LANDED_PRS_LIMIT),
|
|
499
|
+
]);
|
|
500
|
+
if (!result.ok) {
|
|
501
|
+
const stderr = String(result.stderr).trim().split('\n')[0] ?? '';
|
|
502
|
+
return { prs: [], error: `gh failed rc=${result.exitCode}${stderr ? `: ${redactSecretText(stderr).slice(0, 120)}` : ''}` };
|
|
503
|
+
}
|
|
504
|
+
const parsed: unknown = JSON.parse(String(result.stdout));
|
|
505
|
+
if (!Array.isArray(parsed)) return { prs: [], error: 'gh returned non-array' };
|
|
506
|
+
const prs = parsed.filter(isLandedPr)
|
|
507
|
+
.filter((pr) => Date.parse(pr.mergedAt) >= sinceMs)
|
|
508
|
+
.sort((a, b) => Date.parse(b.mergedAt) - Date.parse(a.mergedAt));
|
|
509
|
+
// gh 가 상한만큼 줬으면 더 있을 수 있다 — «전부»라고 말하지 않는다.
|
|
510
|
+
const truncated = parsed.length >= LANDED_PRS_LIMIT;
|
|
511
|
+
landedCache.set(sinceIso, { at: now, prs, truncated });
|
|
512
|
+
return { prs, ...(truncated ? { truncated: true } : {}) };
|
|
513
|
+
} catch (error) {
|
|
514
|
+
return { prs: [], error: message(error).slice(0, 120) };
|
|
515
|
+
}
|
|
516
|
+
})();
|
|
517
|
+
try {
|
|
518
|
+
return await Promise.race([work, timeout]);
|
|
519
|
+
} finally {
|
|
520
|
+
if (timer) clearTimeout(timer);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/** Return the structured shared running-runs observation without human rendering.
|
|
525
|
+
* `?finishedSince=<epoch ms>` 가 있으면(iPhone «오늘» · 2026-09-30) 셋을 더한다 —
|
|
526
|
+
* `finished`(이 우주 원장의 끝난 런 · 최신순 ≤20) · `landed`(그 시각 뒤 main 에 병합된 PR · ≤30 · 실패면 `[]` ⊕ `landedError`) ·
|
|
527
|
+
* 도는 항목마다 `objective`(그 런 원장의 `start.feature`). 없으면 응답은 종전 그대로다(동기 응답). */
|
|
528
|
+
export function handleHarnessRunsGet(req: Request, _metaApi: HarnessMetaApi, deps: HarnessApiDeps = {}): Response | Promise<Response> {
|
|
343
529
|
const runs: RunningRunsResult = (deps.queryRunningRuns ?? queryRunningRuns)({ includeTest: false });
|
|
344
|
-
|
|
530
|
+
const raw = new URL(req.url).searchParams.get('finishedSince');
|
|
531
|
+
if (raw === null) return json(runs);
|
|
532
|
+
const sinceMs = Number(raw);
|
|
533
|
+
if (!raw.trim() || !Number.isFinite(sinceMs) || sinceMs < 0) {
|
|
534
|
+
return json({ error: 'usage: GET /v1/harness/runs?finishedSince=<epoch ms>' }, 400);
|
|
535
|
+
}
|
|
536
|
+
return (async () => {
|
|
537
|
+
const landedWork = listLandedPrs(sinceMs, deps.landed ?? {});
|
|
538
|
+
const dir = deps.ledgerDir ?? runLedgerDir();
|
|
539
|
+
const finished = collectFinishedRuns(dir, sinceMs);
|
|
540
|
+
const entries = runs.entries.map((entry) => {
|
|
541
|
+
const objective = runObjectiveFromLedgers(entry.runId, entry.ledgerDirectories?.length ? entry.ledgerDirectories : [dir]);
|
|
542
|
+
return objective ? { ...entry, objective } : entry;
|
|
543
|
+
});
|
|
544
|
+
const landed = await landedWork;
|
|
545
|
+
debug.log('harness-http', 'runs-finished', {
|
|
546
|
+
sinceMs, count: finished.runs.length, scannedFiles: finished.scannedFiles, skippedFiles: finished.skippedFiles,
|
|
547
|
+
landed: landed.prs.length, landedCached: landed.cached === true, ...(landed.error ? { landedError: landed.error } : {}),
|
|
548
|
+
});
|
|
549
|
+
return json({
|
|
550
|
+
...runs,
|
|
551
|
+
entries,
|
|
552
|
+
finished: finished.runs,
|
|
553
|
+
finishedObservation: { sinceMs, scannedFiles: finished.scannedFiles, skippedFiles: finished.skippedFiles, limit: FINISHED_RUNS_LIMIT },
|
|
554
|
+
landed: landed.prs,
|
|
555
|
+
...(landed.truncated ? { landedTruncated: true } : {}),
|
|
556
|
+
...(landed.error ? { landedError: landed.error } : {}),
|
|
557
|
+
});
|
|
558
|
+
})();
|
|
345
559
|
}
|
|
346
560
|
|
|
347
561
|
/** Return the human-readable skeleton events persisted for one harness run. */
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
// Port policy (N1-2 decision): auto-pick from 31415 upward. Actual port
|
|
16
16
|
// is recorded in runtime.json + printed in boot banner.
|
|
17
17
|
|
|
18
|
+
import { getUserConfig } from '../../user-config.js';
|
|
18
19
|
import type { TelegramEvent } from '../../workflow-runtime/triggers/telegram-source.js';
|
|
19
20
|
import { spawnSync } from 'node:child_process';
|
|
20
21
|
import { compareTokenConstTime } from '../../acp/transport/auth.js';
|
|
@@ -198,6 +199,11 @@ import { handleMcpResourceGet, MCP_RESOURCE_ROUTE_PATH } from './mcp-resource-ro
|
|
|
198
199
|
import { APPROVALS_MERGES_PATH, DESIGN_DIRECTION_PATH, DESIGN_PREVIEW_PATH_PREFIX, DESIGN_PREVIEWS_PATH, DESIGN_SYSTEM_PATH, IPA_PATH_PREFIX, MANIFEST_PATH } from './rest-route-paths.js';
|
|
199
200
|
import { handleMergeApprovals } from './merge-approvals.js';
|
|
200
201
|
import { GRAPH_APPROVALS_PATH, handleGraphApprovals } from './graph-approvals.js';
|
|
202
|
+
import { handleExecRequests } from './exec-requests.js';
|
|
203
|
+
import { handleSeatRequests, SEAT_REQUESTS_PATH, type SeatRequestsDeps } from './seat-requests.js';
|
|
204
|
+
import { handleConsultRequests, CONSULT_REQUESTS_PATH, type ConsultRequestsDeps } from './consult-requests.js';
|
|
205
|
+
import { ExecRequestRunner } from '../../exec-requests/runner.js';
|
|
206
|
+
import { FIELD_SERVER_MAX_BODY_BYTES, FIELD_UPLOADS_PATH, FIELD_REEL_PATH, FIELD_REEL_FILE_PATH, handleFieldUploads } from './field-uploads.js';
|
|
201
207
|
import { handleLiveDetail, LIVE_DETAIL_PATH } from './live-detail.js';
|
|
202
208
|
import { handleLiveShipped, LIVE_SHIPPED_PATH } from './live-shipped.js';
|
|
203
209
|
import { handleMcpWidgetCall, MCP_WIDGET_CALL_ROUTE_PATH, persistWidgetTurnToSessionStore } from './mcp-widget-call-route.js';
|
|
@@ -235,6 +241,7 @@ import {
|
|
|
235
241
|
parseErrorsPath,
|
|
236
242
|
} from './errors.js';
|
|
237
243
|
import { handleChatBackendDetection } from './chat-backend-detection.js';
|
|
244
|
+
import { handleChatFastPathConfig } from './chat-fast-path-config.js';
|
|
238
245
|
import { connectInfoTokenAllowed, handleConnectInfoGet, handleConnectTokenMint, type ConnectInfoCtx } from './connect-info.js';
|
|
239
246
|
import { handleStaticAppRequest, pathMatchesStaticPrefix } from './static-app.js';
|
|
240
247
|
import {
|
|
@@ -445,6 +452,9 @@ export type NexusWsBridgeInit = Omit<WsBridgeOpts, 'hostname' | 'port'>;
|
|
|
445
452
|
export interface NexusHttpServerOpts {
|
|
446
453
|
/** Optional installer state root for isolated API consumers and tests. */
|
|
447
454
|
pluginStateRoot?: string;
|
|
455
|
+
execRequests?: ExecRequestRunner;
|
|
456
|
+
seatRequests?: SeatRequestsDeps;
|
|
457
|
+
consultRequests?: ConsultRequestsDeps;
|
|
448
458
|
state: NexusState;
|
|
449
459
|
registry: TabRegistry;
|
|
450
460
|
eventBus: NexusEventBus;
|
|
@@ -739,12 +749,16 @@ export function startNexusHttpServer(opts: NexusHttpServerOpts): NexusHttpServer
|
|
|
739
749
|
port,
|
|
740
750
|
hostname,
|
|
741
751
|
idleTimeout: 255,
|
|
752
|
+
// Bun 기본 128 MB 로는 현장 업로드(파일 200 MB · 요청 500 MB · JSON base64)가 못 들어온다.
|
|
753
|
+
maxRequestBodySize: FIELD_SERVER_MAX_BODY_BYTES,
|
|
742
754
|
fetch: async (req: Request, srv: unknown) =>
|
|
743
755
|
(await routeRequest(req, opts, srv as BunServerLike, bridge, devProxyRef, { hostname, port })) ??
|
|
744
756
|
new Response(null, { status: 101 }),
|
|
745
757
|
...(websocketHandler ? { websocket: websocketHandler } : {}),
|
|
746
758
|
} as unknown as Parameters<typeof Bun.serve>[0];
|
|
747
759
|
server = Bun.serve(fetchOpts);
|
|
760
|
+
// 데몬이 «실제로» 떴을 때만 재시작 복구(맡긴 일) — 주입된 러너(시험)는 건드리지 않는다.
|
|
761
|
+
if (!opts.execRequests) { try { execRequestRunner().reconcileInterrupted(); } catch { /* 복구 실패가 서버 기동을 막지 않는다 */ } }
|
|
748
762
|
resolvedPort = port;
|
|
749
763
|
break;
|
|
750
764
|
} catch (err) {
|
|
@@ -783,6 +797,13 @@ function isPortBusy(err: unknown): boolean {
|
|
|
783
797
|
return /EADDRINUSE|address already in use/i.test(msg);
|
|
784
798
|
}
|
|
785
799
|
|
|
800
|
+
let execRequestRunnerInstance: ExecRequestRunner | undefined;
|
|
801
|
+
/** 게으른 한 벌 — import 만으로는 만들지 않는다(재시작 복구가 import 에 묶이지 않게). */
|
|
802
|
+
function execRequestRunner(): ExecRequestRunner {
|
|
803
|
+
execRequestRunnerInstance ??= new ExecRequestRunner();
|
|
804
|
+
return execRequestRunnerInstance;
|
|
805
|
+
}
|
|
806
|
+
|
|
786
807
|
export async function routeRequest(
|
|
787
808
|
req: Request,
|
|
788
809
|
opts: NexusHttpServerOpts,
|
|
@@ -831,16 +852,26 @@ export async function routeRequest(
|
|
|
831
852
|
// Per-route checkAuth calls below stay in place.
|
|
832
853
|
// OPTIONS is a CORS preflight and cannot carry Authorization; existing
|
|
833
854
|
// handlers answer it themselves, so the gate does not swallow it.
|
|
834
|
-
//
|
|
835
|
-
//
|
|
836
|
-
const
|
|
837
|
-
|
|
855
|
+
// Without metaApi, only the owner bearer may reach these runtime-less
|
|
856
|
+
// meta-API stubs. All other private paths remain default-denied.
|
|
857
|
+
const runtimeLessMetaRoute = !opts.metaApi && (
|
|
858
|
+
(method !== 'GET' && method !== 'OPTIONS'
|
|
859
|
+
&& (pathname === '/v1/nexus/tabs' || pathname.startsWith('/v1/nexus/tabs/')))
|
|
860
|
+
|| (method === 'GET' && (
|
|
861
|
+
['/v1/simulations', '/v1/tools', '/v1/intake', '/v1/sessions', '/v1/control-signals', '/v1/turns/last/screenshot'].includes(pathname)
|
|
862
|
+
|| pathname.startsWith('/v1/recordings/')
|
|
863
|
+
))
|
|
864
|
+
|| (method === 'POST' && (
|
|
865
|
+
['/v1/intake', '/v1/sessions/external', '/v1/control-signals', '/v1/prompt'].includes(pathname)
|
|
866
|
+
|| pathname.startsWith('/v1/hitl/callback/')
|
|
867
|
+
))
|
|
868
|
+
);
|
|
838
869
|
if (
|
|
839
870
|
method !== 'OPTIONS'
|
|
840
871
|
&& pathname.startsWith('/v1/')
|
|
841
872
|
&& !isPublicRoute(method, pathname, { setupMode: false })
|
|
842
873
|
) {
|
|
843
|
-
const fallbackToken =
|
|
874
|
+
const fallbackToken = runtimeLessMetaRoute ? ensureAuthToken().token : undefined;
|
|
844
875
|
const offered = req.headers.get('authorization');
|
|
845
876
|
const fallbackAuthorized = fallbackToken !== undefined && offered?.startsWith('Bearer ') === true
|
|
846
877
|
&& compareTokenConstTime(offered.slice('Bearer '.length).trim(), fallbackToken);
|
|
@@ -916,12 +947,39 @@ export async function routeRequest(
|
|
|
916
947
|
});
|
|
917
948
|
}
|
|
918
949
|
|
|
950
|
+
if (pathname === SEAT_REQUESTS_PATH) {
|
|
951
|
+
if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
952
|
+
return handleSeatRequests(req, opts.seatRequests);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
if (pathname === CONSULT_REQUESTS_PATH) {
|
|
956
|
+
if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
957
|
+
return handleConsultRequests(req, opts.consultRequests);
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
if (pathname === '/v1/exec-requests' || pathname.startsWith('/v1/exec-requests/')) {
|
|
961
|
+
if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
962
|
+
if (/^\/v1\/exec-requests\/[^/]+\/files\//.test(pathname)) {
|
|
963
|
+
const credential = bearerCredential(req, opts.metaApi);
|
|
964
|
+
if (credential !== 'bearer-match' && credential !== 'temp-token') return jsonResponse({ error: 'unauthorized' }, 401);
|
|
965
|
+
}
|
|
966
|
+
return handleExecRequests(req, opts.execRequests ?? execRequestRunner());
|
|
967
|
+
}
|
|
968
|
+
|
|
919
969
|
if (pathname === GRAPH_APPROVALS_PATH || pathname.startsWith(`${GRAPH_APPROVALS_PATH}/`)) {
|
|
920
970
|
return handleGraphApprovals(req, {
|
|
921
971
|
authorize: (request) => !!opts.metaApi && checkAuth(request, opts.metaApi),
|
|
922
972
|
authReason: (request) => (opts.metaApi ? bearerCredential(request, opts.metaApi) : undefined),
|
|
923
973
|
});
|
|
924
974
|
}
|
|
975
|
+
// 현장 업로드(owner 전용) — GET 목록 · POST 저장. 저장 규칙 정본 = src/field/field-media.ts.
|
|
976
|
+
if ((pathname === FIELD_UPLOADS_PATH && (method === 'GET' || method === 'POST'))
|
|
977
|
+
|| ((pathname === FIELD_REEL_PATH || pathname === FIELD_REEL_FILE_PATH) && method === 'GET')) {
|
|
978
|
+
return handleFieldUploads(req, {
|
|
979
|
+
authorize: (request) => !!opts.metaApi && checkAuth(request, opts.metaApi),
|
|
980
|
+
configuredEvent: () => { try { return getUserConfig().telegram?.fieldDefaultEvent; } catch { return undefined; } },
|
|
981
|
+
});
|
|
982
|
+
}
|
|
925
983
|
if (pathname === APPROVALS_MERGES_PATH || pathname.startsWith(`${APPROVALS_MERGES_PATH}/`)) {
|
|
926
984
|
return handleMergeApprovals(req, {
|
|
927
985
|
authorize: (request) => !!opts.metaApi && checkAuth(request, opts.metaApi),
|
|
@@ -1208,6 +1266,11 @@ export async function routeRequest(
|
|
|
1208
1266
|
return handleGraphsValidatePost(req, opts.metaApi);
|
|
1209
1267
|
}
|
|
1210
1268
|
|
|
1269
|
+
if (pathname === '/v1/config/chat-fast-path' && (method === 'GET' || method === 'PUT')) {
|
|
1270
|
+
if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
1271
|
+
return handleChatFastPathConfig(req);
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1211
1274
|
// Mutation routes (PR ι) — POST/PATCH/DELETE on /v1/nexus/tabs[/:id[/action]].
|
|
1212
1275
|
// Templates POST handled here too (PR κ).
|
|
1213
1276
|
// Config / secrets PUT/POST/DELETE here too (PR μ).
|