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
package/src/discord.ts
CHANGED
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
import { markdownToTelegramHtml as _unusedMarkdownFn } from './telegram-format.js'; // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
24
24
|
import { debug } from './debug/log.js';
|
|
25
25
|
import { getUserConfig } from './user-config.js';
|
|
26
|
+
import { handleDiscordSeatWork, type DiscordSeatWorkDeps } from './intake-plane/discord-seat-work.js';
|
|
27
|
+
import { parseSeatAddress } from './seat-address/seat-address.js';
|
|
26
28
|
|
|
27
29
|
// Telegram's HTML markdown isn't directly useful for Discord (Discord
|
|
28
30
|
// uses CommonMark + its own subset). Import kept as a reminder that
|
|
@@ -163,6 +165,8 @@ export interface DiscordBotOpts {
|
|
|
163
165
|
* primary `onMessage` LLM chat path. Errors are swallowed by the
|
|
164
166
|
* caller — this is a best-effort fan-out. */
|
|
165
167
|
onTriggerTap?: (event: DcTriggerEvent) => void | Promise<void>;
|
|
168
|
+
/** Override the intake door's dependencies for isolated Discord seat-work tests. */
|
|
169
|
+
seatWorkDeps?: DiscordSeatWorkDeps;
|
|
166
170
|
}
|
|
167
171
|
|
|
168
172
|
/** Surface-unification v2 — minimal trigger-side event shape. Maps
|
|
@@ -204,6 +208,8 @@ export class DiscordBot {
|
|
|
204
208
|
private readonly token: string;
|
|
205
209
|
private readonly allowedUsers: Set<string>;
|
|
206
210
|
private readonly guildTextChannels: Set<string>;
|
|
211
|
+
/** The bot's own user id (from READY) — EV12c lets an allowlisted @mention open any guild channel. */
|
|
212
|
+
private selfUserId: string | undefined;
|
|
207
213
|
private readonly onMessage: DcMessageHandler;
|
|
208
214
|
private readonly maxChars: number;
|
|
209
215
|
private readonly log: (msg: string) => void;
|
|
@@ -216,6 +222,7 @@ export class DiscordBot {
|
|
|
216
222
|
private readonly onReaction: ((event: DcReactionEvent) => void | Promise<void>) | null;
|
|
217
223
|
/** Surface-unification v2 (FU-1) — workflow-runtime tap. */
|
|
218
224
|
private readonly onTriggerTap: ((event: DcTriggerEvent) => void | Promise<void>) | null;
|
|
225
|
+
private readonly seatWorkDeps: DiscordSeatWorkDeps;
|
|
219
226
|
private ws: WebSocket | null = null;
|
|
220
227
|
private running = false;
|
|
221
228
|
private heartbeatTimer: ReturnType<typeof setInterval> | null = null;
|
|
@@ -239,6 +246,7 @@ export class DiscordBot {
|
|
|
239
246
|
this.onInteraction = opts.onInteraction ?? null;
|
|
240
247
|
this.onReaction = opts.onReaction ?? null;
|
|
241
248
|
this.onTriggerTap = opts.onTriggerTap ?? null;
|
|
249
|
+
this.seatWorkDeps = opts.seatWorkDeps ?? {};
|
|
242
250
|
}
|
|
243
251
|
|
|
244
252
|
/** Send a raw payload on the gateway WebSocket. Voice adapter uses
|
|
@@ -490,6 +498,23 @@ export class DiscordBot {
|
|
|
490
498
|
return r?.id ? { id: r.id } : null;
|
|
491
499
|
}
|
|
492
500
|
|
|
501
|
+
/** DEC-TG — edit a message's text AND components (an empty list removes the buttons). */
|
|
502
|
+
async editMessageWithComponents(
|
|
503
|
+
channelId: string,
|
|
504
|
+
messageId: string,
|
|
505
|
+
text: string,
|
|
506
|
+
components: ReadonlyArray<Record<string, unknown>>,
|
|
507
|
+
): Promise<void> {
|
|
508
|
+
const content = text.length > this.maxChars ? text.slice(0, this.maxChars - 4) + ' …' : text;
|
|
509
|
+
await this.restCall('PATCH', `/channels/${channelId}/messages/${messageId}`, { content, components });
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/** DEC-TG — the DM channel id with one user (Discord returns the existing one when it already exists). */
|
|
513
|
+
async openDmChannel(userId: string): Promise<string | null> {
|
|
514
|
+
const r = await this.restCall<{ id?: string }>('POST', '/users/@me/channels', { recipient_id: userId });
|
|
515
|
+
return r?.id ?? null;
|
|
516
|
+
}
|
|
517
|
+
|
|
493
518
|
// ── Gateway: connect + run forever ──────────────────────────
|
|
494
519
|
|
|
495
520
|
/** Connect to the Gateway, identify, and process events until
|
|
@@ -604,6 +629,7 @@ export class DiscordBot {
|
|
|
604
629
|
session_id?: string;
|
|
605
630
|
};
|
|
606
631
|
this.log(`READY — logged in as @${d.user?.username ?? '?'} (${d.user?.id ?? '?'})`);
|
|
632
|
+
if (d.user?.id) this.selfUserId = d.user.id;
|
|
607
633
|
if (this.voiceTap?.onReady && d.session_id && d.user?.id) {
|
|
608
634
|
try { this.voiceTap.onReady(d.session_id, d.user.id); }
|
|
609
635
|
catch (err: any) { this.log(`voice tap onReady failed: ${err?.message ?? err}`); }
|
|
@@ -684,6 +710,20 @@ export class DiscordBot {
|
|
|
684
710
|
|
|
685
711
|
// ── Message routing ────────────────────────────────────────
|
|
686
712
|
|
|
713
|
+
/** EV12c — true when this message @mentions the bot itself (mentions array or the raw `<@id>` token). */
|
|
714
|
+
private mentionsSelf(m: Record<string, unknown>, text: string): boolean {
|
|
715
|
+
const self = this.selfUserId;
|
|
716
|
+
if (!self) return false;
|
|
717
|
+
if (Array.isArray(m.mentions) && (m.mentions as Array<{ id?: unknown }>).some(u => String(u?.id) === self)) return true;
|
|
718
|
+
return text.includes(`<@${self}>`) || text.includes(`<@!${self}>`);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
private stripSelfMention(text: string): string {
|
|
722
|
+
const self = this.selfUserId;
|
|
723
|
+
if (!self) return text;
|
|
724
|
+
return text.split(`<@!${self}>`).join('').split(`<@${self}>`).join('').replace(/\s+/g, ' ').trim();
|
|
725
|
+
}
|
|
726
|
+
|
|
687
727
|
private async handleMessageCreate(m: Record<string, unknown>): Promise<void> {
|
|
688
728
|
// Ignore bot's own messages (DM reply from our own send would
|
|
689
729
|
// re-enter here).
|
|
@@ -692,19 +732,25 @@ export class DiscordBot {
|
|
|
692
732
|
|
|
693
733
|
// DM-only v1 scope: guild_id is absent for DM channels.
|
|
694
734
|
// Voice channel commands (`/voice-join`, `/voice-leave`,
|
|
695
|
-
// `/voice-status`)
|
|
696
|
-
//
|
|
697
|
-
//
|
|
735
|
+
// `/voice-status`) and allowlisted, seat-addressed work bypass
|
|
736
|
+
// DM-only. All other guild traffic stays gated unless explicitly
|
|
737
|
+
// configured as a guild text channel.
|
|
698
738
|
const isDm = !m.guild_id;
|
|
739
|
+
const rawText = typeof m.content === 'string' ? m.content : '';
|
|
740
|
+
const mentionsSelf = this.mentionsSelf(m, rawText);
|
|
699
741
|
if (!isDm && !this.guildTextChannels.has(String(m.channel_id))) {
|
|
700
|
-
|
|
701
|
-
|
|
742
|
+
if (!/^\/voice-(join|leave|status)\b/.test(rawText.trim())
|
|
743
|
+
&& !(this.allowedUsers.has(author.id) && (parseSeatAddress(rawText) || mentionsSelf))) {
|
|
744
|
+
debug.log('discord.trigger', 'dropped', { reason: 'guild-not-chat', channelId: String(m.channel_id) });
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
702
747
|
}
|
|
703
748
|
|
|
704
749
|
// Allowlist enforcement. An empty allowlist refuses everyone —
|
|
705
750
|
// safer default than "open to the world" when the bot runs
|
|
706
751
|
// with no user config.
|
|
707
752
|
if (this.allowedUsers.size > 0 && !this.allowedUsers.has(author.id)) {
|
|
753
|
+
debug.log('discord.trigger', 'dropped', { reason: 'not-allowed', isDm });
|
|
708
754
|
this.log(`discord: refusing unknown user ${author.id}`);
|
|
709
755
|
try {
|
|
710
756
|
await this.sendMessage(
|
|
@@ -738,7 +784,7 @@ export class DiscordBot {
|
|
|
738
784
|
channelId: String(m.channel_id),
|
|
739
785
|
userId: author.id,
|
|
740
786
|
userName: author.username,
|
|
741
|
-
text:
|
|
787
|
+
text: mentionsSelf ? this.stripSelfMention(rawText) : rawText,
|
|
742
788
|
messageId: String(m.id),
|
|
743
789
|
isDm,
|
|
744
790
|
attachments,
|
|
@@ -790,6 +836,27 @@ export class DiscordBot {
|
|
|
790
836
|
}
|
|
791
837
|
}
|
|
792
838
|
|
|
839
|
+
// Addressed work is submitted only by configured owners; ordinary chat keeps its existing path.
|
|
840
|
+
if (this.allowedUsers.has(author.id) && parseSeatAddress(ctx.text)) {
|
|
841
|
+
const threadId = typeof m.thread_id === 'string' && m.thread_id ? m.thread_id : undefined;
|
|
842
|
+
const replyChannelId = threadId ?? ctx.channelId;
|
|
843
|
+
try {
|
|
844
|
+
const seatReply = await handleDiscordSeatWork(ctx.text, {
|
|
845
|
+
channelId: replyChannelId,
|
|
846
|
+
messageId: ctx.messageId,
|
|
847
|
+
...(threadId ? { threadId } : {}),
|
|
848
|
+
}, this.seatWorkDeps);
|
|
849
|
+
if (seatReply !== null) {
|
|
850
|
+
await this.sendMessage(replyChannelId, seatReply);
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
} catch (err: unknown) {
|
|
854
|
+
this.log(`discord seat work failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
855
|
+
await this.sendMessage(replyChannelId, '좌석 요청을 접수하지 못했습니다. 잠시 후 다시 보내 주세요.');
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
793
860
|
// §C5 스트리밍 flip — streaming.discord ON 이면 옛 경로(placeholder+streamer+delivery) 억제,
|
|
794
861
|
// 청크 fan-out sink 가 담당(handler chunkProducer 가 primarySurfaces=['discord']). 텔레그램 동형.
|
|
795
862
|
const dcStreamingFlip = getUserConfig().sessionFabric?.streaming?.discord === true;
|
|
@@ -15,6 +15,8 @@ import { existsSync } from 'node:fs';
|
|
|
15
15
|
import { Database } from 'bun:sqlite';
|
|
16
16
|
import type { LLMToolSpec } from '../llm.js';
|
|
17
17
|
import { debug } from '../debug/log.js';
|
|
18
|
+
import { runRecall, type RecallSearch } from '../tool-runtime/self-cognition-runtimes.js';
|
|
19
|
+
import type { ToolRuntimeContext } from '../tool-runtime/types.js';
|
|
18
20
|
import { SCHEDULE_MANAGE_SPEC, dispatchScheduleManage } from './schedule-manage-tool.js';
|
|
19
21
|
import { SESSION_MANAGE_SPEC, dispatchSessionQuery } from './session-query-tool.js';
|
|
20
22
|
import { AUTOPILOT_MISSION_SPEC, dispatchAutopilotMissions } from '../autopilot/mission-tool.js';
|
|
@@ -43,6 +45,7 @@ const MEMORY_RECALL_SPEC: LLMToolSpec = {
|
|
|
43
45
|
sinceHours: { type: 'number', description: '조회 기간 시간(기본 168=7일).' },
|
|
44
46
|
limit: { type: 'number', description: '반환 건수(기본 8).' },
|
|
45
47
|
restoreId: { type: 'string', description: '느린 복원(선택) — archived 후보의 id 를 지정하면 그 cold 기억을 S3 에서 복원(recall_count++·warm 재활성화)해 본문 반환. 회상 결과 archived 에 관련 후보가 있고 본문이 필요할 때만.' },
|
|
48
|
+
timeBudgetMs: { type: 'number', description: '기억 조회 시간 예산(밀리초·기본 60000). 초과 시 찾은 것까지 부분 반환.' },
|
|
46
49
|
},
|
|
47
50
|
required: [],
|
|
48
51
|
},
|
|
@@ -50,7 +53,7 @@ const MEMORY_RECALL_SPEC: LLMToolSpec = {
|
|
|
50
53
|
|
|
51
54
|
/** memory_recall 코어(db 주입·테스트 가능). restoreId 지정 시 느린 복원(축B P2), 아니면 회상 + archive 발견.
|
|
52
55
|
* recency+importance+relevance 스코어. restoreDeps 는 테스트 S3 mock(생략 시 기본 S3). */
|
|
53
|
-
export function recallOrRestore(mdb: Database, args: Record<string, unknown>, restoreDeps?: ArchiveS3Deps): unknown {
|
|
56
|
+
export function recallOrRestore(mdb: Database, args: Record<string, unknown>, restoreDeps?: ArchiveS3Deps, onPartial?: (result: Record<string, unknown>) => void): unknown {
|
|
54
57
|
// ★ 축B P2 느린 복원 — restoreId 지정 시 cold 기억을 S3 에서 명시적 복원(recall_count++·warm 재활성화).
|
|
55
58
|
// 자동 아님(명시적) = 복원 폭주 방지. 죽어있던 restoreFromArchive 호출부 배선.
|
|
56
59
|
if (typeof args.restoreId === 'string' && args.restoreId) {
|
|
@@ -75,15 +78,17 @@ export function recallOrRestore(mdb: Database, args: Record<string, unknown>, re
|
|
|
75
78
|
...(typeof args.sinceHours === 'number' ? { sinceHours: args.sinceHours } : {}),
|
|
76
79
|
...(typeof args.limit === 'number' ? { limit: args.limit } : {}),
|
|
77
80
|
});
|
|
81
|
+
const mappedHits = hits.map(h => ({
|
|
82
|
+
when: h.ts, surface: h.surface, kind: h.kind,
|
|
83
|
+
text: h.text.slice(0, 400), importance: h.importance,
|
|
84
|
+
score: Math.round(h.score * 1000) / 1000,
|
|
85
|
+
}));
|
|
86
|
+
onPartial?.({ hits: mappedHits, count: hits.length });
|
|
78
87
|
// M2 — cold(S3 보관) 기억 메타 발견(본문 fetch 없음·빠름). 존재+id 알리고 복원은 restoreId 로 명시적.
|
|
79
88
|
const q = typeof args.query === 'string' ? String(args.query) : '';
|
|
80
89
|
const archived = q ? searchArchive(mdb, q, { ...(typeof args.domain === 'string' && args.domain ? { domain: String(args.domain) } : {}), limit: 3 }) : [];
|
|
81
90
|
return {
|
|
82
|
-
hits:
|
|
83
|
-
when: h.ts, surface: h.surface, kind: h.kind,
|
|
84
|
-
text: h.text.slice(0, 400), importance: h.importance,
|
|
85
|
-
score: Math.round(h.score * 1000) / 1000,
|
|
86
|
-
})),
|
|
91
|
+
hits: mappedHits,
|
|
87
92
|
count: hits.length,
|
|
88
93
|
...(archived.length ? { archived: archived.map(a => ({ id: a.id, when: a.ts, summary: a.summary, note: 'cold·S3 보관(restoreId 로 느린 복원)' })) } : {}),
|
|
89
94
|
note: '내가 발송/통지한 것의 원장 회상(크로스서피스 기억·hot/warm). archived=흐려져 S3 로 이관된 cold 기억(존재+id·본문은 restoreId 로 느린 복원). 비었으면 해당 기간 발송 없음.',
|
|
@@ -91,18 +96,18 @@ export function recallOrRestore(mdb: Database, args: Record<string, unknown>, re
|
|
|
91
96
|
}
|
|
92
97
|
|
|
93
98
|
/** memory_recall 공유 구현(finance-tools 에서 이관). db 열기 + recallOrRestore 위임. */
|
|
94
|
-
async function dispatchMemoryRecall(args: Record<string, unknown>): Promise<unknown> {
|
|
99
|
+
async function dispatchMemoryRecall(args: Record<string, unknown>, onPartial?: (result: Record<string, unknown>) => void): Promise<unknown> {
|
|
95
100
|
if (!existsSync(surfaceEventsDbPath())) {
|
|
96
101
|
return { hits: [], note: '발송 원장 미생성 — 아직 기록된 발송이 없음(데몬 재시작 후 sendOutbound부터 적재).' };
|
|
97
102
|
}
|
|
98
103
|
const mdb = openSurfaceEventsDb();
|
|
99
|
-
try { return recallOrRestore(mdb, args); }
|
|
104
|
+
try { return recallOrRestore(mdb, args, undefined, onPartial); }
|
|
100
105
|
catch (e) { return { error: `기억 회상 실패: ${e instanceof Error ? e.message.slice(0, 100) : String(e)}` }; }
|
|
101
106
|
finally { mdb.close(); }
|
|
102
107
|
}
|
|
103
108
|
|
|
104
109
|
/** L2 코어 앱 도구 dispatch 핸들러 — 이름 → 구현. 새 코어 도구는 여기 추가. */
|
|
105
|
-
const CORE_TOOL_HANDLERS: Record<string, (args: Record<string, unknown>) => Promise<unknown>> = {
|
|
110
|
+
const CORE_TOOL_HANDLERS: Record<string, (args: Record<string, unknown>, onPartial?: (result: Record<string, unknown>) => void) => Promise<unknown>> = {
|
|
106
111
|
schedule_manage: dispatchScheduleManage,
|
|
107
112
|
session_manage: (args) => dispatchSessionQuery(args), // 대화 세션 검색·열람·목록·삭제(내용/ID/텔레그램)
|
|
108
113
|
memory_recall: dispatchMemoryRecall,
|
|
@@ -115,26 +120,44 @@ const CORE_TOOL_HANDLERS: Record<string, (args: Record<string, unknown>) => Prom
|
|
|
115
120
|
mission_decide: dispatchMissionDecide, // 미션 결정 기록(re-ground·defer·boundary…)·3박자 주입·코어
|
|
116
121
|
};
|
|
117
122
|
|
|
123
|
+
/** Worker-side search entry: dispatch must not re-enter its own budget wrapper. */
|
|
124
|
+
export function dispatchRecallUnbudgeted(name: 'memory_recall' | 'self_recall', args: Record<string, unknown>, onPartial: (result: Record<string, unknown>) => void): Promise<unknown> {
|
|
125
|
+
return CORE_TOOL_HANDLERS[name]!(args, onPartial);
|
|
126
|
+
}
|
|
127
|
+
|
|
118
128
|
/** L2 코어 앱 도구 spec 목록(전 서피스 공용). */
|
|
119
|
-
|
|
129
|
+
const SELF_RECALL_BUDGET_SPEC: LLMToolSpec = {
|
|
130
|
+
...SELF_RECALL_SPEC,
|
|
131
|
+
parameters: {
|
|
132
|
+
...SELF_RECALL_SPEC.parameters,
|
|
133
|
+
properties: {
|
|
134
|
+
...(SELF_RECALL_SPEC.parameters.properties as Record<string, unknown>),
|
|
135
|
+
timeBudgetMs: { type: 'number', description: '기억 조회 시간 예산(밀리초·기본 60000). 초과 시 찾은 것까지 부분 반환.' },
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
export const CORE_TOOL_SPECS: LLMToolSpec[] = [SCHEDULE_MANAGE_SPEC, SESSION_MANAGE_SPEC, MEMORY_RECALL_SPEC, FACT_CHECK_SPEC, SELF_RECALL_BUDGET_SPEC, AUTOPILOT_MISSION_SPEC, OPS_STATUS_SPEC, SE_BUILD_SPEC, LOGS_QUERY_SPEC, MISSION_DECIDE_SPEC];
|
|
120
140
|
|
|
121
141
|
export interface CoreTools {
|
|
122
142
|
specs: LLMToolSpec[];
|
|
123
143
|
names: Set<string>;
|
|
124
|
-
dispatch: (name: string, args: Record<string, unknown>) => Promise<unknown>;
|
|
144
|
+
dispatch: (name: string, args: Record<string, unknown>, onPartial?: (result: Record<string, unknown>) => void, context?: ToolRuntimeContext) => Promise<unknown>;
|
|
125
145
|
}
|
|
126
146
|
|
|
127
147
|
/** 서피스 무관 코어 앱 도구 세트. 모든 서피스가 이걸 조립 → 코어 도구 단일 출처 상속.
|
|
128
148
|
* L1(native)·L3(finance 팩)와 조합해 쓴다. dispatch 는 이름으로 라우팅(미등록=error). */
|
|
129
|
-
export function buildCoreTools(): CoreTools {
|
|
149
|
+
export function buildCoreTools(recallSearch?: RecallSearch): CoreTools {
|
|
130
150
|
const names = new Set(CORE_TOOL_SPECS.map(s => s.name));
|
|
131
151
|
return {
|
|
132
152
|
specs: CORE_TOOL_SPECS,
|
|
133
153
|
names,
|
|
134
|
-
dispatch: async (name, args) => {
|
|
154
|
+
dispatch: async (name, args, onPartial, context) => {
|
|
135
155
|
const h = CORE_TOOL_HANDLERS[name];
|
|
136
156
|
if (!h) return { error: `unknown core tool: ${name}` };
|
|
137
|
-
|
|
157
|
+
if (name === 'memory_recall' || name === 'self_recall') {
|
|
158
|
+
return runRecall(name, args, context ?? { surface: 'skill' }, recallSearch, onPartial);
|
|
159
|
+
}
|
|
160
|
+
return h(args, onPartial);
|
|
138
161
|
},
|
|
139
162
|
};
|
|
140
163
|
}
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
import * as childProcess from 'node:child_process';
|
|
8
8
|
import { existsSync, readFileSync, appendFileSync, mkdirSync, unlinkSync } from 'node:fs';
|
|
9
|
-
import { dirname, join } from 'node:path';
|
|
9
|
+
import { dirname, join, relative } from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
10
11
|
import { getElanousConfigDir } from '../elanous-config-dir.js';
|
|
11
12
|
import { conatusEnv } from './conatus-env.js';
|
|
12
13
|
import { spillLongContent } from '../storage/content-spill.js';
|
|
@@ -17,6 +18,7 @@ import { resolveChannelBotToken } from '../channel-bot-token.js';
|
|
|
17
18
|
import { kindRouteTarget } from './telegram-kind-route.js';
|
|
18
19
|
import { debug } from '../debug/log.js';
|
|
19
20
|
import { resolveDaemonEndpoint } from '../nexus/daemon-endpoint.js';
|
|
21
|
+
import { effectiveInstanceRoot, prodInstanceRoot } from '../instance/resolve.js';
|
|
20
22
|
// ★ origin 되돌림(대표 2026-07-12) — 미션 알림을 발신 채널(메인 Q&A 봇)로 되돌린다. type-only
|
|
21
23
|
// import 라 런타임 순환 없음(발송 로직은 이 파일에 self-contained). origin 없으면 report 폴백.
|
|
22
24
|
import type { MissionOrigin } from '../autopilot/mission-origin.js';
|
|
@@ -54,7 +56,7 @@ function recentSendBurst(): { recentCount: number; burst: boolean } {
|
|
|
54
56
|
|
|
55
57
|
/** 발송 1건을 logs.db 에 관측(fail-open) — 발송 시각·mode·kind·밀림 판정. */
|
|
56
58
|
function logSend(
|
|
57
|
-
mode: 'realtime' | '
|
|
59
|
+
mode: 'realtime' | 'quiet-bypass' | 'flush',
|
|
58
60
|
kind: string,
|
|
59
61
|
extra: Record<string, unknown> = {},
|
|
60
62
|
opts?: { level?: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'critical' },
|
|
@@ -111,15 +113,16 @@ export function userRecentlyActive(now: Date = new Date()): boolean {
|
|
|
111
113
|
|
|
112
114
|
/** 야간 보류 적재 (jsonl append — 크론 동시 실행에 안전). origin 있으면 함께 적재 —
|
|
113
115
|
* 아침 flush 가 그 origin(발신 채널)으로 되돌려 발송(없으면 report 묶음). */
|
|
114
|
-
function deferOutbound(text: string, kind: string, origin?: MissionOrigin | null, path = DEFERRED_PATH):
|
|
116
|
+
function deferOutbound(text: string, kind: string, origin?: MissionOrigin | null, path = DEFERRED_PATH): boolean {
|
|
115
117
|
try {
|
|
116
118
|
mkdirSync(dirname(path), { recursive: true });
|
|
117
119
|
const rec = { ts: new Date().toISOString(), kind, text, ...(origin ? { origin } : {}) };
|
|
118
120
|
appendFileSync(path, JSON.stringify(rec) + '\n');
|
|
119
121
|
console.error(`[outbound] 야간 무음(00:00~06:30 KST) — 보류 적재 (${kind})`);
|
|
120
|
-
|
|
122
|
+
return true;
|
|
121
123
|
} catch (e) {
|
|
122
124
|
console.error(`[outbound] 보류 적재 실패 — 콘솔 출력\n${text}`, e instanceof Error ? e.message : '');
|
|
125
|
+
return false;
|
|
123
126
|
}
|
|
124
127
|
}
|
|
125
128
|
|
|
@@ -139,8 +142,29 @@ function resolveBotToken(botId?: string): string | null {
|
|
|
139
142
|
} catch { return null; }
|
|
140
143
|
}
|
|
141
144
|
|
|
142
|
-
/** origin(발신 채널)으로 직접
|
|
145
|
+
/** origin(발신 채널)으로 직접 발송. 실패하면 호출자가 report 폴백/재시도한다. */
|
|
143
146
|
function deliverToOrigin(origin: MissionOrigin, text: string): boolean {
|
|
147
|
+
if (origin.channel === 'discord') {
|
|
148
|
+
if (!origin.channelId) return false;
|
|
149
|
+
try {
|
|
150
|
+
const token = resolveChannelBotToken('discord', getUserConfig())?.token;
|
|
151
|
+
if (!token) return false;
|
|
152
|
+
const channelId = origin.discordThreadId || origin.channelId;
|
|
153
|
+
const out = spillLongContent(text).text;
|
|
154
|
+
for (let start = 0; start < out.length;) {
|
|
155
|
+
let end = Math.min(start + 2000, out.length);
|
|
156
|
+
if (end < out.length && /[\uD800-\uDBFF]/.test(out[end - 1]!)) end--;
|
|
157
|
+
const response = curlPost(
|
|
158
|
+
`https://discord.com/api/v10/channels/${channelId}/messages`,
|
|
159
|
+
JSON.stringify({ content: out.slice(start, end) }),
|
|
160
|
+
[`Authorization: Bot ${token}`, 'Content-Type: application/json'],
|
|
161
|
+
);
|
|
162
|
+
if (typeof response?.id !== 'string' || !response.id) return false;
|
|
163
|
+
start = end;
|
|
164
|
+
}
|
|
165
|
+
return out.length > 0;
|
|
166
|
+
} catch { return false; }
|
|
167
|
+
}
|
|
144
168
|
if (origin.channel !== 'telegram' || origin.chatId == null) return false;
|
|
145
169
|
const token = resolveBotToken(origin.botId);
|
|
146
170
|
if (!token) return false;
|
|
@@ -189,11 +213,13 @@ export function flushDeferred(path = DEFERRED_PATH): number {
|
|
|
189
213
|
// origin 없는 것 = report 묶음(기존 동작·finance/크론 알림).
|
|
190
214
|
const noOrigin = items.filter(i => !i.origin);
|
|
191
215
|
if (noOrigin.length && deliver(fmt(noOrigin), 'report')) delivered += noOrigin.length;
|
|
192
|
-
//
|
|
216
|
+
// origin 있는 것 = 발신 채널·스레드별로 묶어 서로 다른 수신자에게 섞이지 않게 발송.
|
|
193
217
|
const groups = new Map<string, { origin: MissionOrigin; list: Item[] }>();
|
|
194
218
|
for (const it of items) {
|
|
195
219
|
if (!it.origin) continue;
|
|
196
|
-
const key =
|
|
220
|
+
const key = it.origin.channel === 'discord'
|
|
221
|
+
? JSON.stringify(['discord', it.origin.channelId, it.origin.discordThreadId ?? null])
|
|
222
|
+
: JSON.stringify([it.origin.channel, it.origin.botId ?? null, it.origin.chatId ?? null, it.origin.threadId ?? null]);
|
|
197
223
|
if (!groups.has(key)) groups.set(key, { origin: it.origin, list: [] });
|
|
198
224
|
groups.get(key)!.list.push(it);
|
|
199
225
|
}
|
|
@@ -228,14 +254,26 @@ export function recordOutbound(text: string, kind: string): void {
|
|
|
228
254
|
/** 발송 진입점 — 야간 무음 게이트(00:00~06:30 KST 보류) + 보류분 자동 플러시.
|
|
229
255
|
* origin(발신 채널) 이 주어지면 그 채널(메인 Q&A 봇 등)로 되돌려 발송 — 무음이면 origin 을
|
|
230
256
|
* 함께 보류했다가 아침에 그 채널로 flush. origin 없거나 발송 실패 시 report 폴백(기존 동작). */
|
|
231
|
-
export function sendOutbound(text: string, kind = 'alert', origin?: MissionOrigin | null): boolean {
|
|
257
|
+
export function sendOutbound(text: string, kind = 'alert', origin?: (MissionOrigin & { surface?: string }) | null): boolean {
|
|
258
|
+
// Prefer the producer's surface; otherwise keep the caller's repository-relative path
|
|
259
|
+
// so two producers named index.ts cannot collapse into one sender.
|
|
260
|
+
const caller = new Error().stack?.split('\n')[2] ?? '';
|
|
261
|
+
const callerPath = caller.match(/(?:\(|\s)(file:\/\/[^\s()]+|\/[^\s()]+\.[cm]?[jt]s):\d+:\d+\)?/)?.[1];
|
|
262
|
+
const repoRoot = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
263
|
+
const source = origin?.surface || (callerPath
|
|
264
|
+
? relative(repoRoot, callerPath.startsWith('file://') ? fileURLToPath(callerPath) : callerPath).replace(/\\/g, '/')
|
|
265
|
+
: 'unknown');
|
|
266
|
+
const logOutcome = (event: 'sent' | 'deferred' | 'failed'): void => {
|
|
267
|
+
try { debug.log('outbound.send', event, { kind, source, chars: text.length }); } catch { /* fail-open */ }
|
|
268
|
+
};
|
|
232
269
|
// ★ 무음 우회(대표 2026-07-14) — 사용자가 최근(기본 30분) genuine 인텐트(타이핑/버튼탭)를 냈으면
|
|
233
270
|
// 깨어있으므로 야간 무음이어도 즉시 발송(사용자 발원 흐름의 결과물이 아침까지 묶이지 않게).
|
|
234
271
|
// 우회 시 flushDeferred 로 그간 보류분도 함께 전달(사용자가 지금 볼 수 있음).
|
|
235
272
|
if (inQuietHours() && !userRecentlyActive()) {
|
|
236
|
-
deferOutbound(text, kind, origin);
|
|
273
|
+
const queued = deferOutbound(text, kind, origin);
|
|
237
274
|
recordOutbound(text, kind); // 보류도 논리적 발송 — 회상 대상(데몬 미경유라 클라 기록)
|
|
238
|
-
|
|
275
|
+
logOutcome(queued ? 'deferred' : 'failed');
|
|
276
|
+
return true; // 보류 시 수락 — 적재 오류에서도 기존 호출측 재시도 계약 유지
|
|
239
277
|
}
|
|
240
278
|
const bypass = inQuietHours();
|
|
241
279
|
if (bypass) console.error('[outbound] 야간 무음 우회 — 최근 사용자 활동(깨어있음) → 즉시 발송 + 보류분 flush');
|
|
@@ -245,14 +283,16 @@ export function sendOutbound(text: string, kind = 'alert', origin?: MissionOrigi
|
|
|
245
283
|
const { recentCount, burst } = recentSendBurst();
|
|
246
284
|
logSend(bypass ? 'quiet-bypass' : 'realtime', kind, { burst, recentCount, ...(burst ? { backlog: true } : {}) });
|
|
247
285
|
// ★ origin 되돌림(무음 밖) — 발신 채널로 직접 발송. 성공 시 종료, 실패면 report 폴백.
|
|
248
|
-
if (origin &&
|
|
286
|
+
if (origin && deliverToOrigin(origin, text)) {
|
|
249
287
|
recordOutbound(text, kind);
|
|
288
|
+
logOutcome('sent');
|
|
250
289
|
return true;
|
|
251
290
|
}
|
|
252
291
|
const path = deliver(text, kind);
|
|
253
292
|
// 원장 기록은 정확히 1회. 데몬 경유(daemon)면 /v1/outbound 핸들러(outbound-report.ts)
|
|
254
293
|
// 가 기록하므로 클라는 중복 금지 — 직접 폴백(direct·데몬 다운)만 클라가 기록.
|
|
255
294
|
if (path === 'direct') recordOutbound(text, kind);
|
|
295
|
+
logOutcome(path === false ? 'failed' : 'sent');
|
|
256
296
|
return path !== false;
|
|
257
297
|
}
|
|
258
298
|
|
|
@@ -278,9 +318,31 @@ function logDaemonPath(classification: DaemonPathClass, kind: string, extra: Rec
|
|
|
278
318
|
try { console.error(`[outbound] daemon-path ${classification}`); } catch { /* fail-soft */ }
|
|
279
319
|
}
|
|
280
320
|
|
|
321
|
+
/** OB8 — the daemon registers its own in-process sender at startup. Inside the daemon, deliver() must never curl
|
|
322
|
+
* its own `/v1/outbound`: the curl is synchronous, the event loop that would answer it is the one it blocks, and the
|
|
323
|
+
* whole daemon freezes until the curl times out (~25 s · `unreachable`) — the message is lost (CS1 · 10-01). */
|
|
324
|
+
export type InProcessOutbound = (text: string, kind: string) => Promise<boolean>;
|
|
325
|
+
let inProcessOutbound: InProcessOutbound | null = null;
|
|
326
|
+
export function setInProcessOutbound(send: InProcessOutbound | null): void { inProcessOutbound = send; }
|
|
327
|
+
|
|
281
328
|
/** elanous `/v1/outbound` 우선 → 실패 시 텔레그램 직접. 성공 경로 반환(원장 중복방지용). */
|
|
282
329
|
export function deliver(text: string, kind = 'alert'): 'daemon' | 'direct' | false {
|
|
330
|
+
// 0) inside the daemon — route in-process, asynchronously; the caller's synchronous answer is «accepted».
|
|
331
|
+
if (inProcessOutbound && process.env.SEND_VIA_ELANOUS !== '0') {
|
|
332
|
+
const send = inProcessOutbound;
|
|
333
|
+
void send(text, kind)
|
|
334
|
+
.then((ok) => {
|
|
335
|
+
logDaemonPath(ok ? 'ok' : 'rejected', kind, { inProcess: true });
|
|
336
|
+
if (!ok && sendTelegramDirect(text, kind)) recordOutbound(text, kind);
|
|
337
|
+
})
|
|
338
|
+
.catch((error: unknown) => {
|
|
339
|
+
logDaemonPath('rejected', kind, { inProcess: true, error: error instanceof Error ? error.message : String(error) });
|
|
340
|
+
if (sendTelegramDirect(text, kind)) recordOutbound(text, kind);
|
|
341
|
+
});
|
|
342
|
+
return 'daemon';
|
|
343
|
+
}
|
|
283
344
|
// 1) elanous 단일 발송 지점(/v1/outbound) — 데몬이 팬아웃 + 원장 기록.
|
|
345
|
+
let daemonPath: DaemonPathClass | 'not-found' | 'disabled' = 'disabled';
|
|
284
346
|
if (process.env.SEND_VIA_ELANOUS !== '0') {
|
|
285
347
|
let token = '';
|
|
286
348
|
try { if (existsSync(ACP_TOKEN_PATH)) token = readFileSync(ACP_TOKEN_PATH, 'utf-8').trim(); } catch { /* no token */ }
|
|
@@ -291,12 +353,32 @@ export function deliver(text: string, kind = 'alert'): 'daemon' | 'direct' | fal
|
|
|
291
353
|
: null;
|
|
292
354
|
const classification = classifyDaemonResponse(j);
|
|
293
355
|
if (classification === 'ok') return 'daemon';
|
|
356
|
+
daemonPath = nexus ? classification : 'not-found';
|
|
294
357
|
const extra: Record<string, unknown> = { hasToken: token.length > 0 };
|
|
295
358
|
if (j && typeof j === 'object' && 'error' in (j as object)) extra.error = (j as { error?: unknown }).error;
|
|
296
359
|
logDaemonPath(classification, kind, extra);
|
|
297
360
|
}
|
|
298
361
|
// 2) fallback: 텔레그램 sendMessage 직접(3900자 분할) — 데몬 미경유라 클라가 원장 기록.
|
|
299
|
-
|
|
362
|
+
if (sendTelegramDirect(text, kind)) return 'direct';
|
|
363
|
+
reportUndeliverable(kind, daemonPath);
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/** OB8b — both paths failed: say so loudly, with the universe this process resolved, instead of a silent false.
|
|
368
|
+
* The usual cause (10-01 · MK): an ad-hoc `bun -e` or a script from a source tree resolves a cwd-derived test
|
|
369
|
+
* universe, so it finds neither the production daemon nor the production bot token. */
|
|
370
|
+
function reportUndeliverable(kind: string, daemonPath: DaemonPathClass | 'not-found' | 'disabled'): void {
|
|
371
|
+
let root = '?';
|
|
372
|
+
let universe: 'prod' | 'test' | '?' = '?';
|
|
373
|
+
try {
|
|
374
|
+
root = effectiveInstanceRoot();
|
|
375
|
+
universe = root === prodInstanceRoot() ? 'prod' : 'test';
|
|
376
|
+
} catch { /* the report still goes out */ }
|
|
377
|
+
try { debug.log('outbound.send', 'undeliverable', { kind, daemonPath, universe, root }); } catch { /* fail-soft */ }
|
|
378
|
+
const hint = universe === 'prod'
|
|
379
|
+
? '운영 데몬이 떠 있는지 확인: elanous nexus show'
|
|
380
|
+
: '운영으로 보내려면 설치본 elanous 로 실행하거나 ELANOUS_STATE_DIR=~/.elanous ELANOUS_CONFIG_DIR=~/.elanous 를 준다';
|
|
381
|
+
try { console.error(`[outbound] ⛔ 못 보냄(${kind}) — 데몬 ${daemonPath} · 직접 발송도 실패(토큰 없음 또는 전송 실패) · 이 프로세스의 우주 ${universe} (${root}) · ${hint}`); } catch { /* fail-soft */ }
|
|
300
382
|
}
|
|
301
383
|
|
|
302
384
|
/** 텔레그램 raw 발송(토큰·chatId 명시) — spill + 3900자 분할(줄 경계). thread 지원. */
|
|
@@ -79,7 +79,7 @@ export function explainFailure(group: RepeatedFailureGroup): string {
|
|
|
79
79
|
// the first live tick saw it as `webterm.tabs` (list errors re-raise the socket close) and sent the generic line.
|
|
80
80
|
if (group.area.startsWith('webterm.') && /^socket closed: #: auth_failed$/i.test(group.reason)) {
|
|
81
81
|
// Same words as the PWA banners (🅞 #21591·#21602 · 🅕 #21599) so the alert and the screen say one thing.
|
|
82
|
-
return '이 기기(브라우저)에 소유자 토큰이 없거나 틀리거나 만료돼 데몬이 연결을 거절한다 — 이미 연결된 기기의 설정 ›
|
|
82
|
+
return '이 기기(브라우저)에 소유자 토큰이 없거나 틀리거나 만료돼 데몬이 연결을 거절한다 — 이미 연결된 기기의 설정 › 연결 토큰 만들기 에서 토큰을 만들어, 이 기기의 설정 › 데몬 연결 › 연결 토큰 칸(/app/settings/#bearer-token)에 붙인다';
|
|
83
83
|
}
|
|
84
84
|
const span = Date.parse(group.lastTs) - Date.parse(group.firstTs);
|
|
85
85
|
const minutes = Number.isFinite(span) ? Math.max(1, Math.ceil(span / 60_000)) : 10;
|