elanous 0.2.6 → 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 +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-2eba6079f31b88d4.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/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-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-10ed24f413ed66e9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-211810b6c93ab66a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-624f7a2f89b60c8f.js +1 -0
- 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-55d50e9b0006a91d.js +1 -0
- 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-37b8bf78a3c6eec5.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/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/package.json +5 -5
- 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 +24 -3
- 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 +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/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/env-known-failures.ts +31 -0
- package/scripts/release-loop/gate-node.ts +81 -19
- 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/unattended-release.ts +93 -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/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/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/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/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 +42 -1
- 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 +50 -11
- 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 +8 -3
- 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 +86 -14
- 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 +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 +61 -7
- 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 +16 -10
- 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 +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 +9 -8
- 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/session/chat.ts +17 -5
- 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-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 +38 -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 +80 -4
- 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 → keajW7DDyghEs1y8oJe6T}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export type Maturity = 'stable' | 'beta' | 'tool' | 'ops' | 'broken' | 'system';
|
|
2
|
+
export type Surface = 'pwa' | 'desktop' | 'ios' | 'android' | 'tui' | 'cli' | 'telegram' | 'discord' | 'acp';
|
|
3
|
+
export type Role = 'owner' | 'contributor' | 'general';
|
|
4
|
+
|
|
5
|
+
type SurfaceMaturity = { pwa: Maturity } & Partial<Record<Exclude<Surface, 'pwa'>, Maturity>>;
|
|
6
|
+
|
|
7
|
+
/** App Router pages, including the generated 404 and the dynamic missions route. */
|
|
8
|
+
export const FEATURE_MATURITY = {
|
|
9
|
+
pwaRoute: {
|
|
10
|
+
'/': { pwa: 'stable' },
|
|
11
|
+
'/404': { pwa: 'stable' },
|
|
12
|
+
'/setup': { pwa: 'stable' },
|
|
13
|
+
'/setup/done': { pwa: 'stable' },
|
|
14
|
+
'/consult': { pwa: 'stable' },
|
|
15
|
+
'/chat': { pwa: 'stable' },
|
|
16
|
+
'/term': { pwa: 'stable' },
|
|
17
|
+
'/live': { pwa: 'stable' },
|
|
18
|
+
'/market': { pwa: 'stable' },
|
|
19
|
+
'/sessions': { pwa: 'stable' },
|
|
20
|
+
'/settings': { pwa: 'beta' },
|
|
21
|
+
'/editor': { pwa: 'beta' },
|
|
22
|
+
'/intake': { pwa: 'beta' },
|
|
23
|
+
'/trace': { pwa: 'beta' },
|
|
24
|
+
'/board': { pwa: 'beta' },
|
|
25
|
+
'/autopilot': { pwa: 'beta' },
|
|
26
|
+
'/missions/[id]': { pwa: 'beta' },
|
|
27
|
+
'/tasks': { pwa: 'beta' },
|
|
28
|
+
'/workspace': { pwa: 'beta' },
|
|
29
|
+
'/reflection': { pwa: 'beta' },
|
|
30
|
+
'/vault': { pwa: 'beta' },
|
|
31
|
+
'/observatory': { pwa: 'beta' },
|
|
32
|
+
'/exec': { pwa: 'beta' },
|
|
33
|
+
'/field': { pwa: 'beta' },
|
|
34
|
+
'/showroom': { pwa: 'beta' },
|
|
35
|
+
'/workflows': { pwa: 'beta' },
|
|
36
|
+
'/approvals': { pwa: 'tool' },
|
|
37
|
+
'/worktrees': { pwa: 'tool' },
|
|
38
|
+
'/design-check': { pwa: 'tool' },
|
|
39
|
+
'/control': { pwa: 'tool' },
|
|
40
|
+
'/scheduler': { pwa: 'ops' },
|
|
41
|
+
'/dashboard': { pwa: 'ops' },
|
|
42
|
+
'/bots': { pwa: 'ops' },
|
|
43
|
+
'/botlab': { pwa: 'ops' },
|
|
44
|
+
'/morning': { pwa: 'broken' },
|
|
45
|
+
'/settings/devices': { pwa: 'broken' },
|
|
46
|
+
'/workflows/chat-ui': { pwa: 'broken' },
|
|
47
|
+
'/share': { pwa: 'system' },
|
|
48
|
+
},
|
|
49
|
+
} as const satisfies { pwaRoute: Record<string, SurfaceMaturity> };
|
|
50
|
+
|
|
51
|
+
/** Unregistered routes and surfaces are not implicitly mature. Desktop shares the PWA implementation. */
|
|
52
|
+
export function maturityOn(route: string, surface: Surface): Maturity | undefined {
|
|
53
|
+
if (!Object.prototype.hasOwnProperty.call(FEATURE_MATURITY.pwaRoute, route)) return undefined;
|
|
54
|
+
const grades: SurfaceMaturity = FEATURE_MATURITY.pwaRoute[route as keyof typeof FEATURE_MATURITY.pwaRoute];
|
|
55
|
+
return grades[surface] ?? (surface === 'desktop' ? grades.pwa : undefined);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function visibleOn(route: string, surface: Surface, role: Role): boolean {
|
|
59
|
+
const maturity = maturityOn(route, surface);
|
|
60
|
+
if (!maturity) return false;
|
|
61
|
+
if (role === 'owner') return true;
|
|
62
|
+
if (maturity === 'system') return false;
|
|
63
|
+
if (route === '/settings') return true;
|
|
64
|
+
return maturity === 'stable' || (role === 'contributor' && (maturity === 'beta' || maturity === 'tool'));
|
|
65
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { UserConfig } from '../user-config.js';
|
|
2
|
+
import { resolveLlmTier, type LlmTierSource } from './tier-resolver.js';
|
|
3
|
+
import type { ModelTier } from './types.js';
|
|
4
|
+
import type { ReasoningLevel } from '../user-config.js';
|
|
5
|
+
|
|
6
|
+
export type TurnReasoningEffort = 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
|
|
7
|
+
const REASONING_EFFORT: Record<ReasoningLevel, TurnReasoningEffort> = { off: 'minimal', low: 'low', medium: 'medium', high: 'high', xhigh: 'xhigh' };
|
|
8
|
+
|
|
9
|
+
/** Only a person's explicit LLM choice changes a chat turn's model. */
|
|
10
|
+
export function explicitTurnTier(
|
|
11
|
+
cfg: UserConfig,
|
|
12
|
+
sessionId: string,
|
|
13
|
+
): { model: string; tier: ModelTier; source: LlmTierSource; reasoningEffort?: TurnReasoningEffort } | undefined {
|
|
14
|
+
try {
|
|
15
|
+
const resolved = resolveLlmTier(cfg.modelTier, cfg.llm.provider, { sessionId });
|
|
16
|
+
if (resolved.source !== 'session-override' && resolved.source !== 'user-config-surface') return undefined;
|
|
17
|
+
// On codex the balanced·better·best tiers share one model and differ only in reasoning level (low·medium·high),
|
|
18
|
+
// so carrying the model alone left the slider a no-op there (10-01 live).
|
|
19
|
+
const reasoningEffort = resolved.reasoningLevel ? REASONING_EFFORT[resolved.reasoningLevel] : undefined;
|
|
20
|
+
return { model: resolved.model, tier: resolved.tier, source: resolved.source, ...(reasoningEffort ? { reasoningEffort } : {}) };
|
|
21
|
+
} catch {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import { mkdirSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { elanousStateRoot } from '../autopilot/state-paths.js';
|
|
5
|
+
|
|
6
|
+
export interface MessageEnvelope {
|
|
7
|
+
from: string;
|
|
8
|
+
to: string;
|
|
9
|
+
body: string;
|
|
10
|
+
kind?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface Message extends MessageEnvelope {
|
|
14
|
+
id: number;
|
|
15
|
+
createdAt: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface UnreadRecipient {
|
|
19
|
+
recipient: string;
|
|
20
|
+
count: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const SEAT_ALIASES: Readonly<Record<string, string>> = {
|
|
24
|
+
S: 'OP', OP: 'OP', COO: 'OP',
|
|
25
|
+
T: 'MK', MK: 'MK', CMO: 'MK',
|
|
26
|
+
O: 'TC', TC: 'TC', CTO: 'TC',
|
|
27
|
+
F: 'UX', UX: 'UX', CXO: 'UX',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Normalize historic role names to the current seats; leave other valid seats usable. */
|
|
31
|
+
export function canonicalSeatId(value: string): string {
|
|
32
|
+
if (typeof value !== 'string') throw new TypeError('seat must be a string');
|
|
33
|
+
const seat = value.trim().toUpperCase();
|
|
34
|
+
if (!/^[A-Z][A-Z0-9_-]{0,63}$/.test(seat)) throw new Error('invalid seat ID');
|
|
35
|
+
return Object.hasOwn(SEAT_ALIASES, seat) ? SEAT_ALIASES[seat]! : seat;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Check untrusted input before opening the DB or executing a write. */
|
|
39
|
+
export function validateMessageEnvelope(input: unknown): MessageEnvelope {
|
|
40
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) throw new Error('message must be an object');
|
|
41
|
+
const record = input as Record<string, unknown>;
|
|
42
|
+
const from = canonicalSeatId(record.from as string);
|
|
43
|
+
const to = canonicalSeatId(record.to as string);
|
|
44
|
+
if (typeof record.body !== 'string' || !record.body.trim()) throw new Error('message body must be nonempty');
|
|
45
|
+
if (record.kind !== undefined && (typeof record.kind !== 'string' || !/^[a-z][a-z0-9_-]{0,63}$/.test(record.kind))) {
|
|
46
|
+
throw new Error('invalid message kind');
|
|
47
|
+
}
|
|
48
|
+
if (Object.keys(record).some(key => !['from', 'to', 'body', 'kind'].includes(key))) throw new Error('unknown message field');
|
|
49
|
+
return record.kind === undefined ? { from, to, body: record.body } : { from, to, body: record.body, kind: record.kind as string };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function validCursor(cursor: number): number {
|
|
53
|
+
if (!Number.isSafeInteger(cursor) || cursor < 0) throw new Error('cursor must be a nonnegative safe integer');
|
|
54
|
+
return cursor;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface MessageRow {
|
|
58
|
+
id: number;
|
|
59
|
+
sender: string;
|
|
60
|
+
recipient: string;
|
|
61
|
+
body: string;
|
|
62
|
+
kind: string | null;
|
|
63
|
+
created_at: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function fromRow(row: MessageRow): Message {
|
|
67
|
+
return { id: row.id, from: row.sender, to: row.recipient, body: row.body,
|
|
68
|
+
...(row.kind === null ? {} : { kind: row.kind }), createdAt: row.created_at };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** SQLite owns both append order and per-recipient acknowledgement progress. */
|
|
72
|
+
export class MsgStore {
|
|
73
|
+
readonly db: Database;
|
|
74
|
+
|
|
75
|
+
constructor(path: string = defaultMsgStorePath()) {
|
|
76
|
+
if (path !== ':memory:') mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
77
|
+
this.db = new Database(path);
|
|
78
|
+
this.db.exec('PRAGMA busy_timeout = 5000');
|
|
79
|
+
this.db.exec(`CREATE TABLE IF NOT EXISTS msg_messages (
|
|
80
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
81
|
+
sender TEXT NOT NULL,
|
|
82
|
+
recipient TEXT NOT NULL,
|
|
83
|
+
body TEXT NOT NULL,
|
|
84
|
+
kind TEXT,
|
|
85
|
+
created_at TEXT NOT NULL
|
|
86
|
+
);
|
|
87
|
+
CREATE INDEX IF NOT EXISTS idx_msg_messages_recipient_id ON msg_messages(recipient, id);
|
|
88
|
+
CREATE TABLE IF NOT EXISTS msg_cursors (
|
|
89
|
+
recipient TEXT PRIMARY KEY,
|
|
90
|
+
last_id INTEGER NOT NULL DEFAULT 0 CHECK(last_id >= 0)
|
|
91
|
+
);`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
append(input: MessageEnvelope): Message {
|
|
95
|
+
const msg = validateMessageEnvelope(input);
|
|
96
|
+
const createdAt = new Date().toISOString();
|
|
97
|
+
const result = this.db.query(`INSERT INTO msg_messages(sender, recipient, body, kind, created_at)
|
|
98
|
+
VALUES (?, ?, ?, ?, ?)`).run(msg.from, msg.to, msg.body, msg.kind ?? null, createdAt);
|
|
99
|
+
return { id: Number(result.lastInsertRowid), ...msg, createdAt };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
post(input: MessageEnvelope): Message { return this.append(input); }
|
|
103
|
+
|
|
104
|
+
/** Messages strictly after the supplied cursor, ordered by durable insertion ID. */
|
|
105
|
+
listByRecipient(recipient: string, after: number = 0, limit: number = 100): Message[] {
|
|
106
|
+
const to = canonicalSeatId(recipient);
|
|
107
|
+
validCursor(after);
|
|
108
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1000) throw new Error('limit must be between 1 and 1000');
|
|
109
|
+
return (this.db.query(`SELECT id, sender, recipient, body, kind, created_at FROM msg_messages
|
|
110
|
+
WHERE recipient = ? AND id > ? ORDER BY id ASC LIMIT ?`).all(to, after, limit) as MessageRow[]).map(fromRow);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
list(recipient: string, after: number = 0, limit: number = 100): Message[] {
|
|
114
|
+
return this.listByRecipient(recipient, after, limit);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
getCursor(recipient: string): number {
|
|
118
|
+
const to = canonicalSeatId(recipient);
|
|
119
|
+
return (this.db.query('SELECT last_id FROM msg_cursors WHERE recipient = ?').get(to) as { last_id: number } | null)?.last_id ?? 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Atomically reject future IDs and keep concurrent or stale acknowledgements from rewinding. */
|
|
123
|
+
advanceCursor(recipient: string, cursor: number): number {
|
|
124
|
+
const to = canonicalSeatId(recipient);
|
|
125
|
+
validCursor(cursor);
|
|
126
|
+
const result = this.db.query(`INSERT INTO msg_cursors(recipient, last_id)
|
|
127
|
+
SELECT ?, ? WHERE ? <= (SELECT COALESCE(MAX(id), 0) FROM msg_messages)
|
|
128
|
+
ON CONFLICT(recipient) DO UPDATE SET last_id = MAX(msg_cursors.last_id, excluded.last_id)`)
|
|
129
|
+
.run(to, cursor, cursor);
|
|
130
|
+
if (result.changes === 0) throw new Error('cursor exceeds the latest message ID');
|
|
131
|
+
return this.getCursor(to);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
ack(recipient: string, cursor: number): number { return this.advanceCursor(recipient, cursor); }
|
|
135
|
+
|
|
136
|
+
unreadRecipients(): UnreadRecipient[] {
|
|
137
|
+
return this.db.query(`SELECT m.recipient AS recipient, COUNT(*) AS count
|
|
138
|
+
FROM msg_messages m LEFT JOIN msg_cursors c ON c.recipient = m.recipient
|
|
139
|
+
WHERE m.id > COALESCE(c.last_id, 0)
|
|
140
|
+
GROUP BY m.recipient ORDER BY m.recipient ASC`).all() as UnreadRecipient[];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
unread(): UnreadRecipient[] { return this.unreadRecipients(); }
|
|
144
|
+
|
|
145
|
+
close(): void { this.db.close(); }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function defaultMsgStorePath(): string { return join(elanousStateRoot(), 'msg', 'messages.db'); }
|
|
149
|
+
export function openMsgStore(path?: string): MsgStore { return new MsgStore(path); }
|
|
@@ -10,6 +10,7 @@ import type { GuardianSpec } from './guardian/types.js';
|
|
|
10
10
|
import { ptyAvailable } from './pty-shell/registry.js';
|
|
11
11
|
import { krFlowAvailable } from './skills/tools/kr-flow.js';
|
|
12
12
|
import { SELF_COGNITION_MCP_CATALOG_ENTRIES } from './tool-runtime/self-cognition-runtimes.js';
|
|
13
|
+
import { SKILLS_LIST_DESCRIPTION } from './onboarding/entry-hints.js';
|
|
13
14
|
|
|
14
15
|
export type { NativeToolHost } from './tool-surface.js';
|
|
15
16
|
import type { NativeToolHost } from './tool-surface.js';
|
|
@@ -549,7 +550,7 @@ export const nativeToolCatalog: NativeToolCatalogEntry[] = [
|
|
|
549
550
|
kind: 'other',
|
|
550
551
|
aliases: ['ElanousSkillsList'],
|
|
551
552
|
displayName: 'ElanousSkillsList',
|
|
552
|
-
description:
|
|
553
|
+
description: SKILLS_LIST_DESCRIPTION,
|
|
553
554
|
promptSummary: '`elanous_skills_list` (list installed skill names before skill_exec when the exact name is unknown)',
|
|
554
555
|
host: ['skill', 'tui', 'mcp'],
|
|
555
556
|
safety: ['read-only'],
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { buildUserConfig, saveUserConfig, userConfigPath } from '../../user-config.js';
|
|
2
|
+
|
|
3
|
+
function json(body: unknown, status = 200): Response {
|
|
4
|
+
return new Response(JSON.stringify(body), {
|
|
5
|
+
status,
|
|
6
|
+
headers: { 'content-type': 'application/json' },
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** GET/PUT /v1/config/chat-fast-path. Authentication is enforced by the HTTP dispatcher. */
|
|
11
|
+
export async function handleChatFastPathConfig(req: Request): Promise<Response> {
|
|
12
|
+
if (req.method === 'GET') {
|
|
13
|
+
return json({ enabled: buildUserConfig(userConfigPath()).chat.fastPath });
|
|
14
|
+
}
|
|
15
|
+
if (req.method !== 'PUT') return json({ error: 'method-not-allowed' }, 405);
|
|
16
|
+
|
|
17
|
+
let body: unknown;
|
|
18
|
+
try { body = await req.json(); }
|
|
19
|
+
catch { return json({ error: 'invalid-json' }, 400); }
|
|
20
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)
|
|
21
|
+
|| Object.keys(body).length !== 1 || typeof (body as { enabled?: unknown }).enabled !== 'boolean') {
|
|
22
|
+
return json({ error: 'invalid-shape' }, 400);
|
|
23
|
+
}
|
|
24
|
+
const path = userConfigPath();
|
|
25
|
+
const cfg = buildUserConfig(path);
|
|
26
|
+
saveUserConfig({ ...cfg, chat: { ...cfg.chat, fastPath: (body as { enabled: boolean }).enabled } }, path);
|
|
27
|
+
return json({ enabled: buildUserConfig(path).chat.fastPath });
|
|
28
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { debug } from '../../debug/log.js';
|
|
5
|
+
import { sendOutbound } from '../../domains/outbound-alert.js';
|
|
6
|
+
import { effectiveInstanceRoot } from '../../instance/resolve.js';
|
|
7
|
+
import { jsonResponse } from './json-response.js';
|
|
8
|
+
|
|
9
|
+
export const CONSULT_REQUESTS_PATH = '/v1/consult-requests';
|
|
10
|
+
|
|
11
|
+
interface ConsultRequest {
|
|
12
|
+
receiptId: string;
|
|
13
|
+
name: string;
|
|
14
|
+
org: string | null;
|
|
15
|
+
kind: 'company' | 'personal';
|
|
16
|
+
interest: 'A' | 'B';
|
|
17
|
+
contact: string;
|
|
18
|
+
consent: true;
|
|
19
|
+
receivedAt: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ConsultRequestsDeps {
|
|
23
|
+
root?: () => string;
|
|
24
|
+
now?: () => string;
|
|
25
|
+
send?: typeof sendOutbound;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function file(root: string): string {
|
|
29
|
+
return join(root, 'consult-requests', 'requests.jsonl');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function append(path: string, entry: ConsultRequest): void {
|
|
33
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
34
|
+
const previous = existsSync(path) ? readFileSync(path, 'utf8') : '';
|
|
35
|
+
const temp = `${path}.${randomUUID()}.tmp`;
|
|
36
|
+
try {
|
|
37
|
+
writeFileSync(temp, `${previous}${JSON.stringify(entry)}\n`, { mode: 0o600 });
|
|
38
|
+
const fd = openSync(temp, 'r');
|
|
39
|
+
try { fsyncSync(fd); } finally { closeSync(fd); }
|
|
40
|
+
renameSync(temp, path);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
try { unlinkSync(temp); } catch { /* no temp file to remove */ }
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function records(path: string): ConsultRequest[] {
|
|
48
|
+
try {
|
|
49
|
+
return readFileSync(path, 'utf8').split('\n').filter(Boolean).map((line) => JSON.parse(line) as ConsultRequest);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return [];
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function rejected(field: string): Response {
|
|
57
|
+
debug.log('consult.request', 'rejected', { field });
|
|
58
|
+
return jsonResponse({ error: 'bad_request', field }, 400);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function singleLine(value: string): string {
|
|
62
|
+
return value.trim().replace(/[\r\n\u2028\u2029]+/g, ' ');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function handleConsultRequests(req: Request, deps: ConsultRequestsDeps = {}): Promise<Response> {
|
|
66
|
+
if (req.method !== 'GET' && req.method !== 'POST') return jsonResponse({ error: 'method-not-allowed' }, 405);
|
|
67
|
+
const path = file((deps.root ?? effectiveInstanceRoot)());
|
|
68
|
+
if (req.method === 'GET') {
|
|
69
|
+
const rawLimit = new URL(req.url).searchParams.get('limit');
|
|
70
|
+
const limit = rawLimit === null ? 20 : Number(rawLimit);
|
|
71
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100) return rejected('limit');
|
|
72
|
+
try {
|
|
73
|
+
const items = records(path).reverse().slice(0, limit).map(({ receiptId, name, org, kind, interest, receivedAt }) =>
|
|
74
|
+
({ receiptId, name, org, kind, interest, receivedAt }));
|
|
75
|
+
return jsonResponse({ items });
|
|
76
|
+
} catch {
|
|
77
|
+
return jsonResponse({ error: 'journal-unavailable' }, 503);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let body: unknown;
|
|
82
|
+
try { body = await req.json(); }
|
|
83
|
+
catch { return rejected('body'); }
|
|
84
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) return rejected('body');
|
|
85
|
+
const input = body as Record<string, unknown>;
|
|
86
|
+
for (const field of ['name', 'contact'] as const) {
|
|
87
|
+
if (typeof input[field] !== 'string' || !input[field].trim()) return rejected(field);
|
|
88
|
+
}
|
|
89
|
+
if (input.org !== undefined && (typeof input.org !== 'string' || !input.org.trim())) return rejected('org');
|
|
90
|
+
if (input.kind !== 'company' && input.kind !== 'personal') return rejected('kind');
|
|
91
|
+
if (input.interest !== 'A' && input.interest !== 'B') return rejected('interest');
|
|
92
|
+
if (input.consent !== true) return rejected('consent');
|
|
93
|
+
|
|
94
|
+
const entry: ConsultRequest = {
|
|
95
|
+
receiptId: `R-${randomUUID()}`,
|
|
96
|
+
name: singleLine(input.name as string),
|
|
97
|
+
org: typeof input.org === 'string' ? singleLine(input.org) : null,
|
|
98
|
+
kind: input.kind,
|
|
99
|
+
interest: input.interest,
|
|
100
|
+
contact: (input.contact as string).trim(),
|
|
101
|
+
consent: true,
|
|
102
|
+
receivedAt: (deps.now ?? (() => new Date().toISOString()))(),
|
|
103
|
+
};
|
|
104
|
+
try { append(path, entry); }
|
|
105
|
+
catch {
|
|
106
|
+
debug.log('consult.request', 'rejected', { field: 'journal' });
|
|
107
|
+
return jsonResponse({ error: 'journal-unavailable' }, 503);
|
|
108
|
+
}
|
|
109
|
+
debug.log('consult.request', 'accepted', { receiptId: entry.receiptId });
|
|
110
|
+
try {
|
|
111
|
+
(deps.send ?? sendOutbound)(
|
|
112
|
+
`📮 상담 문의 ${entry.receiptId} · ${entry.name}(${entry.kind === 'personal' ? '개인' : (entry.org ?? '회사')}) · 관심 ${entry.interest}\n앱에서 보기`,
|
|
113
|
+
'alert',
|
|
114
|
+
);
|
|
115
|
+
} catch { /* the local receipt remains accepted if delivery is unavailable */ }
|
|
116
|
+
return jsonResponse({ receiptId: entry.receiptId, receivedAt: entry.receivedAt }, 202);
|
|
117
|
+
}
|
|
@@ -66,6 +66,8 @@ export interface NexusDiscordTriggerBotOpts {
|
|
|
66
66
|
* commands (/cc·/fork·/voice-join·…) route into the same composed
|
|
67
67
|
* message pipeline. Absent ⇒ interactions ignored (legacy). */
|
|
68
68
|
onInteraction?: (raw: Record<string, unknown>) => void | Promise<void>;
|
|
69
|
+
/** EV12c — guild text channels answered like DMs (`discord.chatChannels`). Absent ⇒ DM-only. */
|
|
70
|
+
guildTextChannels?: string[];
|
|
69
71
|
/** Optional logger. Defaults to `[discord/trigger]` prefix. */
|
|
70
72
|
log?: (msg: string) => void;
|
|
71
73
|
/** Test seam — pre-built DiscordBot (factory skips construction +
|
|
@@ -156,6 +158,7 @@ export function createNexusDiscordTriggerBot(
|
|
|
156
158
|
const bot = opts.bot ?? new DiscordBot({
|
|
157
159
|
token: opts.token,
|
|
158
160
|
allowedUsers: opts.allowedUsers,
|
|
161
|
+
...(opts.guildTextChannels?.length ? { guildTextChannels: opts.guildTextChannels } : {}),
|
|
159
162
|
// Chat path: the injected self-turn handler (M4b nexus wire) or the
|
|
160
163
|
// legacy trigger-only no-op. Workflows wanting a chat reply can
|
|
161
164
|
// still declare a `chatTrigger` (routes via `/v1/workflows/chat/*`).
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { extname } from 'node:path';
|
|
2
|
+
import { ExecRequestRunner } from '../../exec-requests/runner.js';
|
|
3
|
+
import { jsonResponse } from './json-response.js';
|
|
4
|
+
|
|
5
|
+
const mime: Record<string, string> = {
|
|
6
|
+
'.pdf': 'application/pdf', '.mp4': 'video/mp4', '.mov': 'video/quicktime', '.webm': 'video/webm',
|
|
7
|
+
'.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.webp': 'image/webp',
|
|
8
|
+
'.md': 'text/markdown; charset=utf-8', '.txt': 'text/plain; charset=utf-8',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export async function handleExecRequests(req: Request, runner: ExecRequestRunner): Promise<Response> {
|
|
12
|
+
const path = new URL(req.url).pathname;
|
|
13
|
+
if (path === '/v1/exec-requests') {
|
|
14
|
+
if (req.method === 'POST') {
|
|
15
|
+
let body: unknown;
|
|
16
|
+
try { body = await req.json(); } catch { return jsonResponse({ error: 'bad_request' }, 400); }
|
|
17
|
+
const text = body && typeof body === 'object' && !Array.isArray(body) ? (body as { text?: unknown }).text : undefined;
|
|
18
|
+
if (typeof text !== 'string' || !text.trim()) return jsonResponse({ error: 'bad_request', reason: 'text required' }, 400);
|
|
19
|
+
const item = runner.submit(text.trim());
|
|
20
|
+
return jsonResponse({ id: item.id, status: 'planning' }, 202);
|
|
21
|
+
}
|
|
22
|
+
if (req.method === 'GET') return jsonResponse({ items: runner.store.list().map(item => {
|
|
23
|
+
const current = runner.get(item.id) ?? item;
|
|
24
|
+
return { id: current.id, text: current.text, createdAt: current.createdAt, status: current.status,
|
|
25
|
+
seats: current.seats.map(({ seat, title, status }) => ({ seat, title, status })), resultCount: current.results.length };
|
|
26
|
+
}) });
|
|
27
|
+
return jsonResponse({ error: 'method-not-allowed' }, 405);
|
|
28
|
+
}
|
|
29
|
+
const file = /^\/v1\/exec-requests\/([^/]+)\/files\/([^/]+)$/.exec(path);
|
|
30
|
+
if (file) {
|
|
31
|
+
if (req.method !== 'GET') return jsonResponse({ error: 'method-not-allowed' }, 405);
|
|
32
|
+
let id: string, name: string;
|
|
33
|
+
try { id = decodeURIComponent(file[1]!); name = decodeURIComponent(file[2]!); }
|
|
34
|
+
catch { return jsonResponse({ error: 'not-found' }, 404); }
|
|
35
|
+
const path = runner.file(id, name);
|
|
36
|
+
if (!path) return jsonResponse({ error: 'not-found' }, 404);
|
|
37
|
+
return new Response(Bun.file(path), { headers: { 'content-type': mime[extname(path).toLowerCase()] ?? 'application/octet-stream', 'x-content-type-options': 'nosniff' } });
|
|
38
|
+
}
|
|
39
|
+
const detail = /^\/v1\/exec-requests\/([^/]+)$/.exec(path);
|
|
40
|
+
if (detail) {
|
|
41
|
+
if (req.method !== 'GET') return jsonResponse({ error: 'method-not-allowed' }, 405);
|
|
42
|
+
let id: string;
|
|
43
|
+
try { id = decodeURIComponent(detail[1]!); } catch { return jsonResponse({ error: 'not-found' }, 404); }
|
|
44
|
+
const item = runner.get(id);
|
|
45
|
+
if (!item) return jsonResponse({ error: 'not-found' }, 404);
|
|
46
|
+
const { seats, ...rest } = item;
|
|
47
|
+
return jsonResponse({ ...rest, seats: seats.map(({ seat, title, status, graphId, runId, reason }) =>
|
|
48
|
+
({ seat, title, status, graphId, runId, ...(reason ? { reason } : {}) })) });
|
|
49
|
+
}
|
|
50
|
+
return jsonResponse({ error: 'not-found' }, 404);
|
|
51
|
+
}
|
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
// 저장 규칙은 src/field/field-media.ts 가 유일한 정본이고 텔레그램 `#현장` 도 같은 함수를 쓴다.
|
|
3
3
|
|
|
4
4
|
import { debug } from '../../debug/log.js';
|
|
5
|
+
import { readFieldReelStatus, scheduleFieldReel, fieldReelFile, hasFieldReelFile, type FieldReelOptions } from '../../field/field-reel.js';
|
|
5
6
|
import { getElanousConfigDir } from '../../elanous-config-dir.js';
|
|
6
7
|
import {
|
|
7
8
|
FIELD_MAX_FILE_BYTES,
|
|
8
9
|
FIELD_MAX_REQUEST_BYTES,
|
|
9
10
|
FieldUploadError,
|
|
11
|
+
defaultFieldEvent,
|
|
10
12
|
fieldEventDir,
|
|
11
13
|
isFieldSlug,
|
|
12
14
|
listFieldMedia,
|
|
@@ -16,6 +18,9 @@ import {
|
|
|
16
18
|
import { jsonResponse } from './json-response.js';
|
|
17
19
|
|
|
18
20
|
export const FIELD_UPLOADS_PATH = '/v1/field/uploads';
|
|
21
|
+
export const FIELD_REEL_PATH = '/v1/field/reel';
|
|
22
|
+
export const FIELD_REEL_FILE_PATH = '/v1/field/reel/file';
|
|
23
|
+
const reelUrl = (event: string) => `${FIELD_REEL_FILE_PATH}?event=${encodeURIComponent(event)}`;
|
|
19
24
|
|
|
20
25
|
/** 멀티파트 경계·헤더 여유. */
|
|
21
26
|
const MULTIPART_SLACK_BYTES = 4 * 1024 * 1024;
|
|
@@ -29,6 +34,9 @@ interface FieldUploadsDeps {
|
|
|
29
34
|
/** config 뿌리 — 기본 `getElanousConfigDir()`(데몬의 다른 저장소와 같은 해석). 시험은 temp 를 준다. */
|
|
30
35
|
rootDir?: () => string;
|
|
31
36
|
now?: () => Date;
|
|
37
|
+
reel?: FieldReelOptions;
|
|
38
|
+
/** 설정 `telegram.fieldDefaultEvent` — 앱이 «오늘» 행사 칸의 기본값으로 받는다(비면 field-<날짜>). */
|
|
39
|
+
configuredEvent?: () => string | undefined;
|
|
32
40
|
}
|
|
33
41
|
|
|
34
42
|
function errorResponse(error: FieldUploadError): Response {
|
|
@@ -52,6 +60,7 @@ async function readMultipart(req: Request): Promise<FieldMediaInput[]> {
|
|
|
52
60
|
catch { throw new FieldUploadError('bad-request', 'invalid multipart body'); }
|
|
53
61
|
const files = form.getAll('file').filter((v): v is File => typeof v !== 'string');
|
|
54
62
|
const captured = parseCapturedAtList(form.getAll('capturedAt'));
|
|
63
|
+
const captions = form.getAll('caption').filter((v): v is string => typeof v === 'string');
|
|
55
64
|
let total = 0;
|
|
56
65
|
const inputs: FieldMediaInput[] = [];
|
|
57
66
|
for (const [i, file] of files.entries()) {
|
|
@@ -63,6 +72,7 @@ async function readMultipart(req: Request): Promise<FieldMediaInput[]> {
|
|
|
63
72
|
mimeType: file.type,
|
|
64
73
|
bytes: new Uint8Array(await file.arrayBuffer()),
|
|
65
74
|
...(captured[i] ? { capturedAt: captured[i] } : {}),
|
|
75
|
+
...((captions.length === 1 ? i === 0 : i < captions.length) ? { caption: captions[i] } : {}),
|
|
66
76
|
});
|
|
67
77
|
}
|
|
68
78
|
return inputs;
|
|
@@ -89,22 +99,48 @@ async function readJson(req: Request): Promise<FieldMediaInput[]> {
|
|
|
89
99
|
...(typeof f.mimeType === 'string' ? { mimeType: f.mimeType } : {}),
|
|
90
100
|
bytes: new Uint8Array(Buffer.from(f.dataBase64, 'base64')),
|
|
91
101
|
...(capturedAt !== undefined ? { capturedAt } : {}),
|
|
102
|
+
...(typeof f.caption === 'string' ? { caption: f.caption } : {}),
|
|
92
103
|
};
|
|
93
104
|
});
|
|
94
105
|
}
|
|
95
106
|
|
|
96
107
|
export async function handleFieldUploads(req: Request, deps: FieldUploadsDeps = {}): Promise<Response> {
|
|
97
108
|
const url = new URL(req.url);
|
|
98
|
-
if (url.pathname
|
|
109
|
+
if (![FIELD_UPLOADS_PATH, FIELD_REEL_PATH, FIELD_REEL_FILE_PATH].includes(url.pathname)) return jsonResponse({ error: 'not-found' }, 404);
|
|
99
110
|
if (!deps.authorize?.(req)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
100
111
|
const rootDir = (deps.rootDir ?? getElanousConfigDir)();
|
|
101
112
|
const event = url.searchParams.get('event') ?? '';
|
|
113
|
+
// event 없는 GET = 기본 행사 이름만(앱 «오늘» 행사 칸 · 텔레그램 `#현장` 과 같은 기본).
|
|
114
|
+
if (req.method === 'GET' && !url.searchParams.has('event')) {
|
|
115
|
+
const defaultEvent = defaultFieldEvent((deps.configuredEvent ?? (() => undefined))(), (deps.now ?? (() => new Date()))());
|
|
116
|
+
return jsonResponse({ defaultEvent });
|
|
117
|
+
}
|
|
102
118
|
if (!isFieldSlug(event)) return errorResponse(new FieldUploadError('bad-slug', 'event slug must match ^[a-z0-9][a-z0-9._-]{0,63}$'));
|
|
103
119
|
|
|
120
|
+
const dir = fieldEventDir(rootDir, event);
|
|
121
|
+
if (url.pathname !== FIELD_UPLOADS_PATH && req.method !== 'GET') return jsonResponse({ error: 'method-not-allowed' }, 405);
|
|
122
|
+
if (url.pathname === FIELD_REEL_PATH) {
|
|
123
|
+
const status = readFieldReelStatus(dir);
|
|
124
|
+
if (!status) return jsonResponse({ error: 'not-found' }, 404);
|
|
125
|
+
return jsonResponse({ event, state: status.state, items: status.items,
|
|
126
|
+
...(status.seconds !== undefined ? { seconds: status.seconds } : {}),
|
|
127
|
+
...(status.finishedAt ? { finishedAt: status.finishedAt } : {}),
|
|
128
|
+
...(status.state === 'failed' && status.error ? { error: status.error } : {}),
|
|
129
|
+
...(status.state === 'done' && hasFieldReelFile(fieldReelFile(dir)) ? { url: reelUrl(event) } : {}) });
|
|
130
|
+
}
|
|
131
|
+
if (url.pathname === FIELD_REEL_FILE_PATH) {
|
|
132
|
+
const status = readFieldReelStatus(dir);
|
|
133
|
+
const file = fieldReelFile(dir);
|
|
134
|
+
if (status?.state !== 'done' || !hasFieldReelFile(file)) return jsonResponse({ error: 'not-found' }, 404);
|
|
135
|
+
return new Response(Bun.file(file), { headers: { 'content-type': 'video/mp4' } });
|
|
136
|
+
}
|
|
104
137
|
if (req.method === 'GET') {
|
|
105
|
-
const files = listFieldMedia(
|
|
138
|
+
const files = listFieldMedia(dir);
|
|
139
|
+
const status = readFieldReelStatus(dir);
|
|
106
140
|
debug.log('field.upload', 'listed', { count: files.length });
|
|
107
|
-
return jsonResponse({ event, count: files.length, files
|
|
141
|
+
return jsonResponse({ event, count: files.length, files,
|
|
142
|
+
...(status ? { reel: { status: status.state, updatedAt: status.updatedAt,
|
|
143
|
+
...(status.state === 'done' && hasFieldReelFile(fieldReelFile(dir)) ? { url: reelUrl(event) } : {}) } } : {}) });
|
|
108
144
|
}
|
|
109
145
|
if (req.method !== 'POST') return jsonResponse({ error: 'method-not-allowed' }, 405);
|
|
110
146
|
|
|
@@ -131,6 +167,7 @@ export async function handleFieldUploads(req: Request, deps: FieldUploadsDeps =
|
|
|
131
167
|
debug.log('field.upload', 'saved', {
|
|
132
168
|
surface: 'http', files: result.saved.length, bytes: result.saved.reduce((n, s) => n + s.bytes, 0), count: result.count,
|
|
133
169
|
});
|
|
170
|
+
scheduleFieldReel(result.dir, deps.reel);
|
|
134
171
|
return jsonResponse({ ok: true, event, saved: result.saved, count: result.count });
|
|
135
172
|
} catch (error) {
|
|
136
173
|
if (error instanceof FieldUploadError) {
|