elanous 0.2.6 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +10 -7
- package/README.md +4 -0
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3995-bee512a7063036ce.js → 2375-cb6debea56cce4fb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +25 -0
- package/apps/pwa/out/_next/static/chunks/{8d0d9faf-97887ff99713b84e.js → 2ea96958-e8c7e112de50b067.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{5329.8b7e4bca684b6eec.js → 3403.4bc97b4066945a94.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +1 -0
- package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7633.0654fe6ea259c1ee.js → 4323.c5dfb6f503304775.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4836-3084c58c1725a40b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2984-977cfa339fcd65a5.js → 5080-1613752891fcadb4.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{763-7d0e1913b5074e98.js → 5791-bb82743fa6299562.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{4240-7153881b0b377306.js → 6062-73e68343a9814f6a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7032-f4ef698518fba61c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{91ff6dda-3b33d42d20657429.js → 8046cadf-211ca3c0e00f2b72.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8067-d976b441a104ab74.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8894-7317998f6cb97d8c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9454-a3e0953307216c58.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9740-8f5ff96c522b5f55.js +1 -0
- package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-9aa47b0bc6d570ff.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f4d2ecbbae6088d6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-2053e49930c82748.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-d4b6df905f185055.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-a70ba27c33003dd0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-186fc6952f027acc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-18166f885d831d3e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-1c9d6cfe68b121c2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-cbbe03c0da7b081a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-c108ff1fdb665c97.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-147a328753d983e2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-799395a9df7d95aa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-6a8158b71d1fe9d7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-9567560270857531.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-e7ed3d010153d818.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-c0c0d19d51342d3e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-2e002ad8faa43291.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-b672e02e4bec8deb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-1ba4a5d857276334.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-9db2226f38c542fb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-70ee65f5f7a151dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-0bb8e43203efb23b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-092e1bcf27d751cc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-eb52580c4b75b905.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-7464c1987dd76e6d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-e573437b0a1c728f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-294300625e49e4d3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-085847b088675978.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-9fe9d38c36999eb4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-d8ab4d1489223459.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-6141f67f531d8b6a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-295a02b1026b8255.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-1eb9a0e1e054209d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-82efa177a30f3741.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d0be51cec4f62606.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-2e367075ffdbc062.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-647ed2b66c1d49f1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ba058c56e38f2792.js +1 -0
- package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{webpack-37b8bf78a3c6eec5.js → webpack-e5bea36f183b25e6.js} +1 -1
- package/apps/pwa/out/_next/static/css/5ef72a618a8994f3.css +1 -0
- package/apps/pwa/out/_next/static/lywc8m_HDzH9MKSDQhyEj/_buildManifest.js +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +22 -22
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +22 -22
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +22 -22
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +21 -21
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +21 -21
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +21 -21
- package/apps/pwa/out/consult/index.html +1 -0
- package/apps/pwa/out/consult/index.txt +32 -0
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +22 -22
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +22 -22
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +22 -22
- package/apps/pwa/out/exec/index.html +1 -0
- package/apps/pwa/out/exec/index.txt +32 -0
- package/apps/pwa/out/field/index.html +1 -0
- package/apps/pwa/out/field/index.txt +32 -0
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +21 -21
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +22 -22
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +22 -22
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +21 -21
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +21 -21
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +22 -22
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +22 -22
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +22 -22
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +22 -22
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +21 -21
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +22 -22
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +22 -22
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +22 -22
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +22 -22
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +22 -22
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +21 -21
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +22 -22
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +22 -22
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +22 -22
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +22 -22
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +22 -22
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +22 -22
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +22 -22
- package/bin/elanous.cjs +107 -0
- package/graphs/field/field-feed.yaml +24 -0
- package/graphs/field/recipes.yaml +12 -0
- package/graphs/landing/landing-heal.yaml +3 -1
- package/graphs/landing/recipes.yaml +3 -0
- package/graphs/release/recipes.yaml +10 -0
- package/graphs/release/release-loop.yaml +10 -3
- package/graphs/test-diet/recipes.yaml +9 -0
- package/graphs/test-diet/test-diet.yaml +22 -0
- package/package.json +6 -5
- package/persona-presets/_index.yaml +10 -0
- package/persona-presets/_schema.json +146 -0
- package/persona-presets/dana.yaml +26 -0
- package/persona-presets/haru.yaml +27 -0
- package/persona-presets/kai.yaml +27 -0
- package/persona-presets/leo.yaml +27 -0
- package/persona-presets/mira.yaml +30 -0
- package/persona-presets/nari.yaml +27 -0
- package/persona-presets/sora.yaml +27 -0
- package/persona-presets/yuna.yaml +27 -0
- package/plugins/card-followup/README.md +13 -4
- package/plugins/card-followup/graphs/card-followup.yaml +3 -1
- package/plugins/card-followup/graphs/recipes.yaml +5 -1
- package/plugins/card-followup/graphs/run-step.ts +122 -18
- package/plugins/card-followup/graphs/sales-strategy.ts +173 -0
- package/plugins/card-followup/plugin.json +1 -1
- package/plugins/doc-draft/README.md +13 -0
- package/plugins/doc-draft/examples/brief-sample.json +8 -0
- package/plugins/doc-draft/graphs/doc-draft.yaml +14 -0
- package/plugins/doc-draft/graphs/recipes.yaml +9 -0
- package/plugins/doc-draft/graphs/run-step.ts +113 -0
- package/plugins/doc-draft/plugin.json +24 -0
- package/plugins/geo-check/README.md +3 -1
- package/plugins/geo-check/graphs/run-step.ts +58 -13
- package/plugins/geo-check/plugin.json +1 -1
- package/plugins/job-coach/.codex-plugin/plugin.json +1 -1
- package/plugins/job-coach/README.md +1 -1
- package/plugins/job-coach/graphs/run-step.ts +2 -2
- package/plugins/job-coach/plugin.json +1 -1
- package/plugins/job-coach/plugin.ts +1 -1
- package/scripts/affinity/endcard.sh +3 -2
- package/scripts/affinity/reframe.sh +3 -1
- package/scripts/ci-public-export-leak-gate.ts +48 -0
- package/scripts/codex-quota-alert.ts +122 -258
- package/scripts/coord-tracks.json +4 -4
- package/scripts/field-feed/deliver-node.ts +57 -0
- package/scripts/field-feed/feed-node.ts +67 -0
- package/scripts/field-feed/lib.ts +164 -0
- package/scripts/field-feed/reel-node.ts +24 -0
- package/scripts/field-feed/start.ts +62 -0
- package/scripts/fixtures/vflow-sample.ndjson +6 -0
- package/scripts/install.sh +109 -21
- package/scripts/landing-heal/clean-checkout.ts +114 -0
- package/scripts/landing-heal/run-mission.ts +38 -3
- package/scripts/lib/pwa-scenarios/scenarios.ts +2 -1
- package/scripts/persona-presets/lint.ts +79 -0
- package/scripts/public-export.ts +8 -4
- package/scripts/release-loop/baseline-from-logs.ts +92 -0
- package/scripts/release-loop/checklist-gate-node.ts +2 -2
- package/scripts/release-loop/env-known-failures.ts +31 -0
- package/scripts/release-loop/gate-node.ts +108 -22
- package/scripts/release-loop/known-issues-node.ts +159 -0
- package/scripts/release-loop/npm-publish-node.ts +124 -0
- package/scripts/release-loop/ops-upgrade-node.ts +95 -0
- package/scripts/release-loop/publish-node.ts +23 -1
- package/scripts/release-loop/release-readiness.ts +58 -0
- package/scripts/release-loop/shard-plan.ts +17 -0
- package/scripts/release-loop/unattended-release.ts +93 -0
- package/scripts/storyboard/storyboard.ts +167 -0
- package/scripts/test-diet/lib.ts +88 -0
- package/scripts/test-diet/node.ts +102 -0
- package/scripts/ux/measure-first-text.ts +102 -0
- package/scripts/vflow-collect.ts +20 -7
- package/scripts/vflow-search.ts +73 -0
- package/scripts/video-free-line-test-walker.ts +36 -0
- package/scripts/video-pipeline.ts +2 -2
- package/skills/explainer-video/SKILL.md +12 -9
- package/skills/explainer-video/engine/build.mjs +1 -1
- package/skills/explainer-video/engine/reel.mjs +138 -31
- package/skills/explainer-video/engine/reel.sh +2 -1
- package/skills/explainer-video/examples/elanous-0.2.5/script.json +1 -1
- package/skills/grill-me/SKILL.md +1 -1
- package/skills/omni-digest/docs/diagrams/noncode-terminal-promotion-path-2026-08-13.mmd +1 -1
- package/src/a2a/server.ts +2 -0
- package/src/acp/auth-methods.ts +30 -0
- package/src/acp/elanous-aware-peers.ts +30 -0
- package/src/acp/server.ts +96 -20
- package/src/agent/monad-agent-turn.ts +25 -6
- package/src/agent/shared-app-tools.ts +5 -3
- package/src/agent-substrate/review-intent.ts +6 -2
- package/src/agent-substrate/shard-boundary-intent.ts +70 -0
- package/src/auth/github-app-token.ts +4 -1
- package/src/autopilot/track-agent-shadow.ts +151 -0
- package/src/autopilot/track-agent.ts +23 -3
- package/src/boot/acp-server-help.ts +16 -0
- package/src/boot/daemon-prompt-turn.ts +31 -10
- package/src/boot/daemon-tools/index.ts +7 -1
- package/src/budget/codex-credit-pace.ts +84 -0
- package/src/budget/codex-credit-plan.ts +154 -0
- package/src/budget/codex-reset-credit-state.ts +2 -0
- package/src/channel-import/detect.ts +191 -0
- package/src/channel-import/run.ts +111 -0
- package/src/chat/chat-cli.ts +4 -1
- package/src/chat/short-question.ts +40 -0
- package/src/cli/agent-cli.ts +24 -15
- package/src/cli/ask-bare.ts +42 -0
- package/src/cli/cli-user-error.ts +25 -0
- package/src/cli/codex-credit-plan-view.ts +25 -0
- package/src/cli/config-test-sync.ts +4 -0
- package/src/cli/decisions-cli.ts +13 -3
- package/src/cli/dependence-gauge.ts +237 -0
- package/src/cli/doctor-bundle.ts +128 -0
- package/src/cli/doctor-cli.ts +41 -1
- package/src/cli/doctor-local-llm.ts +95 -0
- package/src/cli/doctor-modes.ts +48 -0
- package/src/cli/import-cli.ts +60 -0
- package/src/cli/intake-cli.ts +12 -2
- package/src/cli/intake-source-cli.ts +58 -0
- package/src/cli/machine-cli.ts +23 -0
- package/src/cli/mcp-login.ts +13 -4
- package/src/cli/msg-cli.ts +137 -0
- package/src/cli/persona-cli.ts +116 -0
- package/src/cli/plugin-cli.ts +25 -0
- package/src/cli/pr-cli.ts +36 -0
- package/src/cli/provider-cli.ts +5 -0
- package/src/cli/release-cli.ts +50 -8
- package/src/cli/release-run-if-ready.ts +48 -0
- package/src/cli/setup-cli.ts +27 -6
- package/src/cli/setup-recommend.ts +8 -0
- package/src/cli/skills-cli.ts +173 -0
- package/src/cli/start-cli.ts +13 -4
- package/src/cli/usage-cli.ts +11 -1
- package/src/codex/setup.ts +37 -16
- package/src/dashboard/index.ts +18 -2
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +2 -2
- package/src/dashboard/slash-runtime/help-from-registry.ts +4 -2
- package/src/decisions/decision-cards.ts +291 -0
- package/src/decisions/decision-ledger.ts +5 -2
- package/src/decisions/discord-decision-cards.ts +133 -0
- package/src/decisions/telegram-decision-cards.ts +100 -0
- package/src/directives/version-at.ts +77 -0
- package/src/discord.ts +73 -6
- package/src/domains/core-tools.ts +37 -14
- package/src/domains/outbound-alert.ts +94 -12
- package/src/domains/repeated-failure.ts +1 -1
- package/src/error-report/report.ts +200 -0
- package/src/exec-requests/planner.ts +146 -0
- package/src/exec-requests/runner.ts +378 -0
- package/src/exec-requests/store.ts +78 -0
- package/src/field/field-feed-auto.ts +48 -0
- package/src/field/field-media.ts +8 -3
- package/src/field/field-reel.ts +203 -0
- package/src/grok/credential.ts +7 -5
- package/src/harness/aside-run-status.ts +33 -0
- package/src/harness/browser-verify.ts +48 -17
- package/src/harness/harness-cli-command.ts +25 -3
- package/src/harness/image-load-state.ts +21 -0
- package/src/harness/process-garbage.ts +45 -0
- package/src/hooks/error-report.ts +210 -0
- package/src/hooks/receiver.ts +41 -1
- package/src/import/apply.ts +128 -0
- package/src/import/plan.ts +197 -0
- package/src/index.ts +168 -21
- package/src/input-core/text-entry.ts +13 -5
- package/src/instance/leader.ts +13 -1
- package/src/intake-plane/collect-github.ts +5 -3
- package/src/intake-plane/collect-telegram-saved.ts +15 -3
- package/src/intake-plane/discord-seat-work.ts +56 -0
- package/src/intake-plane/intake-sources.ts +202 -0
- package/src/intake-plane/items.ts +28 -12
- package/src/intake-plane/seat-answer.ts +96 -0
- package/src/intake-plane/seat-doc-route.ts +221 -0
- package/src/intake-plane/submit-intake-work.ts +9 -1
- package/src/intake-plane/telegram-seat-work.ts +72 -0
- package/src/llm/model-defaults.ts +24 -1
- package/src/llm.ts +5 -4
- package/src/machines/machine-ledger.ts +127 -0
- package/src/market/pack-internal-refs.ts +28 -0
- package/src/market/publish.ts +31 -4
- package/src/maturity/feature-maturity.ts +65 -0
- package/src/model-tier/turn-tier.ts +24 -0
- package/src/msg/msg-store.ts +149 -0
- package/src/native-tool-catalog.ts +2 -1
- package/src/nexus/api/chat-fast-path-config.ts +28 -0
- package/src/nexus/api/consult-requests.ts +128 -0
- package/src/nexus/api/discord-trigger-bot.ts +3 -0
- package/src/nexus/api/error-reports.ts +108 -0
- package/src/nexus/api/exec-requests.ts +51 -0
- package/src/nexus/api/field-uploads.ts +40 -3
- package/src/nexus/api/graph-approvals-feed.ts +140 -0
- package/src/nexus/api/graph-approvals.ts +71 -4
- package/src/nexus/api/http-server.ts +82 -8
- package/src/nexus/api/outbound-report.ts +7 -0
- package/src/nexus/api/running-runs-background.ts +68 -0
- package/src/nexus/api/running-runs-child.ts +6 -0
- package/src/nexus/api/seat-requests.ts +254 -0
- package/src/nexus/api/share-captures.ts +91 -0
- package/src/nexus/api/skill-problems.ts +56 -0
- package/src/nexus/api/terminals.ts +24 -8
- package/src/nexus/api/trace-run-scan.ts +37 -0
- package/src/nexus/api/trace.ts +19 -3
- package/src/nexus/index.ts +28 -1
- package/src/nexus/setup-status.ts +11 -7
- package/src/notify/llm-budget-alert.ts +16 -10
- package/src/oauth/browser-availability.ts +14 -0
- package/src/oauth/codex-account-rotation.ts +26 -4
- package/src/oauth/codex-account-store.ts +20 -2
- package/src/oauth/codex-browser-login.ts +173 -0
- package/src/oauth/codex-quota-policy.ts +16 -0
- package/src/oauth/codex.ts +4 -1
- package/src/oauth/fallback-chain.ts +5 -5
- package/src/oauth/store.ts +42 -5
- package/src/onboarding/continue-here.ts +34 -0
- package/src/onboarding/entry-hints.ts +8 -0
- package/src/onboarding/full-screen-io.ts +35 -3
- package/src/onboarding/help/skills.md +1 -1
- package/src/onboarding/non-interactive.ts +1 -0
- package/src/onboarding/subscription-hint.ts +27 -0
- package/src/onboarding/web-first.ts +90 -0
- package/src/onboarding.ts +287 -26
- package/src/persona/presets.ts +105 -0
- package/src/plugins/maker/node-install.ts +92 -0
- package/src/plugins/maker/node-maker.ts +139 -0
- package/src/plugins/maker/plugin-maker.ts +19 -7
- package/src/policy/llm-policy.ts +268 -0
- package/src/prompt-library/session-guidance.ts +7 -1
- package/src/prompt-library/universal-preamble.ts +2 -2
- package/src/relay/relay-cli.ts +20 -0
- package/src/relay/relay-server.ts +220 -0
- package/src/release-loop/checklist.ts +37 -13
- package/src/self-dev/decompose.ts +1 -0
- package/src/self-dev/dev-pipeline.ts +6 -3
- package/src/self-dev/orchestrate.ts +18 -11
- package/src/self-dev/run-record-mirror.ts +44 -0
- package/src/self-dev/run-store.ts +12 -3
- package/src/self-implement/harness-policy-gates.ts +4 -1
- package/src/self-implement/report-origin.ts +12 -1
- package/src/session/chat.ts +17 -5
- package/src/shell-env-bootstrap.ts +44 -7
- package/src/skills/connect-claude-plugin.ts +142 -0
- package/src/skills/connect.ts +70 -0
- package/src/skills/index.ts +29 -1
- package/src/skills/repair.ts +124 -0
- package/src/skills/runner.ts +11 -2
- package/src/skills/shared-agent-skill-roots.ts +25 -0
- package/src/skills/trigger-extract.ts +1 -1
- package/src/steward/failure-streak.ts +78 -0
- package/src/steward/steward-cards.ts +24 -0
- package/src/steward/triage-budget.ts +127 -0
- package/src/steward/triage-plan.ts +75 -0
- package/src/steward/triage.ts +244 -42
- package/src/task-cards/card-store.ts +1 -1
- package/src/task-orchestrator/surfaces/pod-command-job.ts +16 -8
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +11 -5
- package/src/task-orchestrator/surfaces/pod-ledger-events.ts +68 -0
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +78 -4
- package/src/telegram-commands.ts +13 -0
- package/src/telegram.ts +72 -2
- package/src/tool-runtime/elanous-skills-list-runtime.ts +3 -3
- package/src/tool-runtime/self-cognition-runtimes.ts +160 -2
- package/src/user-config.ts +189 -15
- package/src/version/packed-revision.json +1 -1
- package/src/video-pipeline/recipes/character.ts +42 -5
- package/src/video-pipeline/recipes/ffmpeg.ts +1 -1
- package/src/video-pipeline/recipes/free-line.ts +16 -3
- package/src/video-pipeline/recipes/remote.ts +7 -1
- package/src/video-pipeline/vflow-reference.ts +109 -0
- package/apps/pwa/out/_next/static/chunks/1169-696263ac7af8c4d4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1883.c91a2648af4842fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1947-9d64872eeb9cfdec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2808-dc06482994b40f70.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3165-95515ca5f299a120.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3190-e9282753e7ebde85.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4259-99614149577e77cb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4283-329eabeaf233e6cd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4328-cb5058b1acc2de5e.js +0 -25
- package/apps/pwa/out/_next/static/chunks/4801-ecee6ef62b349b18.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5110-8058791dd25a2e14.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5330-c41e9670f594c38f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5423-e95fd125d1794349.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5478-b5eae07dc79ea619.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5787-96ee6642b9a661e5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6448-79d72368589f98bc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6522-9866c002b5531836.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6729-b4a558b7aa891e0c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6969-cb03f9410b7d2e03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7007-3b9861091bb8dd21.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7930-8dc7e20fc6fc19ca.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8071-187edbc22a86741f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8198-d4101ad70cb9a579.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8250.4a2c5553a07cdf47.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8293-123b91e7b0330dc0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8296-d6d5d87dfa911d41.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8305-603963c33ee8eec3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8612-4a0066f20951b8fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9181-cf4e1b61c4783f92.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9262-26c2ecbd5a67e4a2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9328-45abdebce4fbecb7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9355-dbc67205adf8745f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9763.255b90dd69875689.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9933-66d31ec25c0231af.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-bd06fb55f4da0381.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-4e586142da455a25.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-889ad5cddcf45b0d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-47ba401d56572c27.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-0b54f9c83458e038.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-4fcc296ef6c8ed90.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-fe9fda494a87f4e5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-f9642e25ce129590.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-e1063e8ba5f3e2e7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-9d0df30027d8aa9d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-bd06fb55f4da0381.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-d97adf2c9455b8d0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-236b80d202110362.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-21a30f35f1a276ad.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-b1a5be357c4fa25d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1705a60303f7f573.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-76b337c11ad0e237.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-7c0f85fbb56f64c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-65ca16b20617b44d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-e7f9b8fc29411c1d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-92ea6213d1aeec04.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a913a005aacb5476.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-2d78804a40dfdef6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-08a99148e780ac00.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-deb674e7ed36f815.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-49f8de0534d192d7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-bd06fb55f4da0381.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-ba162a28d2176e18.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-1ff1a37b550ab41a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-ccaa698f56c0fbdb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-53b944082de1d7db.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-9b70adf1552789bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-30d482aa63241fe8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-aecf786f54651d8a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6ab9b62c7b6e08f2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-08949735455e0c9a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-a1479005234a46c2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3890e90bd232732.js +0 -1
- package/apps/pwa/out/_next/static/chunks/beb5cf10-9adcc10b83930090.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-d68d41c88e05c801.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-11c63971b7a9593b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-d09452353bd0d9cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-184d727cebeca767.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-21bbbbac99b720b1.js +0 -1
- package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +0 -1
- package/apps/pwa/out/_next/static/qMI0jnlZaBjjm4aIDYNGM/_buildManifest.js +0 -1
- /package/apps/pwa/out/_next/static/{qMI0jnlZaBjjm4aIDYNGM → lywc8m_HDzH9MKSDQhyEj}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { debug } from '../../debug/log.js';
|
|
5
|
+
import { effectiveInstanceRoot } from '../../instance/resolve.js';
|
|
6
|
+
import { submitIntakeWork } from '../../intake-plane/submit-intake-work.js';
|
|
7
|
+
import { parseSeatAddress, resolveSeat } from '../../seat-address/seat-address.js';
|
|
8
|
+
import trackData from '../../../scripts/coord-tracks.json';
|
|
9
|
+
import { jsonResponse } from './json-response.js';
|
|
10
|
+
|
|
11
|
+
export const SEAT_REQUESTS_PATH = '/v1/seat-requests';
|
|
12
|
+
|
|
13
|
+
interface SeatRequestItem {
|
|
14
|
+
receiptId: string;
|
|
15
|
+
seat: string;
|
|
16
|
+
text: string;
|
|
17
|
+
queuedAt: string;
|
|
18
|
+
status: 'queued';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface PendingRequest {
|
|
22
|
+
status: 'pending';
|
|
23
|
+
key: string;
|
|
24
|
+
seat: string;
|
|
25
|
+
text: string;
|
|
26
|
+
queuedAt: string;
|
|
27
|
+
ref: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface RejectedRequest extends Omit<PendingRequest, 'status'> {
|
|
31
|
+
status: 'rejected';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type RecordEntry = (SeatRequestItem & { key: string }) | PendingRequest | RejectedRequest;
|
|
35
|
+
|
|
36
|
+
export interface SeatRequestsDeps {
|
|
37
|
+
root?: () => string;
|
|
38
|
+
submit?: typeof submitIntakeWork;
|
|
39
|
+
now?: () => string;
|
|
40
|
+
append?: (path: string, entry: RecordEntry) => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const seats = trackData.tracks.filter((entry): entry is typeof entry & { title: string } => 'title' in entry)
|
|
44
|
+
.map(({ id, title }) => ({ id, title }));
|
|
45
|
+
const inFlight = new Map<string, { payload: string; task: Promise<Response> }>();
|
|
46
|
+
|
|
47
|
+
function unknownSeat(): Response {
|
|
48
|
+
debug.log('seat-address.pwa', 'rejected', { reason: 'unknown-seat' });
|
|
49
|
+
return jsonResponse({ error: 'unknown-seat', seats }, 400);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function file(root: string): string {
|
|
53
|
+
return join(root, 'seat-requests', 'requests.jsonl');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function append(path: string, entry: RecordEntry): void {
|
|
57
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
58
|
+
// Atomically replace the journal so a failed write cannot leave a truncated JSON line.
|
|
59
|
+
const previous = existsSync(path) ? readFileSync(path, 'utf8') : '';
|
|
60
|
+
const temp = `${path}.${randomUUID()}.tmp`;
|
|
61
|
+
try {
|
|
62
|
+
writeFileSync(temp, `${previous}${JSON.stringify(entry)}\n`);
|
|
63
|
+
const fd = openSync(temp, 'r');
|
|
64
|
+
try { fsyncSync(fd); } finally { closeSync(fd); }
|
|
65
|
+
renameSync(temp, path);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
try { unlinkSync(temp); } catch { /* no temp file to remove */ }
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function records(path: string): Map<string, RecordEntry> {
|
|
73
|
+
let data: string;
|
|
74
|
+
try { data = readFileSync(path, 'utf8'); }
|
|
75
|
+
catch (error) {
|
|
76
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT' && !existsSync(path)) data = '';
|
|
77
|
+
else throw error;
|
|
78
|
+
}
|
|
79
|
+
const entries = new Map<string, RecordEntry>();
|
|
80
|
+
for (const line of data.split('\n')) {
|
|
81
|
+
if (!line) continue;
|
|
82
|
+
const entry = JSON.parse(line) as RecordEntry;
|
|
83
|
+
if (typeof entry.key !== 'string' || typeof entry.seat !== 'string' || typeof entry.text !== 'string'
|
|
84
|
+
|| typeof entry.queuedAt !== 'string' || !['pending', 'queued', 'rejected'].includes(entry.status)
|
|
85
|
+
|| (entry.status === 'queued' && typeof entry.receiptId !== 'string')
|
|
86
|
+
|| (entry.status !== 'queued' && typeof entry.ref !== 'string')) {
|
|
87
|
+
throw new Error('invalid seat request journal');
|
|
88
|
+
}
|
|
89
|
+
entries.set(entry.key, entry);
|
|
90
|
+
}
|
|
91
|
+
const dir = join(dirname(path), 'outcomes');
|
|
92
|
+
let names: string[];
|
|
93
|
+
try { names = readdirSync(dir); }
|
|
94
|
+
catch (error) {
|
|
95
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return entries;
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
for (const name of names) {
|
|
99
|
+
if (!/^[a-f0-9]{64}\.json$/.test(name)) continue;
|
|
100
|
+
const item = JSON.parse(readFileSync(join(dir, name), 'utf8')) as RecordEntry;
|
|
101
|
+
if (item.key !== name.slice(0, -5) || (item.status !== 'queued' && item.status !== 'rejected')) {
|
|
102
|
+
throw new Error('invalid seat request outcome');
|
|
103
|
+
}
|
|
104
|
+
const current = entries.get(item.key);
|
|
105
|
+
if (current?.status === 'queued') continue;
|
|
106
|
+
if (current?.status === 'pending') {
|
|
107
|
+
if (item.status === 'rejected' && current.ref !== item.ref) continue;
|
|
108
|
+
if (item.status === 'queued' && current.queuedAt !== item.queuedAt) continue;
|
|
109
|
+
}
|
|
110
|
+
entries.set(item.key, item);
|
|
111
|
+
}
|
|
112
|
+
return entries;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function outcomeFile(path: string, key: string): string {
|
|
116
|
+
return join(dirname(path), 'outcomes', `${key}.json`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function persistOutcome(path: string, entry: RecordEntry, write: typeof append): void {
|
|
120
|
+
try {
|
|
121
|
+
write(path, entry);
|
|
122
|
+
const sidecar = outcomeFile(path, entry.key);
|
|
123
|
+
if (existsSync(sidecar)) unlinkSync(sidecar);
|
|
124
|
+
} catch {
|
|
125
|
+
// The primary journal may be unavailable while the outcome is already committed
|
|
126
|
+
// to the intake queue. Keep a durable sidecar rather than a process-local receipt.
|
|
127
|
+
const target = outcomeFile(path, entry.key);
|
|
128
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
129
|
+
const temp = `${target}.${randomUUID()}.tmp`;
|
|
130
|
+
try {
|
|
131
|
+
writeFileSync(temp, JSON.stringify(entry));
|
|
132
|
+
const fd = openSync(temp, 'r');
|
|
133
|
+
try { fsyncSync(fd); } finally { closeSync(fd); }
|
|
134
|
+
renameSync(temp, target);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
try { unlinkSync(temp); } catch { /* no temp file */ }
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function receipt(item: SeatRequestItem): Response {
|
|
143
|
+
return jsonResponse({ receiptId: item.receiptId, seat: item.seat, queuedAt: item.queuedAt }, 202);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function unavailable(): Response {
|
|
147
|
+
debug.log('seat-address.pwa', 'rejected', { reason: 'journal-unavailable' });
|
|
148
|
+
return jsonResponse({ error: 'enqueue-failed' }, 503);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export async function handleSeatRequests(req: Request, deps: SeatRequestsDeps = {}): Promise<Response> {
|
|
152
|
+
const path = file((deps.root ?? effectiveInstanceRoot)());
|
|
153
|
+
if (req.method === 'GET') {
|
|
154
|
+
const params = new URL(req.url).searchParams;
|
|
155
|
+
const requestedSeat = params.get('seat');
|
|
156
|
+
const seat = requestedSeat ? resolveSeat(requestedSeat) : undefined;
|
|
157
|
+
if (requestedSeat && !seat) return unknownSeat();
|
|
158
|
+
const rawLimit = params.get('limit');
|
|
159
|
+
const limit = rawLimit === null ? 20 : Number(rawLimit);
|
|
160
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100) return jsonResponse({ error: 'bad_request' }, 400);
|
|
161
|
+
try {
|
|
162
|
+
const items = [...records(path).values()].filter((entry): entry is RecordEntry & SeatRequestItem => entry.status === 'queued')
|
|
163
|
+
.reverse().filter((item) => !seat || item.seat === seat.id).slice(0, limit)
|
|
164
|
+
.map(({ receiptId, seat: id, text, queuedAt, status }) => ({ receiptId, seat: id, text, queuedAt, status }));
|
|
165
|
+
return jsonResponse({ items, seats });
|
|
166
|
+
} catch { return unavailable(); }
|
|
167
|
+
}
|
|
168
|
+
if (req.method !== 'POST') return jsonResponse({ error: 'method-not-allowed' }, 405);
|
|
169
|
+
|
|
170
|
+
let body: unknown;
|
|
171
|
+
try { body = await req.json(); }
|
|
172
|
+
catch { return jsonResponse({ error: 'bad_request' }, 400); }
|
|
173
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) return jsonResponse({ error: 'bad_request' }, 400);
|
|
174
|
+
const input = body as Record<string, unknown>;
|
|
175
|
+
if (typeof input.text !== 'string' || !input.text.trim()
|
|
176
|
+
|| (input.seat !== undefined && typeof input.seat !== 'string')) {
|
|
177
|
+
debug.log('seat-address.pwa', 'rejected', { reason: 'invalid-body' });
|
|
178
|
+
return jsonResponse({ error: 'bad_request' }, 400);
|
|
179
|
+
}
|
|
180
|
+
// An implicit address must start the text; parseSeatAddress also accepts later lines.
|
|
181
|
+
const parsed = input.text.startsWith('@') ? parseSeatAddress(input.text) : null;
|
|
182
|
+
const prefix = parsed ? `@${parsed.seats.join(',')}` : '';
|
|
183
|
+
const firstAddress = parsed && input.text.startsWith(prefix)
|
|
184
|
+
&& (input.text.length === prefix.length || /[ \t\r\n]/.test(input.text[prefix.length]!)) ? parsed : null;
|
|
185
|
+
const address = input.seat ?? (firstAddress?.seats.length === 1 ? firstAddress.seats[0] : undefined);
|
|
186
|
+
const seat = address ? resolveSeat(address) : undefined;
|
|
187
|
+
if (!seat) return unknownSeat();
|
|
188
|
+
const text = input.seat === undefined ? firstAddress!.body.trim() : input.text.trim();
|
|
189
|
+
if (!text) {
|
|
190
|
+
debug.log('seat-address.pwa', 'rejected', { reason: 'empty-text', seat: seat.id });
|
|
191
|
+
return jsonResponse({ error: 'bad_request' }, 400);
|
|
192
|
+
}
|
|
193
|
+
debug.log('seat-address.pwa', 'parsed', { seat: seat.id, textLength: text.length });
|
|
194
|
+
const now = (deps.now ?? (() => new Date().toISOString()))();
|
|
195
|
+
const header = req.headers.get('idempotency-key');
|
|
196
|
+
if (header !== null && (!header.trim() || header.length > 200)) return jsonResponse({ error: 'bad_request' }, 400);
|
|
197
|
+
const payload = `${seat.id}\0${text}`;
|
|
198
|
+
const key = header === null ? createHash('sha256').update(randomUUID()).digest('hex')
|
|
199
|
+
: createHash('sha256').update(`client\0${header}`).digest('hex');
|
|
200
|
+
const lock = `${path}\0${key}`;
|
|
201
|
+
const pending = inFlight.get(lock);
|
|
202
|
+
if (pending) {
|
|
203
|
+
if (pending.payload !== payload) return jsonResponse({ error: 'idempotency-conflict' }, 409);
|
|
204
|
+
return pending.task.then((response) => response.clone());
|
|
205
|
+
}
|
|
206
|
+
// Install the lock before starting disk or queue work; a second caller must
|
|
207
|
+
// wait for the first instead of independently submitting the same key.
|
|
208
|
+
let start!: () => void;
|
|
209
|
+
const gate = new Promise<void>((resolve) => { start = resolve; });
|
|
210
|
+
const task = (async (): Promise<Response> => {
|
|
211
|
+
await gate;
|
|
212
|
+
let existing: RecordEntry | undefined;
|
|
213
|
+
try { existing = records(path).get(key); }
|
|
214
|
+
catch { return unavailable(); }
|
|
215
|
+
if (existing) {
|
|
216
|
+
if (existing.seat !== seat.id || existing.text !== text) return jsonResponse({ error: 'idempotency-conflict' }, 409);
|
|
217
|
+
if (existing.status === 'queued') return receipt(existing);
|
|
218
|
+
}
|
|
219
|
+
const intent: PendingRequest = existing?.status === 'pending'
|
|
220
|
+
? existing : { key, seat: seat.id, text, queuedAt: now, status: 'pending', ref: `pwa:${randomUUID()}` };
|
|
221
|
+
if (existing?.status !== 'pending') {
|
|
222
|
+
try {
|
|
223
|
+
(deps.append ?? append)(path, intent);
|
|
224
|
+
if (existing?.status === 'rejected') {
|
|
225
|
+
const sidecar = outcomeFile(path, key);
|
|
226
|
+
if (existsSync(sidecar)) unlinkSync(sidecar);
|
|
227
|
+
}
|
|
228
|
+
} catch { return unavailable(); }
|
|
229
|
+
}
|
|
230
|
+
let result: Awaited<ReturnType<typeof submitIntakeWork>>;
|
|
231
|
+
try {
|
|
232
|
+
result = await (deps.submit ?? submitIntakeWork)({
|
|
233
|
+
text: `@${seat.title ?? seat.id} ${text}`,
|
|
234
|
+
track: 'graph',
|
|
235
|
+
origin: { kind: 'external', ledgerSource: 'pwa', provider: 'other', ref: intent.ref, reportTo: { channel: 'pwa' } },
|
|
236
|
+
});
|
|
237
|
+
} catch { return unavailable(); }
|
|
238
|
+
if (!result.ok || result.track !== 'graph') {
|
|
239
|
+
debug.log('seat-address.pwa', 'rejected', { reason: result.ok ? 'unexpected-track' : result.reason, seat: seat.id });
|
|
240
|
+
try { persistOutcome(path, { ...intent, status: 'rejected' }, deps.append ?? append); }
|
|
241
|
+
catch { return unavailable(); }
|
|
242
|
+
return unavailable();
|
|
243
|
+
}
|
|
244
|
+
const item: RecordEntry & SeatRequestItem = { key, receiptId: result.acceptanceId, seat: seat.id, text, queuedAt: intent.queuedAt, status: 'queued' };
|
|
245
|
+
try { persistOutcome(path, item, deps.append ?? append); }
|
|
246
|
+
catch { return unavailable(); }
|
|
247
|
+
debug.log('seat-address.pwa', 'enqueued', { seat: seat.id, receiptId: item.receiptId });
|
|
248
|
+
return receipt(item);
|
|
249
|
+
})();
|
|
250
|
+
inFlight.set(lock, { payload, task });
|
|
251
|
+
start();
|
|
252
|
+
try { return await task; }
|
|
253
|
+
finally { inFlight.delete(lock); }
|
|
254
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// SC1 — 앱 안 «공유용 캡처»(beta) 받는 쪽. PWA 가 화면을 가린 뒤 PNG 로 올린다(티저·현장·보고용).
|
|
2
|
+
// POST /v1/captures body = image/png(≤ 8 MiB) → { id, bytes, droppedChunks }
|
|
3
|
+
// GET /v1/captures → { items: [{ id, bytes, at }] } (최신 먼저 · 50개)
|
|
4
|
+
// GET /v1/captures/<id> → image/png
|
|
5
|
+
// ⛔ 두 번째 방어: 받은 PNG 에서 «그림 아닌» 덩어리(tEXt·iTXt·zTXt·eXIf·iCCP·tIME 등 메타)를 전부 걷고 저장한다 —
|
|
6
|
+
// 화면 쪽 가림이 놓친 것이 파일 메타로 새지 않게. 남기는 것은 그림을 그리는 데 필요한 덩어리뿐이다.
|
|
7
|
+
import { randomBytes } from 'node:crypto';
|
|
8
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { debug } from '../../debug/log.js';
|
|
11
|
+
import { getElanousConfigDir } from '../../elanous-config-dir.js';
|
|
12
|
+
import { jsonResponse } from './json-response.js';
|
|
13
|
+
import { checkAuth, type MetaApiOpts } from './meta-api.js';
|
|
14
|
+
|
|
15
|
+
export const SHARE_CAPTURES_PATH = '/v1/captures';
|
|
16
|
+
const MAX_BYTES = 8 * 1024 * 1024;
|
|
17
|
+
const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
|
18
|
+
/** Chunks needed to draw the picture. Everything else (text, EXIF, ICC profile, time…) is dropped. */
|
|
19
|
+
const KEEP = new Set(['IHDR', 'PLTE', 'IDAT', 'IEND', 'tRNS', 'gAMA', 'cHRM', 'sRGB', 'pHYs']);
|
|
20
|
+
const ID = /^share-\d{8}-\d{6}-[0-9a-f]{6}$/;
|
|
21
|
+
|
|
22
|
+
export interface ShareCapturesDeps { configDir?: string; now?: () => Date }
|
|
23
|
+
|
|
24
|
+
/** Rebuild a PNG with only drawing chunks. Returns null when it is not a well-formed PNG. */
|
|
25
|
+
export function stripPngMetadata(input: Uint8Array): { png: Buffer; dropped: string[] } | null {
|
|
26
|
+
const buf = Buffer.from(input);
|
|
27
|
+
if (buf.length < 8 + 12 || !buf.subarray(0, 8).equals(PNG_SIGNATURE)) return null;
|
|
28
|
+
const parts: Buffer[] = [PNG_SIGNATURE];
|
|
29
|
+
const dropped: string[] = [];
|
|
30
|
+
let offset = 8;
|
|
31
|
+
let sawHeader = false;
|
|
32
|
+
let sawEnd = false;
|
|
33
|
+
while (offset + 12 <= buf.length) {
|
|
34
|
+
const length = buf.readUInt32BE(offset);
|
|
35
|
+
const type = buf.subarray(offset + 4, offset + 8).toString('latin1');
|
|
36
|
+
const end = offset + 12 + length;
|
|
37
|
+
if (end > buf.length || !/^[A-Za-z]{4}$/.test(type)) return null;
|
|
38
|
+
if (!sawHeader && type !== 'IHDR') return null;
|
|
39
|
+
sawHeader = true;
|
|
40
|
+
if (KEEP.has(type)) parts.push(buf.subarray(offset, end)); else dropped.push(type);
|
|
41
|
+
offset = end;
|
|
42
|
+
if (type === 'IEND') { sawEnd = true; break; }
|
|
43
|
+
}
|
|
44
|
+
if (!sawEnd) return null;
|
|
45
|
+
return { png: Buffer.concat(parts), dropped };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function capturesDir(deps: ShareCapturesDeps): string {
|
|
49
|
+
return join(deps.configDir ?? getElanousConfigDir(), 'captures');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function newId(now: Date): string {
|
|
53
|
+
const p = (n: number) => String(n).padStart(2, '0');
|
|
54
|
+
return `share-${now.getFullYear()}${p(now.getMonth() + 1)}${p(now.getDate())}-${p(now.getHours())}${p(now.getMinutes())}${p(now.getSeconds())}-${randomBytes(3).toString('hex')}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function handleShareCaptures(req: Request, metaApi: MetaApiOpts, deps: ShareCapturesDeps = {}): Promise<Response> {
|
|
58
|
+
if (!checkAuth(req, metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
59
|
+
const pathname = new URL(req.url).pathname;
|
|
60
|
+
const dir = capturesDir(deps);
|
|
61
|
+
|
|
62
|
+
if (pathname === SHARE_CAPTURES_PATH && req.method === 'POST') {
|
|
63
|
+
const declared = Number(req.headers.get('content-length') ?? 0);
|
|
64
|
+
if (declared > MAX_BYTES) return jsonResponse({ error: 'too-large', maxBytes: MAX_BYTES }, 413);
|
|
65
|
+
const body = new Uint8Array(await req.arrayBuffer());
|
|
66
|
+
if (body.byteLength > MAX_BYTES) return jsonResponse({ error: 'too-large', maxBytes: MAX_BYTES }, 413);
|
|
67
|
+
const cleaned = stripPngMetadata(body);
|
|
68
|
+
if (!cleaned) return jsonResponse({ error: 'not-png' }, 400);
|
|
69
|
+
const id = newId((deps.now ?? (() => new Date()))());
|
|
70
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
71
|
+
writeFileSync(join(dir, `${id}.png`), cleaned.png, { mode: 0o600, flag: 'wx' });
|
|
72
|
+
debug.log('share-capture', 'stored', { id, bytes: cleaned.png.length, dropped: cleaned.dropped });
|
|
73
|
+
return jsonResponse({ id, bytes: cleaned.png.length, droppedChunks: cleaned.dropped }, 201);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (pathname === SHARE_CAPTURES_PATH && req.method === 'GET') {
|
|
77
|
+
const items = existsSync(dir)
|
|
78
|
+
? readdirSync(dir).filter((f) => f.endsWith('.png') && ID.test(f.slice(0, -4))).sort().reverse().slice(0, 50)
|
|
79
|
+
.map((f) => { const s = statSync(join(dir, f)); return { id: f.slice(0, -4), bytes: s.size, at: s.mtime.toISOString() }; })
|
|
80
|
+
: [];
|
|
81
|
+
return jsonResponse({ items });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const id = pathname.slice(SHARE_CAPTURES_PATH.length + 1);
|
|
85
|
+
if (req.method === 'GET' && ID.test(id)) {
|
|
86
|
+
const file = join(dir, `${id}.png`);
|
|
87
|
+
if (!existsSync(file)) return jsonResponse({ error: 'not-found' }, 404);
|
|
88
|
+
return new Response(readFileSync(file), { headers: { 'content-type': 'image/png', 'cache-control': 'no-store' } });
|
|
89
|
+
}
|
|
90
|
+
return jsonResponse({ error: 'not-found' }, 404);
|
|
91
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// SK2 (PWA) — 색인이 못 읽은 스킬을 PWA 에도 «안내 ⊕ 확인 뒤 수리»로.
|
|
2
|
+
// GET /v1/skills/problems → { items: [{ id, name, code, fixable, hint? }] } — 경로 대신 해시 id 만 내보낸다
|
|
3
|
+
// POST /v1/skills/problems/repair → body { id } · 고칠 수 있는 것만 고친다(남의 도구 파일 — PWA 가 먼저 확인을 받는다)
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { debug } from '../../debug/log.js';
|
|
6
|
+
import { getSkillIndex, skillIndexProblems, type SkillIndexProblem } from '../../skills/index.js';
|
|
7
|
+
import { applySkillRepair, planSkillRepair } from '../../skills/repair.js';
|
|
8
|
+
import { jsonResponse } from './json-response.js';
|
|
9
|
+
import { checkAuth, type MetaApiOpts } from './meta-api.js';
|
|
10
|
+
|
|
11
|
+
export const SKILL_PROBLEMS_PATH = '/v1/skills/problems';
|
|
12
|
+
|
|
13
|
+
export interface SkillProblemsDeps {
|
|
14
|
+
problems?: () => SkillIndexProblem[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function problemId(p: SkillIndexProblem): string {
|
|
18
|
+
return createHash('sha256').update(`${p.dir}\u0000${p.name}`).digest('hex').slice(0, 16);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function current(deps: SkillProblemsDeps): SkillIndexProblem[] {
|
|
22
|
+
if (deps.problems) return deps.problems();
|
|
23
|
+
getSkillIndex();
|
|
24
|
+
return skillIndexProblems();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function handleSkillProblems(req: Request, metaApi: MetaApiOpts, deps: SkillProblemsDeps = {}): Promise<Response> {
|
|
28
|
+
if (!checkAuth(req, metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
29
|
+
const pathname = new URL(req.url).pathname;
|
|
30
|
+
if (pathname === SKILL_PROBLEMS_PATH) {
|
|
31
|
+
if (req.method !== 'GET') return jsonResponse({ error: 'method-not-allowed' }, 405);
|
|
32
|
+
const items = current(deps).map((p) => {
|
|
33
|
+
const plan = planSkillRepair(p);
|
|
34
|
+
return {
|
|
35
|
+
id: problemId(p), name: p.name, code: p.code,
|
|
36
|
+
fixable: plan.kind === 'fixable' || plan.kind === 'fixable-permission',
|
|
37
|
+
...(plan.kind === 'permission' || plan.kind === 'manual' ? { hint: plan.hint.replace(p.dir, '…') } : {}),
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
return jsonResponse({ items });
|
|
41
|
+
}
|
|
42
|
+
if (pathname === `${SKILL_PROBLEMS_PATH}/repair`) {
|
|
43
|
+
if (req.method !== 'POST') return jsonResponse({ error: 'method-not-allowed' }, 405);
|
|
44
|
+
let body: unknown;
|
|
45
|
+
try { body = await req.json(); } catch { return jsonResponse({ error: 'bad_request' }, 400); }
|
|
46
|
+
const id = body && typeof body === 'object' && typeof (body as { id?: unknown }).id === 'string' ? (body as { id: string }).id : '';
|
|
47
|
+
const problem = current(deps).find((p) => problemId(p) === id);
|
|
48
|
+
if (!problem) return jsonResponse({ error: 'not-found' }, 404);
|
|
49
|
+
const plan = planSkillRepair(problem);
|
|
50
|
+
if (plan.kind !== 'fixable' && plan.kind !== 'fixable-permission') return jsonResponse({ error: 'not-fixable', hint: plan.hint.replace(problem.dir, '…') }, 409);
|
|
51
|
+
const result = applySkillRepair(plan);
|
|
52
|
+
debug.log('skills.repair', 'pwa-request', { name: problem.name, ok: result.ok });
|
|
53
|
+
return result.ok ? jsonResponse({ ok: true, name: problem.name }) : jsonResponse({ ok: false, reason: result.reason }, 409);
|
|
54
|
+
}
|
|
55
|
+
return jsonResponse({ error: 'not-found' }, 404);
|
|
56
|
+
}
|
|
@@ -34,6 +34,7 @@ import { classifyPtyOwnerRunUsage, joinPtyLineage, resolveRunTermination, type P
|
|
|
34
34
|
import { loadRunLedger, resolveFederatedRunLedgerDirectories, runLedgerDir } from '../../self-implement/run-ledger.js';
|
|
35
35
|
import { requestRemotePtyControl, type PtyControlAction, type PtyControlPayload, type PtyControlRequestOptions, type PtyControlResult } from '../../pty-shell/pty-control-ipc.js';
|
|
36
36
|
import { encodeSgrMouse, type PtyMouseInput } from '../../pty-shell/pty-mouse.js';
|
|
37
|
+
import { createRunningRunsBackground, type RunningRunsBackground } from './running-runs-background.js';
|
|
37
38
|
import { queryRunningRuns, type RunningRunAssessment, type RunningRunPresence, type RunningRunStatus, type RunningRunsResult } from '../../self-implement/running-runs.js';
|
|
38
39
|
// ⭐P4 §4-1 — 표시 폭(wide-char/CJK/emoji 인지) SSOT. PNG 캔버스 cols 파생용.
|
|
39
40
|
import { cellWidth } from '../../ui/printer.js';
|
|
@@ -494,6 +495,12 @@ function runningRunsCacheFor(
|
|
|
494
495
|
return binding.cache;
|
|
495
496
|
}
|
|
496
497
|
|
|
498
|
+
let liveBackground: RunningRunsBackground | null = null;
|
|
499
|
+
function liveRunningRunsBackground(): RunningRunsBackground {
|
|
500
|
+
liveBackground ??= createRunningRunsBackground();
|
|
501
|
+
return liveBackground;
|
|
502
|
+
}
|
|
503
|
+
|
|
497
504
|
const liveTerminalsListDeps: TerminalsListDeps = {
|
|
498
505
|
ptyManifestTargets,
|
|
499
506
|
listPtyManifestRows,
|
|
@@ -1054,17 +1061,26 @@ export function handleTerminalsList(req: Request, opts: MetaApiOpts, deps: Termi
|
|
|
1054
1061
|
const resolveRunTerminationForRow = (runId: string): RunTermination => deps.runTerminated
|
|
1055
1062
|
? deps.runTerminated(runId, { includeTest })
|
|
1056
1063
|
: 'ledger-indeterminate';
|
|
1057
|
-
const runningRunsCache = runningRunsCacheFor(getRunningRuns, deps.now ?? Date.now);
|
|
1058
1064
|
let runningRuns: RunningRunsResult | null = null;
|
|
1059
1065
|
let runningRunsAgeMs = 0;
|
|
1060
1066
|
let runningRunsQueryFailed = false;
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1067
|
+
if (getRunningRuns === queryRunningRuns) {
|
|
1068
|
+
// Live daemon: never scan ledgers on this event loop (it froze web-terminal typing) — use the last snapshot a
|
|
1069
|
+
// separate process refreshed. Before the first one lands, subjects keep unknown assessments and the age is null.
|
|
1070
|
+
const snap = liveRunningRunsBackground().snapshot({ includeTest });
|
|
1071
|
+
runningRuns = snap.result;
|
|
1072
|
+
if (snap.ageMs === null) runningRunsQueryFailed = true;
|
|
1073
|
+
else runningRunsAgeMs = snap.ageMs;
|
|
1074
|
+
} else {
|
|
1075
|
+
const runningRunsCache = runningRunsCacheFor(getRunningRuns, deps.now ?? Date.now);
|
|
1076
|
+
try {
|
|
1077
|
+
const cached = runningRunsCache({ includeTest });
|
|
1078
|
+
runningRuns = cached.result;
|
|
1079
|
+
runningRunsAgeMs = cached.ageMs;
|
|
1080
|
+
} catch {
|
|
1081
|
+
// Fail-soft and do not cache: subjects keep unknown assessments.
|
|
1082
|
+
runningRunsQueryFailed = true;
|
|
1083
|
+
}
|
|
1068
1084
|
}
|
|
1069
1085
|
const ptyLessSubAgentCollection = collectPtyLessSubAgents(
|
|
1070
1086
|
getLogStore,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { statSync } from 'node:fs';
|
|
2
|
+
import type { LogInstanceView } from '../../mss/logging/instance-registry.js';
|
|
3
|
+
import type { LogStore } from '../../mss/logging/log-store.js';
|
|
4
|
+
|
|
5
|
+
/** Bounded, read-only fallback when a run's ledger is not on this host. */
|
|
6
|
+
export function scanStoresForRun(
|
|
7
|
+
runId: string,
|
|
8
|
+
views: readonly LogInstanceView[],
|
|
9
|
+
open: (view: LogInstanceView) => Pick<LogStore, 'queryTraceRun' | 'close'> | null,
|
|
10
|
+
opts: { sinceMs?: number; untilMs?: number; maxStores?: number; deadlineMs?: number; now?: () => number } = {},
|
|
11
|
+
): { universe?: string; checked: number; truncated: boolean; unresolved?: boolean } {
|
|
12
|
+
const now = opts.now ?? Date.now;
|
|
13
|
+
const deadline = now() + (opts.deadlineMs ?? 3000);
|
|
14
|
+
const maxStores = opts.maxStores ?? 200;
|
|
15
|
+
const mtime = (view: LogInstanceView): number => {
|
|
16
|
+
try { return statSync(view.dbPath).mtimeMs; } catch { return -Infinity; }
|
|
17
|
+
};
|
|
18
|
+
const candidates = views.map((view) => ({ view, at: view.dbExists ? mtime(view) : -Infinity }))
|
|
19
|
+
.sort((a, b) => (b.at === a.at ? a.view.name.localeCompare(b.view.name) : b.at - a.at));
|
|
20
|
+
let checked = 0;
|
|
21
|
+
let unresolved = false;
|
|
22
|
+
for (const { view } of candidates) {
|
|
23
|
+
if (checked >= maxStores || now() >= deadline) return { checked, truncated: true };
|
|
24
|
+
checked++;
|
|
25
|
+
let store: ReturnType<typeof open> = null;
|
|
26
|
+
try {
|
|
27
|
+
if (!view.dbExists) continue;
|
|
28
|
+
store = open(view);
|
|
29
|
+
if (!store) { unresolved = true; continue; }
|
|
30
|
+
if (store.queryTraceRun(runId, { sinceMs: opts.sinceMs, untilMs: opts.untilMs, limit: 1 }).length) {
|
|
31
|
+
return { universe: view.name, checked, truncated: false };
|
|
32
|
+
}
|
|
33
|
+
} catch { unresolved = true; }
|
|
34
|
+
finally { try { store?.close(); } catch { /* read-only handle cleanup is best effort */ } }
|
|
35
|
+
}
|
|
36
|
+
return { checked, truncated: false, ...(unresolved ? { unresolved } : {}) };
|
|
37
|
+
}
|
package/src/nexus/api/trace.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { activeStoreNames, parseSinceParam, type LogFabricDeps, registeredLogSto
|
|
|
8
8
|
import { checkAuth, type MetaApiOpts } from './meta-api.js';
|
|
9
9
|
import { jsonResponse } from './json-response.js';
|
|
10
10
|
import { runLedgerDir, runLedgerPath } from '../../self-implement/run-ledger.js';
|
|
11
|
+
import { scanStoresForRun } from './trace-run-scan.js';
|
|
11
12
|
|
|
12
13
|
export type TraceLevel = 'L0' | 'L1' | 'L2' | 'L3';
|
|
13
14
|
export interface TraceEvent {
|
|
@@ -346,7 +347,8 @@ export function handleTrace(req: Request, opts: MetaApiOpts, deps: TraceDeps = {
|
|
|
346
347
|
}
|
|
347
348
|
if (!opened) return jsonResponse({ ok: false, error: 'log-store-unavailable', failedStores: failures }, 503);
|
|
348
349
|
const runId = url.searchParams.get('runId');
|
|
349
|
-
let resolution: { universe?: string; checked: number } | undefined;
|
|
350
|
+
let resolution: { universe?: string; checked: number; truncated?: boolean; unresolved?: boolean } | undefined;
|
|
351
|
+
let resolvedFrom: 'ledger' | 'log-scan' | undefined;
|
|
350
352
|
if ((parsed.level === 'L2' || parsed.level === 'L3') && runId && !rows.some((row) => matches(row, url)) && !requestedUniverse) {
|
|
351
353
|
// A store with no log db at all («has no log store yet» — e.g. the daemon's own tree-derived universe) cannot
|
|
352
354
|
// hold the run, so it must not block the ledger lookup. A store that exists but failed to open/read still does:
|
|
@@ -356,6 +358,20 @@ export function handleTrace(req: Request, opts: MetaApiOpts, deps: TraceDeps = {
|
|
|
356
358
|
try { resolution = findRunUniverse(runId, names, deps); }
|
|
357
359
|
catch (error) { return jsonResponse({ ok: false, error: 'run-universe-unavailable', reason: error instanceof Error ? error.message : String(error) }, 503); }
|
|
358
360
|
try { debug.log('nexus.trace', 'run-universe-resolved', { runId, universe: resolution.universe ?? 'not-found', checked: resolution.checked }); } catch { /* observation must not block reads */ }
|
|
361
|
+
if (resolution.universe) resolvedFrom = 'ledger';
|
|
362
|
+
else {
|
|
363
|
+
let views: LogInstanceView[];
|
|
364
|
+
try { views = (deps.instances ?? readLogInstances)(); }
|
|
365
|
+
catch (error) { return jsonResponse({ ok: false, error: 'run-universe-unavailable', reason: error instanceof Error ? error.message : String(error) }, 503); }
|
|
366
|
+
resolution = scanStoresForRun(runId, views, (view) => {
|
|
367
|
+
const store = (deps.openRemoteStore ?? ((v: LogInstanceView) => LogStore.openReadOnly(v.dbPath)))(view);
|
|
368
|
+
if (!store) return null;
|
|
369
|
+
return { queryTraceRun: (id, query) => store.queryTraceRun(id, query),
|
|
370
|
+
close: () => (deps.closeRemoteStore ?? ((s: LogStore) => s.close()))(store) };
|
|
371
|
+
}, { sinceMs: parsed.from, untilMs: parsed.to });
|
|
372
|
+
if (resolution.universe) resolvedFrom = 'log-scan';
|
|
373
|
+
try { debug.log('nexus.trace', 'run-universe-scanned', { runId, universe: resolution.universe ?? (resolution.truncated || resolution.unresolved ? 'unresolved' : 'not-found'), checked: resolution.checked, truncated: resolution.truncated }); } catch { /* observation must not block reads */ }
|
|
374
|
+
}
|
|
359
375
|
if (resolution.universe && !names.includes(resolution.universe)) {
|
|
360
376
|
const name = resolution.universe;
|
|
361
377
|
names.push(name);
|
|
@@ -373,9 +389,9 @@ export function handleTrace(req: Request, opts: MetaApiOpts, deps: TraceDeps = {
|
|
|
373
389
|
}
|
|
374
390
|
}
|
|
375
391
|
const filtered = rows.filter((row) => matches(row, url)).sort(sortRows);
|
|
376
|
-
const trace = buildTrace(filtered.slice(0, parsed.limit), parsed.level, filtered.length > parsed.limit || candidateLimitReached || failures.length > 0);
|
|
392
|
+
const trace = buildTrace(filtered.slice(0, parsed.limit), parsed.level, filtered.length > parsed.limit || candidateLimitReached || failures.length > 0 || resolution?.truncated === true);
|
|
377
393
|
try { (deps.queryLog ?? ((details) => debug.log('logs.trace', 'query', details)))({ stores: names, level: parsed.level, count: trace.events.length, truncated: trace.truncated, mode: 'rows', runs: trace.nodes.filter((node) => node.level === 'L1' && node.count > 0).length }); } catch { /* query logging must not block reads */ }
|
|
378
|
-
return jsonResponse({ ok: true, ...trace, stores: names, registeredStores: registeredStoreCount(deps), ...(resolution ? resolution.universe ? { resolvedFrom: '
|
|
394
|
+
return jsonResponse({ ok: true, ...trace, stores: names, registeredStores: registeredStoreCount(deps), ...(resolution ? resolution.universe ? { resolvedFrom, runUniverse: resolution.universe, ...(resolvedFrom === 'log-scan' ? { checked: resolution.checked } : {}) } : { runUniverse: resolution.truncated || resolution.unresolved ? 'unresolved' as const : 'not-found' as const, checked: resolution.checked } : {}), ...(failures.length ? { failedStores: failures } : {}) }, 200);
|
|
379
395
|
} finally { closeStores(stores, deps); }
|
|
380
396
|
}
|
|
381
397
|
|
package/src/nexus/index.ts
CHANGED
|
@@ -2609,10 +2609,16 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
|
|
|
2609
2609
|
runTurnImpl,
|
|
2610
2610
|
getBot: () => getBotRef.current,
|
|
2611
2611
|
});
|
|
2612
|
+
// DEC-TG(대표 10-02) — decision cards in the owner's Discord DM. Filled once the bot exists (below).
|
|
2613
|
+
let decisionWire: import('../decisions/discord-decision-cards.js').DiscordDecisionWire | null = null;
|
|
2612
2614
|
const composedOnMessage = async (
|
|
2613
2615
|
ctx: import('../discord.js').DcIncoming,
|
|
2614
2616
|
streamer?: import('../discord.js').DcMessageStreamer,
|
|
2615
2617
|
): Promise<string | void> => {
|
|
2618
|
+
if (decisionWire && ctx.text.trim() === '/decisions') {
|
|
2619
|
+
const { discordDecisionsCommand } = await import('../decisions/discord-decision-cards.js');
|
|
2620
|
+
return discordDecisionsCommand(decisionWire.service, ctx);
|
|
2621
|
+
}
|
|
2616
2622
|
const voiceReply = await voiceWire.dispatchVoiceCommand(ctx);
|
|
2617
2623
|
if (voiceReply !== null) return voiceReply;
|
|
2618
2624
|
return selfOnMessage(ctx, streamer as never);
|
|
@@ -2626,13 +2632,17 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
|
|
|
2626
2632
|
getBot: () => getBotRef.current,
|
|
2627
2633
|
allowedUsers: [...cfg.discord.allowedUsers],
|
|
2628
2634
|
});
|
|
2635
|
+
const chatChannels = cfg.discord.chatChannels ?? [];
|
|
2636
|
+
debug.log('nexus.discord', 'chat-channels', { count: chatChannels.length });
|
|
2629
2637
|
const handle = createNexusDiscordTriggerBot({
|
|
2630
2638
|
token: discordToken,
|
|
2631
2639
|
allowedUsers: [...cfg.discord.allowedUsers],
|
|
2640
|
+
...(chatChannels.length ? { guildTextChannels: chatChannels } : {}),
|
|
2632
2641
|
dispatch: (event) => workflowDaemon!.dispatchDiscord(event),
|
|
2633
2642
|
onMessage: composedOnMessage,
|
|
2634
2643
|
onInteraction: async (raw) => {
|
|
2635
|
-
// C1 버튼 탭
|
|
2644
|
+
// DEC-TG 결정 카드(dec:/decmemo:) → C1 버튼 탭(elanous-q: 소비) → 아니면 C3 슬래시.
|
|
2645
|
+
if (decisionWire && await decisionWire.onInteraction(raw)) return;
|
|
2636
2646
|
if (await questionRuntime.handleComponentInteraction(raw)) return;
|
|
2637
2647
|
await slashWire.onInteraction(raw);
|
|
2638
2648
|
},
|
|
@@ -2641,6 +2651,14 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
|
|
|
2641
2651
|
if (handle) {
|
|
2642
2652
|
getBotRef.current = handle.bot;
|
|
2643
2653
|
workflowDiscordBot = handle;
|
|
2654
|
+
if ((cfg.raw?.decisions as { cards?: unknown } | undefined)?.cards !== false) {
|
|
2655
|
+
try {
|
|
2656
|
+
const { attachDiscordDecisionCards } = await import('../decisions/discord-decision-cards.js');
|
|
2657
|
+
decisionWire = attachDiscordDecisionCards(handle.bot, cfg);
|
|
2658
|
+
} catch (err) {
|
|
2659
|
+
console.warn(`[nexus] decision cards (discord) not attached: ${err instanceof Error ? err.message : String(err)}`);
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2644
2662
|
void slashWire.registerCommands().catch((err: unknown) => {
|
|
2645
2663
|
console.warn(`[nexus] slash registration failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2646
2664
|
});
|
|
@@ -2916,6 +2934,15 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
|
|
|
2916
2934
|
: readyCount === 1 ? readyServerIds[0] : undefined,
|
|
2917
2935
|
readyCount,
|
|
2918
2936
|
});
|
|
2937
|
+
// TERM1 ④ — the first web-terminal spawn used to pay the login-shell env capture (spawnSync · ~3 s with the
|
|
2938
|
+
// daemon frozen) and the terminal module import (~0.9 s). Warm both off the event loop shortly after boot.
|
|
2939
|
+
if (!opts.startNexusHttpServerFn) {
|
|
2940
|
+
const warm = setTimeout(() => {
|
|
2941
|
+
void import('../shell-env-bootstrap.js').then(({ warmCapturedEnv }) => warmCapturedEnv()).catch(() => undefined);
|
|
2942
|
+
void import('../web-terminal/preview-tap-registry.js').then(() => import('../preview/terminal.js')).catch(() => undefined);
|
|
2943
|
+
}, 1_500);
|
|
2944
|
+
(warm as { unref?: () => void }).unref?.();
|
|
2945
|
+
}
|
|
2919
2946
|
httpServer = startHttpServer({
|
|
2920
2947
|
state,
|
|
2921
2948
|
registry,
|