elanous 0.2.4 → 0.2.6
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/README.md +7 -6
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/1169-696263ac7af8c4d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1883.c91a2648af4842fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1947-9d64872eeb9cfdec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2808-dc06482994b40f70.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 2984-977cfa339fcd65a5.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/3165-95515ca5f299a120.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8142-011e1f474efb8b33.js → 3190-e9282753e7ebde85.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3995-bee512a7063036ce.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6062-73e68343a9814f6a.js → 4240-7153881b0b377306.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4259-99614149577e77cb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4283-329eabeaf233e6cd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4328-cb5058b1acc2de5e.js +25 -0
- package/apps/pwa/out/_next/static/chunks/4801-ecee6ef62b349b18.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5110-8058791dd25a2e14.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3403.4bc97b4066945a94.js → 5329.8b7e4bca684b6eec.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/5330-c41e9670f594c38f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5423-e95fd125d1794349.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5478-b5eae07dc79ea619.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5787-96ee6642b9a661e5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6448-79d72368589f98bc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6522-9866c002b5531836.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6729-b4a558b7aa891e0c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6969-cb03f9410b7d2e03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7007-3b9861091bb8dd21.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5791-bb82743fa6299562.js → 763-7d0e1913b5074e98.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{4323.c5dfb6f503304775.js → 7633.0654fe6ea259c1ee.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7930-8dc7e20fc6fc19ca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8071-187edbc22a86741f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8198-d4101ad70cb9a579.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8250.4a2c5553a07cdf47.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8293-123b91e7b0330dc0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8296-d6d5d87dfa911d41.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8305-603963c33ee8eec3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8612-4a0066f20951b8fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2ea96958-e8c7e112de50b067.js → 8d0d9faf-97887ff99713b84e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9181-cf4e1b61c4783f92.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8046cadf-211ca3c0e00f2b72.js → 91ff6dda-3b33d42d20657429.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9262-26c2ecbd5a67e4a2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9328-45abdebce4fbecb7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9355-dbc67205adf8745f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9763.255b90dd69875689.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9933-66d31ec25c0231af.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-bd06fb55f4da0381.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-4e586142da455a25.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-889ad5cddcf45b0d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-47ba401d56572c27.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-0b54f9c83458e038.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-4fcc296ef6c8ed90.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-fe9fda494a87f4e5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-f9642e25ce129590.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-e1063e8ba5f3e2e7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-9d0df30027d8aa9d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-bd06fb55f4da0381.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-d97adf2c9455b8d0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-236b80d202110362.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-21a30f35f1a276ad.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-b1a5be357c4fa25d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1705a60303f7f573.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-76b337c11ad0e237.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-7c0f85fbb56f64c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-65ca16b20617b44d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-e7f9b8fc29411c1d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-92ea6213d1aeec04.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a913a005aacb5476.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-2d78804a40dfdef6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-08a99148e780ac00.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-deb674e7ed36f815.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-49f8de0534d192d7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-bd06fb55f4da0381.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-ba162a28d2176e18.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-1ff1a37b550ab41a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-ccaa698f56c0fbdb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-53b944082de1d7db.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-9b70adf1552789bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-30d482aa63241fe8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-aecf786f54651d8a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6ab9b62c7b6e08f2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-08949735455e0c9a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-a1479005234a46c2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3890e90bd232732.js +1 -0
- package/apps/pwa/out/_next/static/chunks/beb5cf10-9adcc10b83930090.js +1 -0
- package/apps/pwa/out/_next/static/chunks/framework-d68d41c88e05c801.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-11c63971b7a9593b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-d09452353bd0d9cc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-184d727cebeca767.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-21bbbbac99b720b1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{webpack-e5bea36f183b25e6.js → webpack-37b8bf78a3c6eec5.js} +1 -1
- package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +1 -0
- package/apps/pwa/out/_next/static/{zoQX5iTvXOsIvCjxCe1Ji → qMI0jnlZaBjjm4aIDYNGM}/_buildManifest.js +1 -1
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +29 -27
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +29 -27
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +29 -27
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +26 -24
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +26 -24
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +26 -24
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +29 -27
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +29 -27
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +29 -27
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/icons/elanous-16.png +0 -0
- package/apps/pwa/out/icons/elanous-180.png +0 -0
- package/apps/pwa/out/icons/elanous-192.png +0 -0
- package/apps/pwa/out/icons/elanous-32.png +0 -0
- package/apps/pwa/out/icons/elanous-512.png +0 -0
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +26 -24
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +29 -27
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +29 -27
- package/apps/pwa/out/manifest.webmanifest +5 -2
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +26 -24
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +26 -24
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +29 -27
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +29 -27
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +27 -25
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +29 -27
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +26 -24
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +29 -27
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +29 -27
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +29 -27
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +29 -27
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +29 -27
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +26 -24
- package/apps/pwa/out/sw-strategies.js +44 -0
- package/apps/pwa/out/sw.js +6 -44
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +29 -27
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +29 -27
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +29 -27
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +29 -27
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +29 -27
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +29 -27
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +29 -27
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +29 -27
- package/catalog/resources.yaml +6 -6
- package/docs/goal-context/steward-directives-2026-09-28.md +35 -37
- package/graphs/landing/landing-heal.yaml +1 -1
- package/graphs/release/recipes.yaml +11 -1
- package/graphs/release/release-loop.yaml +9 -2
- package/graphs/video/README.md +1 -0
- package/graphs/video/explainer-line.yaml +58 -0
- package/package.json +3 -1
- package/plugins/card-followup/README.md +33 -0
- package/plugins/card-followup/examples/card-sample.json +7 -0
- package/plugins/card-followup/graphs/card-followup.yaml +16 -0
- package/plugins/card-followup/graphs/recipes.yaml +12 -0
- package/plugins/card-followup/graphs/run-step.ts +172 -0
- package/plugins/card-followup/plugin.json +25 -0
- package/plugins/geo-check/README.md +13 -0
- package/plugins/geo-check/examples/brand-sample.json +8 -0
- package/plugins/geo-check/graphs/geo-check.yaml +20 -0
- package/plugins/geo-check/graphs/recipes.yaml +18 -0
- package/plugins/geo-check/graphs/run-step.ts +177 -0
- package/plugins/geo-check/plugin.json +24 -0
- package/plugins/job-coach/.codex-plugin/plugin.json +18 -1
- package/plugins/job-coach/README.md +42 -0
- package/plugins/job-coach/connectors/ncs/server.ts +4 -3
- package/plugins/job-coach/graphs/gap-judge.ts +44 -0
- package/plugins/job-coach/graphs/recipes.yaml +27 -0
- package/plugins/job-coach/graphs/report-enterprise.yaml +28 -0
- package/plugins/job-coach/graphs/run-step.ts +211 -17
- package/plugins/job-coach/plugin.json +16 -3
- package/plugins/job-coach/skills/enterprise-ai-fit/SKILL.md +8 -0
- package/plugins/job-coach/skills/enterprise-competency/SKILL.md +8 -0
- package/plugins/job-coach/skills/enterprise-needs/SKILL.md +8 -0
- package/plugins/job-coach/skills/enterprise-roadmap/SKILL.md +8 -0
- package/plugins/job-coach/skills/enterprise-task-analysis/SKILL.md +8 -0
- package/plugins/job-coach/skills/interview-to-profile/SKILL.md +18 -1
- package/scripts/backup/elanous-backup.sh +4 -1
- package/scripts/build-docs-site.ts +4 -3
- package/scripts/coord-channel-watch.sh +23 -4
- package/scripts/coord-post.sh +49 -12
- package/scripts/coord-tracks.json +5 -5
- package/scripts/daemon-port-baseline.txt +1 -2
- package/scripts/eval-bot-vs-grok.ts +79 -0
- package/scripts/eval-grok-live-baseline.ts +36 -0
- package/scripts/fixtures/front-route-corpus.sample.json +17 -0
- package/scripts/fleet/node-setup.sh +46 -7
- package/scripts/landing-heal/must-fix-match.ts +26 -0
- package/scripts/landing-heal/run-mission.ts +114 -20
- package/scripts/lib/android-test-device.ts +32 -0
- package/scripts/lib/front-route-measurement.ts +131 -0
- package/scripts/lib/tui-sim-keys.ts +42 -0
- package/scripts/measure-front-route-classifier.ts +56 -0
- package/scripts/media/ocr-leak-scan.sh +21 -0
- package/scripts/public-export.ts +2 -3
- package/scripts/public-import-pr.ts +80 -6
- package/scripts/publish-internal-dist.ts +2 -3
- package/scripts/red-sweep-diff.ts +1 -1
- package/scripts/release-loop/auto-approve-node.ts +72 -0
- package/scripts/release-loop/checklist-gate-node.ts +35 -0
- package/scripts/release-loop/gate-diff.ts +29 -0
- package/scripts/release-loop/gate-node.ts +366 -45
- package/scripts/release-loop/publish-node.ts +7 -3
- package/scripts/release-loop/release-docs.ts +20 -13
- package/scripts/release-loop/shard-plan.ts +47 -0
- package/scripts/release-loop/test-durations.ts +63 -0
- package/scripts/release-loop/upgrade-node.ts +3 -2
- package/scripts/test-audit-coverage-per-file.sh +16 -0
- package/scripts/test-audit-meaningless.ts +88 -0
- package/scripts/test-audit-overlap-and-weak.ts +73 -0
- package/scripts/test-deterministic.ts +27 -5
- package/scripts/tui-sim.ts +59 -14
- package/scripts/ux/bump-installed.sh +20 -0
- package/scripts/ux/lib/cdp.ts +72 -0
- package/scripts/ux/record-page.ts +53 -0
- package/skills/explainer-video/SKILL.md +117 -0
- package/skills/explainer-video/engine/brand/elanous-mark-on-dark.svg +1 -0
- package/skills/explainer-video/engine/build.mjs +431 -0
- package/skills/explainer-video/engine/reel.mjs +128 -0
- package/skills/explainer-video/engine/reel.sh +8 -0
- package/skills/explainer-video/engine/vo.ts +79 -0
- package/skills/explainer-video/examples/elanous-0.2.5/script.json +155 -0
- package/src/agent/nested-agent-env.ts +1 -0
- package/src/agent-mission/driver.ts +95 -16
- package/src/agent-mission/mission-cli.ts +6 -0
- package/src/agent-mission/resource-ladder.ts +73 -25
- package/src/auth/acp-token.ts +16 -0
- package/src/auth/github-app-token.ts +112 -0
- package/src/autopilot/gh-rest-fallback.ts +177 -0
- package/src/autopilot/mission-origin.ts +20 -4
- package/src/autopilot/mission-phase-track.ts +11 -1
- package/src/autopilot/pr-manager.ts +51 -4
- package/src/autopilot/track-agent.ts +3 -3
- package/src/autopilot/track-courier.ts +2 -2
- package/src/boot/acp-server.ts +2 -16
- package/src/boot/daemon-tools/web-search.ts +2 -1
- package/src/boot/ws-bridge.ts +11 -0
- package/src/chat/index.ts +37 -8
- package/src/chat/log-scroll-key.ts +12 -0
- package/src/chat/submit-trace.ts +30 -0
- package/src/cli/agent-cli.ts +7 -2
- package/src/cli/decisions-cli.ts +94 -0
- package/src/cli/doctor-cli.ts +40 -54
- package/src/cli/doctor-readiness.ts +16 -24
- package/src/cli/intake-cli.ts +1 -1
- package/src/cli/mcp-call.ts +9 -0
- package/src/cli/mcp-gateway-cli.ts +7 -3
- package/src/cli/nexus-ios-bind.ts +25 -8
- package/src/cli/phone-link.ts +117 -0
- package/src/cli/plugin-cli.ts +136 -11
- package/src/cli/pr-lineage.ts +2 -1
- package/src/cli/private-ledger-commands.ts +31 -0
- package/src/cli/pwa-start.ts +3 -0
- package/src/cli/release-cli.ts +97 -7
- package/src/cli/self-update.ts +7 -4
- package/src/cli/start-cli.ts +33 -14
- package/src/dashboard/acp-boot.ts +3 -0
- package/src/dashboard/index.ts +62 -13
- package/src/dashboard/input/no-provider-reply.ts +13 -0
- package/src/dashboard/slash-runtime/unknown-slash-reply.ts +78 -0
- package/src/dashboard/stale-install-check.ts +77 -0
- package/src/decisions/decision-ledger.ts +404 -0
- package/src/domains/outbound-alert.ts +13 -7
- package/src/domains/telegram-kind-route.ts +21 -0
- package/src/execution-loop/dispatch-task.ts +139 -0
- package/src/execution-loop/gate-decision-summary.ts +29 -0
- package/src/execution-loop/gates.ts +197 -0
- package/src/field/field-media.ts +245 -0
- package/src/graph-kinds/installed-plugin-nodes.ts +95 -0
- package/src/graph-kinds/plugin-nodes.ts +10 -2
- package/src/graph-runner/runner.ts +35 -5
- package/src/grounding/token.ts +12 -6
- package/src/harness/harness-cli-command.ts +68 -9
- package/src/harness/harness-pod-dispatch.ts +8 -2
- package/src/harness/harness-stop.ts +147 -18
- package/src/harness/self-send-decide.ts +26 -17
- package/src/harness/self-send-target.ts +22 -1
- package/src/harness/worktree-branch-prefix.ts +4 -3
- package/src/index.ts +50 -13
- package/src/instance/resolve.ts +6 -0
- package/src/intake-plane/collect-telegram-saved.ts +5 -0
- package/src/intake-plane/digest.ts +11 -2
- package/src/live/detail-switch.ts +8 -3
- package/src/llm/usable-llm.ts +32 -0
- package/src/loops/registry.ts +27 -5
- package/src/market/publish.ts +59 -3
- package/src/native-tool-catalog.ts +2 -2
- package/src/nexus/api/field-uploads.ts +143 -0
- package/src/nexus/api/graph-approvals.ts +174 -0
- package/src/nexus/api/harness-api.ts +219 -5
- package/src/nexus/api/http-server.ts +69 -6
- package/src/nexus/api/log-fabric.ts +10 -1
- package/src/nexus/api/mcp-oauth-hint.ts +10 -9
- package/src/nexus/api/merge-approvals.ts +2 -1
- package/src/nexus/api/meta-api.ts +14 -0
- package/src/nexus/api/plugins-credentials-api.ts +47 -0
- package/src/nexus/api/plugins-market.ts +87 -3
- package/src/nexus/api/pod-credential-api.ts +68 -14
- package/src/nexus/api/public-routes.ts +7 -0
- package/src/nexus/api/trace.ts +57 -6
- package/src/nexus/boot/register-mcp-clients.ts +99 -14
- package/src/nexus/index.ts +33 -32
- package/src/nexus/outbound/router.ts +1 -1
- package/src/notify/llm-budget-alert.ts +119 -0
- package/src/onboarding.ts +25 -2
- package/src/plugins/core/host.ts +14 -0
- package/src/plugins/install/market-fetch.ts +4 -3
- package/src/plugins/install/plugin-credentials.ts +106 -0
- package/src/plugins/install/plugin-install.ts +163 -32
- package/src/plugins/maker/plugin-maker.ts +208 -0
- package/src/prompt-library/universal-preamble.ts +8 -2
- package/src/relay/channel.ts +184 -0
- package/src/relay/mux.ts +182 -0
- package/src/relay/test-vectors.json +29 -0
- package/src/release-loop/checklist.ts +217 -0
- package/src/release-loop/release-note.ts +6 -3
- package/src/seat-address/seat-address.ts +34 -0
- package/src/self-dev/dev-cli.ts +40 -20
- package/src/self-dev/dev-pipeline.ts +77 -1
- package/src/self-implement/gate-baseline.ts +3 -1
- package/src/self-implement/goal-author.ts +18 -1
- package/src/self-implement/host-regate.ts +6 -2
- package/src/self-implement/orchestrator.ts +7 -2
- package/src/self-implement/run-ledger-compact.ts +1 -1
- package/src/self-implement/seams.ts +4 -2
- package/src/session/index.ts +23 -10
- package/src/skills/tools/dev-harness.ts +42 -0
- package/src/steward/steward-cards.ts +45 -0
- package/src/steward/triage.ts +50 -14
- package/src/task-orchestrator/surfaces/pod-artifact-return.ts +61 -3
- package/src/task-orchestrator/surfaces/pod-bun-cache.ts +18 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +36 -6
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +39 -9
- package/src/task-orchestrator/surfaces/pod-source-bundle.ts +16 -1
- package/src/task-orchestrator/surfaces/pod-source-receive.ts +51 -13
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +172 -28
- package/src/telegram-report.ts +12 -6
- package/src/telegram.ts +108 -1
- package/src/tui-client/acp-transport-ws-client.ts +72 -5
- package/src/tui.ts +3 -2
- package/src/user-config.ts +32 -2
- package/src/util/spawn-sync-output.ts +15 -0
- package/src/version/code-revision.ts +1 -1
- package/src/version/packed-revision.json +1 -1
- package/src/video-pipeline/recipes/explainer.ts +213 -0
- package/src/video-pipeline/walk-line.ts +3 -2
- package/src/web-search/free.ts +77 -0
- package/src/web-search/index.ts +2 -0
- package/src/workflow-runtime/deps-bridge.ts +4 -3
- package/src/workflow-runtime/plugin-kind-node.ts +32 -4
- package/src/workflow-runtime/types.ts +2 -1
- package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1191-7b13a24117a689fa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2375-3fcae5186c64fe6b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +0 -25
- package/apps/pwa/out/_next/static/chunks/319-d37df1b6e0cbeff4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4836-3c93795ab834381e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6090-1bd6077591603947.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7457-f5cb1cc8db9f7453.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8067-f74bf14d67ad98f1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8519-0059d82bdb4812bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9740-52467f244755917f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9804-64c02c3c127d5014.js +0 -1
- package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-21c3261af6ab377c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-1991445b8ea86274.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-4ebfaee2369fe248.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-9fa879ebf5959096.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-ca7c3cc305c3285a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-9951930add3d2051.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-4472ddf1e2458e69.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-4de08d4b766f134b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-7a975e2b9db1dfb7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-49270714ef35bb96.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-33fbdf353e2a5982.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-259f122e2dd64317.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-65b82726753658e1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-4b8df3748dc06285.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-112f7e477bda77fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-542a5c0006da637c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-63ffb347831262ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-98e4e359d97263b4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-97e24ac675ce576e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-ac6391f26391d89d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-9f45c9d45ffc43bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-36995b9cb5d98218.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-0c795b577988d63f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b4311e2f71c6735a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-eb2381074df20dba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-b01336e64eba01ae.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-7af7f21c1f2858ef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-a98ecd364586c89b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-57f0502dfbfa089b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-3467d2e1abd8a14a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-7842ca23d53ba682.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-ae9ac882912b0198.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-3a0d72e3f783bcef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-f3b39fe8358c2a97.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-5a22af3181ac2afb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-1457627bb302a22c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +0 -1
- package/apps/pwa/out/_next/static/css/e66b7f5efc8df3f3.css +0 -1
- /package/apps/pwa/out/_next/static/{zoQX5iTvXOsIvCjxCe1Ji → qMI0jnlZaBjjm4aIDYNGM}/_ssgManifest.js +0 -0
package/src/telegram-report.ts
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
// the report channel while interactive Q&A stays on the main bot.
|
|
12
12
|
|
|
13
13
|
import { TelegramBot } from './telegram.js';
|
|
14
|
+
import { kindRouteTarget } from './domains/telegram-kind-route.js';
|
|
15
|
+
export { DEFAULT_KIND_ROLES, roleForKind } from './domains/telegram-kind-route.js';
|
|
14
16
|
import { resolveChannelBotToken } from './channel-bot-token.js';
|
|
15
17
|
import type { UserConfig } from './user-config.js';
|
|
16
18
|
import { findSessionByTelegramChat, createSession, appendMessage } from './session/index.js';
|
|
@@ -49,11 +51,13 @@ export interface ReportTarget {
|
|
|
49
51
|
chatId: number;
|
|
50
52
|
}
|
|
51
53
|
|
|
52
|
-
/** Resolve the
|
|
53
|
-
*
|
|
54
|
-
* token when the report channel is served by the
|
|
55
|
-
*
|
|
56
|
-
export function resolveReportTarget(cfg: UserConfig): ReportTarget | null {
|
|
54
|
+
/** Resolve the send target. With a purpose `kind` that maps to a role and explicit `telegram.channels`,
|
|
55
|
+
* the channel holding that role wins (fallback: the `default` channel). Otherwise the legacy report
|
|
56
|
+
* channel: `botToken` falls back to the main Q&A bot token when the report channel is served by the
|
|
57
|
+
* same bot. Returns null when nothing is configured. */
|
|
58
|
+
export function resolveReportTarget(cfg: UserConfig, kind?: string): ReportTarget | null {
|
|
59
|
+
const routed = kindRouteTarget(cfg, kind);
|
|
60
|
+
if (routed) return routed;
|
|
57
61
|
const rc = cfg.telegram.reportChannel;
|
|
58
62
|
if (!rc || !Number.isFinite(rc.chatId)) return null;
|
|
59
63
|
const botToken = rc.botToken ?? resolveChannelBotToken('telegram', cfg)?.token;
|
|
@@ -67,6 +71,8 @@ export interface SendReportOpts {
|
|
|
67
71
|
markdown?: boolean;
|
|
68
72
|
/** DI seam for tests — passed through to the send-only TelegramBot. */
|
|
69
73
|
fetchImpl?: typeof fetch;
|
|
74
|
+
/** Purpose of the message — routes to a channel role (see `DEFAULT_KIND_ROLES`). */
|
|
75
|
+
kind?: string;
|
|
70
76
|
}
|
|
71
77
|
|
|
72
78
|
/** Send a report to the configured report channel. Returns false (a
|
|
@@ -78,7 +84,7 @@ export async function sendTelegramReport(
|
|
|
78
84
|
text: string,
|
|
79
85
|
opts: SendReportOpts = {},
|
|
80
86
|
): Promise<boolean> {
|
|
81
|
-
const target = resolveReportTarget(cfg);
|
|
87
|
+
const target = resolveReportTarget(cfg, opts.kind);
|
|
82
88
|
if (!target || !text) return false;
|
|
83
89
|
const bot = new TelegramBot({
|
|
84
90
|
token: target.botToken,
|
package/src/telegram.ts
CHANGED
|
@@ -67,6 +67,8 @@ import { runUrlRoute } from './skills/url-route-exec.js';
|
|
|
67
67
|
import { recordRoutedLinkAbsorbed } from './intake-plane/link-ledger.js';
|
|
68
68
|
import { classifyFrontInput } from './intake-plane/front-classifier.js';
|
|
69
69
|
import { effectiveInstanceRoot } from './instance/resolve.js';
|
|
70
|
+
import { getElanousConfigDir } from './elanous-config-dir.js';
|
|
71
|
+
import { defaultFieldEvent, FieldUploadError, parseFieldCaption, resolveFieldMime, saveFieldMedia } from './field/field-media.js';
|
|
70
72
|
|
|
71
73
|
export type TgAttachmentKind = 'photo' | 'voice' | 'audio' | 'document';
|
|
72
74
|
|
|
@@ -105,6 +107,10 @@ export interface TgIncoming {
|
|
|
105
107
|
botId?: string;
|
|
106
108
|
/** 답장 대상 메시지의 텍스트(force_reply 정정 가로채기용). parseUpdate 가 채운다. */
|
|
107
109
|
replyToText?: string;
|
|
110
|
+
/** 앨범(media group) id — `#현장` 캡션은 앨범의 첫 항목에만 붙으므로 나머지를 같은 행사로 묶는 데 쓴다. */
|
|
111
|
+
mediaGroupId?: string;
|
|
112
|
+
/** 영상 — `#현장` 저장 전용. `attachments` 에 넣지 않는다(LLM 경로의 동작을 안 바꾸려고). */
|
|
113
|
+
video?: TgIncomingAttachment;
|
|
108
114
|
}
|
|
109
115
|
|
|
110
116
|
/** Streaming surface passed to the message handler. The handler can
|
|
@@ -200,6 +206,10 @@ export interface TelegramBotOpts {
|
|
|
200
206
|
* independently of the LLM chat path. Best-effort: errors are
|
|
201
207
|
* swallowed by the caller — never blocks chat reply. */
|
|
202
208
|
onTriggerTap?: (event: TgTriggerEvent) => void | Promise<void>;
|
|
209
|
+
/** `#현장` 기본 행사 — 없으면 `slashContext.userConfig.telegram.fieldDefaultEvent` · 그것도 없으면 `field-<로컬 날짜>`. */
|
|
210
|
+
fieldDefaultEvent?: string;
|
|
211
|
+
/** 시험 seam — 현장 폴더 뿌리(기본 `getElanousConfigDir()`). */
|
|
212
|
+
fieldRootDir?: () => string;
|
|
203
213
|
}
|
|
204
214
|
|
|
205
215
|
/** Surface-unification v2 (FU-2) — Telegram trigger event passed to
|
|
@@ -314,6 +324,13 @@ export class TelegramBot {
|
|
|
314
324
|
* the bot under Telegram's ~30/sec global cap without blocking
|
|
315
325
|
* further if load is bursty but average-safe. */
|
|
316
326
|
private readonly globalWindow: number[] = [];
|
|
327
|
+
private readonly fieldDefaultEventOpt?: string;
|
|
328
|
+
private readonly fieldRootDir: () => string;
|
|
329
|
+
/** 앨범 id → `#현장` 행사 · 답장 디바운스. 태그된 앨범만 들어온다. */
|
|
330
|
+
private readonly fieldGroups = new Map<string, {
|
|
331
|
+
event: string; at: number; chatId: number; threadId?: number; replyTo: number; count: number;
|
|
332
|
+
timer?: ReturnType<typeof setTimeout>;
|
|
333
|
+
}>();
|
|
317
334
|
|
|
318
335
|
constructor(opts: TelegramBotOpts) {
|
|
319
336
|
if (!opts.token) throw new Error('TelegramBot: token required');
|
|
@@ -340,6 +357,8 @@ export class TelegramBot {
|
|
|
340
357
|
this.slashCommands = opts.slashCommands ?? [];
|
|
341
358
|
this.slashContext = opts.slashContext;
|
|
342
359
|
this.voiceAdapter = opts.voiceAdapter;
|
|
360
|
+
this.fieldDefaultEventOpt = opts.fieldDefaultEvent;
|
|
361
|
+
this.fieldRootDir = opts.fieldRootDir ?? getElanousConfigDir;
|
|
343
362
|
if (opts.onTriggerTap) {
|
|
344
363
|
// Assign through `as unknown` to bypass `readonly` + private —
|
|
345
364
|
// the tap is set once at construction and never reassigned.
|
|
@@ -1347,11 +1366,82 @@ export class TelegramBot {
|
|
|
1347
1366
|
return false;
|
|
1348
1367
|
}
|
|
1349
1368
|
|
|
1369
|
+
/** `#현장` 캡션 사진·영상(이미지·영상 문서 포함)을 현장 폴더에 저장하고 한 줄로 답한다.
|
|
1370
|
+
* 처리했으면 true. 저장 규칙은 HTTP 입구와 같은 `saveFieldMedia` 다. */
|
|
1371
|
+
private async tryHandleFieldUpload(ctx: TgIncoming): Promise<boolean> {
|
|
1372
|
+
const media = [
|
|
1373
|
+
...ctx.attachments.filter((a) => a.kind === 'photo'
|
|
1374
|
+
|| (a.kind === 'document' && resolveFieldMime(a.mimeType, a.fileName ?? '') !== null)),
|
|
1375
|
+
...(ctx.video ? [ctx.video] : []),
|
|
1376
|
+
];
|
|
1377
|
+
if (media.length === 0) return false;
|
|
1378
|
+
const now = this.nowImpl();
|
|
1379
|
+
for (const [id, g] of this.fieldGroups) if (now - g.at > 10 * 60_000 && !g.timer) this.fieldGroups.delete(id);
|
|
1380
|
+
const tag = parseFieldCaption(ctx.text);
|
|
1381
|
+
const group = ctx.mediaGroupId ? this.fieldGroups.get(ctx.mediaGroupId) : undefined;
|
|
1382
|
+
if (!tag && !group) return false;
|
|
1383
|
+
const event = tag
|
|
1384
|
+
? tag.event ?? defaultFieldEvent(
|
|
1385
|
+
this.fieldDefaultEventOpt ?? this.slashContext?.userConfig?.telegram?.fieldDefaultEvent, new Date(now))
|
|
1386
|
+
: group!.event;
|
|
1387
|
+
|
|
1388
|
+
let count: number | null = null;
|
|
1389
|
+
let failure: string | null = null;
|
|
1390
|
+
try {
|
|
1391
|
+
const inputs = [];
|
|
1392
|
+
for (const att of media) {
|
|
1393
|
+
const downloaded = await this.downloadFile(att.fileId);
|
|
1394
|
+
try {
|
|
1395
|
+
inputs.push({
|
|
1396
|
+
originalName: att.fileName ?? downloaded.fileName ?? 'media',
|
|
1397
|
+
mimeType: att.mimeType ?? (att.kind === 'photo' ? 'image/jpeg' : undefined),
|
|
1398
|
+
bytes: new Uint8Array(readFileSync(downloaded.localPath)),
|
|
1399
|
+
});
|
|
1400
|
+
} finally {
|
|
1401
|
+
try { unlinkSync(downloaded.localPath); } catch { /* 이미 없음 */ }
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
// 글(자막)은 캡션이 달린 항목에만 있다 — 앨범이면 첫 장에만 붙는다(MK 2026-09-30).
|
|
1405
|
+
count = saveFieldMedia(inputs, { rootDir: this.fieldRootDir(), event, device: 'telegram', ...(tag?.text ? { caption: tag.text } : {}) }).count;
|
|
1406
|
+
debug.log('field.upload', 'saved', { surface: 'telegram', files: inputs.length, count, album: !!ctx.mediaGroupId });
|
|
1407
|
+
} catch (err) {
|
|
1408
|
+
failure = err instanceof FieldUploadError ? err.code : 'download-failed';
|
|
1409
|
+
debug.log('field.upload', 'refused', { surface: 'telegram', reason: failure });
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
if (failure !== null) {
|
|
1413
|
+
await this.sendMessage(ctx.chatId, `현장 저장 실패 · ${event} · ${failure}`, { replyTo: ctx.messageId, threadId: ctx.threadId });
|
|
1414
|
+
return true;
|
|
1415
|
+
}
|
|
1416
|
+
const line = (n: number) => `현장 폴더에 저장 · ${event} · 지금 ${n}장`;
|
|
1417
|
+
if (!ctx.mediaGroupId) {
|
|
1418
|
+
await this.sendMessage(ctx.chatId, line(count!), { replyTo: ctx.messageId, threadId: ctx.threadId });
|
|
1419
|
+
return true;
|
|
1420
|
+
}
|
|
1421
|
+
// 앨범은 항목마다 답하지 않는다 — 마지막 항목 뒤 한 번(디바운스)만 최신 수로 답한다.
|
|
1422
|
+
const entry = group ?? { event, at: now, chatId: ctx.chatId, threadId: ctx.threadId, replyTo: ctx.messageId, count: 0 };
|
|
1423
|
+
entry.at = now;
|
|
1424
|
+
entry.count = count!;
|
|
1425
|
+
if (entry.timer) clearTimeout(entry.timer);
|
|
1426
|
+
entry.timer = setTimeout(() => {
|
|
1427
|
+
entry.timer = undefined;
|
|
1428
|
+
void this.sendMessage(entry.chatId, line(entry.count), { replyTo: entry.replyTo, threadId: entry.threadId })
|
|
1429
|
+
.catch((e) => this.log(`field reply failed: ${e instanceof Error ? e.message : String(e)}`));
|
|
1430
|
+
}, 1500);
|
|
1431
|
+
(entry.timer as { unref?: () => void }).unref?.();
|
|
1432
|
+
this.fieldGroups.set(ctx.mediaGroupId, entry);
|
|
1433
|
+
return true;
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1350
1436
|
private async handleIncoming(ctx: TgIncoming): Promise<void> {
|
|
1351
1437
|
if (!this.isOwnerAllowed(ctx.userId)) {
|
|
1352
1438
|
await this.refuseMessage(ctx);
|
|
1353
1439
|
return;
|
|
1354
1440
|
}
|
|
1441
|
+
// 현장 업로드 — `#현장` 캡션(또는 태그된 앨범의 나머지)의 사진·영상은 LLM 대신 현장 폴더로.
|
|
1442
|
+
if (await this.tryHandleFieldUpload(ctx)) return;
|
|
1443
|
+
// 영상은 `#현장` 저장 전용으로만 파싱한다 — 그 밖의 영상은 종전처럼 무시한다.
|
|
1444
|
+
if (ctx.video && ctx.attachments.length === 0) return;
|
|
1355
1445
|
// Cascade-zyu U2 — capture utterance intent at update ingress.
|
|
1356
1446
|
try {
|
|
1357
1447
|
const { userIntentLogger } = await import('./user-intent/index.js');
|
|
@@ -2069,6 +2159,7 @@ export class TelegramBot {
|
|
|
2069
2159
|
interface PhotoSize { file_id: string; file_size?: number; width: number; height: number }
|
|
2070
2160
|
interface VoiceLike { file_id: string; file_size?: number; mime_type?: string; duration?: number }
|
|
2071
2161
|
interface DocumentLike { file_id: string; file_size?: number; mime_type?: string; file_name?: string }
|
|
2162
|
+
interface VideoLike { file_id: string; file_size?: number; mime_type?: string; file_name?: string; duration?: number }
|
|
2072
2163
|
|
|
2073
2164
|
interface RawUpdate {
|
|
2074
2165
|
update_id: number;
|
|
@@ -2083,6 +2174,8 @@ interface RawUpdate {
|
|
|
2083
2174
|
voice?: VoiceLike;
|
|
2084
2175
|
audio?: VoiceLike;
|
|
2085
2176
|
document?: DocumentLike;
|
|
2177
|
+
video?: VideoLike;
|
|
2178
|
+
media_group_id?: string;
|
|
2086
2179
|
reply_to_message?: { text?: string; message_id?: number };
|
|
2087
2180
|
};
|
|
2088
2181
|
callback_query?: {
|
|
@@ -2175,8 +2268,20 @@ export function parseUpdate(u: RawUpdate): TgIncoming | null {
|
|
|
2175
2268
|
});
|
|
2176
2269
|
}
|
|
2177
2270
|
|
|
2271
|
+
// 영상은 `#현장` 저장 전용 — 태그된 캡션이거나 앨범 항목일 때만 싣는다(그 밖의 영상은 종전처럼 무시).
|
|
2272
|
+
const video = m.video && (parseFieldCaption(m.caption) || m.media_group_id)
|
|
2273
|
+
? {
|
|
2274
|
+
kind: 'document' as const,
|
|
2275
|
+
fileId: m.video.file_id,
|
|
2276
|
+
fileSize: m.video.file_size,
|
|
2277
|
+
mimeType: m.video.mime_type ?? 'video/mp4',
|
|
2278
|
+
fileName: m.video.file_name,
|
|
2279
|
+
duration: m.video.duration,
|
|
2280
|
+
}
|
|
2281
|
+
: undefined;
|
|
2282
|
+
|
|
2178
2283
|
// Skip updates with zero signal: no text AND no attachments.
|
|
2179
|
-
if (typeof m.text !== 'string' && attachments.length === 0) return null;
|
|
2284
|
+
if (typeof m.text !== 'string' && attachments.length === 0 && !video) return null;
|
|
2180
2285
|
|
|
2181
2286
|
const isDm = m.chat.type === 'private';
|
|
2182
2287
|
return {
|
|
@@ -2191,6 +2296,8 @@ export function parseUpdate(u: RawUpdate): TgIncoming | null {
|
|
|
2191
2296
|
isGroup: !isDm,
|
|
2192
2297
|
attachments,
|
|
2193
2298
|
...(m.reply_to_message?.text ? { replyToText: m.reply_to_message.text } : {}),
|
|
2299
|
+
...(m.media_group_id ? { mediaGroupId: m.media_group_id } : {}),
|
|
2300
|
+
...(video ? { video } : {}),
|
|
2194
2301
|
};
|
|
2195
2302
|
}
|
|
2196
2303
|
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
AcpTransportError,
|
|
26
26
|
type AcpTransportConnection,
|
|
27
27
|
} from '../acp/transport/index.js';
|
|
28
|
+
import { debug } from '../debug/log.js';
|
|
28
29
|
|
|
29
30
|
export interface ConnectWebSocketClientOpts {
|
|
30
31
|
/** Full WebSocket URL — e.g. `ws://mbp.tailnet:31415/v1/acp`. */
|
|
@@ -42,11 +43,15 @@ export interface ConnectWebSocketClientOpts {
|
|
|
42
43
|
* ⛔ 없으면 서버가 핸드셰이크에 답하지 않을 때 이 Promise 가 «영원히» 안 풀린다 —
|
|
43
44
|
* 그러면 호출자는 산출도 종료 코드도 못 얻는다(2026-08-31 실측: 100초 상한을 걸 때까지 매달렸다). */
|
|
44
45
|
connectTimeoutMs?: number;
|
|
46
|
+
onDisconnect?(info: { reason: 'close' | 'error'; code?: number; afterMs: number; at: string; lastEventId?: string }): void;
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
/** ⏱️ 기본 연결 상한. 테일넷 왕복은 보통 수백 ms 라 넉넉하다. */
|
|
48
50
|
export const DEFAULT_WS_CONNECT_TIMEOUT_MS = 10_000;
|
|
49
51
|
|
|
52
|
+
/** Maximum NDJSON line inspected for an event id; ACP stream delivery is not limited. */
|
|
53
|
+
const MAX_EVENT_ID_LINE_LENGTH = 64 * 1024;
|
|
54
|
+
|
|
50
55
|
/** Connect to a Elanous daemon over its public WebSocket endpoint.
|
|
51
56
|
* Resolves with the bidirectional stream pair once the (optional)
|
|
52
57
|
* auth handshake has completed; rejects with `AcpTransportError` on
|
|
@@ -72,6 +77,65 @@ export async function connectWebSocketClient(
|
|
|
72
77
|
ws.binaryType = 'arraybuffer';
|
|
73
78
|
|
|
74
79
|
let settled = false;
|
|
80
|
+
let connectedAt: number | null = null;
|
|
81
|
+
let disconnected = false;
|
|
82
|
+
let lastEventId: string | undefined;
|
|
83
|
+
let incomingLine = '';
|
|
84
|
+
let oversizedLine = false;
|
|
85
|
+
const lineDecoder = new TextDecoder();
|
|
86
|
+
const recordEventId = (line: string): void => {
|
|
87
|
+
try {
|
|
88
|
+
const message: unknown = JSON.parse(line);
|
|
89
|
+
if (message !== null && typeof message === 'object' && 'id' in message
|
|
90
|
+
&& typeof message.id === 'string') lastEventId = message.id;
|
|
91
|
+
} catch { /* Non-JSON or incomplete ACP payload. */ }
|
|
92
|
+
};
|
|
93
|
+
const trackEventIds = (text: string): void => {
|
|
94
|
+
let start = 0;
|
|
95
|
+
while (start < text.length) {
|
|
96
|
+
const newline = text.indexOf('\n', start);
|
|
97
|
+
const end = newline < 0 ? text.length : newline;
|
|
98
|
+
const length = end - start;
|
|
99
|
+
if (!oversizedLine) {
|
|
100
|
+
if (incomingLine.length + length <= MAX_EVENT_ID_LINE_LENGTH) {
|
|
101
|
+
incomingLine += text.slice(start, end);
|
|
102
|
+
} else {
|
|
103
|
+
incomingLine = '';
|
|
104
|
+
oversizedLine = true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (newline < 0) break;
|
|
108
|
+
if (!oversizedLine) recordEventId(incomingLine);
|
|
109
|
+
incomingLine = '';
|
|
110
|
+
oversizedLine = false;
|
|
111
|
+
start = newline + 1;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
const recordConnected = (): void => {
|
|
115
|
+
connectedAt = Date.now();
|
|
116
|
+
try {
|
|
117
|
+
const host = new URL(opts.url).hostname;
|
|
118
|
+
debug.log('tui-client.ws', 'connected', {
|
|
119
|
+
hostKind: host === 'localhost' || host === '127.0.0.1' || host === '::1' ? 'local' : 'remote',
|
|
120
|
+
});
|
|
121
|
+
} catch { /* observation must not prevent connection */ }
|
|
122
|
+
};
|
|
123
|
+
const disconnect = (reason: 'close' | 'error', code?: number): void => {
|
|
124
|
+
finishReadable();
|
|
125
|
+
if (connectedAt === null || disconnected) return;
|
|
126
|
+
disconnected = true;
|
|
127
|
+
trackEventIds(lineDecoder.decode());
|
|
128
|
+
if (incomingLine && !oversizedLine) recordEventId(incomingLine);
|
|
129
|
+
const info = {
|
|
130
|
+
reason,
|
|
131
|
+
...(code !== undefined ? { code } : {}),
|
|
132
|
+
afterMs: Math.max(0, Date.now() - connectedAt),
|
|
133
|
+
at: new Date().toISOString(),
|
|
134
|
+
...(lastEventId !== undefined ? { lastEventId } : {}),
|
|
135
|
+
};
|
|
136
|
+
try { debug.log('tui-client.ws', 'disconnect', info); } catch { /* observation must not prevent notification */ }
|
|
137
|
+
opts.onDisconnect?.(info);
|
|
138
|
+
};
|
|
75
139
|
let authed = !opts.token; // noAuth mode = no handshake needed
|
|
76
140
|
// ⛔ 「안 됐다」를 한 값으로 접지 않는다 — «어느 단계»에서 멎었는지 이름을 붙인다.
|
|
77
141
|
// 소켓조차 안 열린 것과, 열렸는데 서버가 인증에 «답을 안 한» 것은 다른 결함이다.
|
|
@@ -152,6 +216,7 @@ export async function connectWebSocketClient(
|
|
|
152
216
|
settled = true;
|
|
153
217
|
clearConnectTimer();
|
|
154
218
|
opts.signal?.removeEventListener('abort', onAbort);
|
|
219
|
+
recordConnected();
|
|
155
220
|
resolve(buildConnection());
|
|
156
221
|
return;
|
|
157
222
|
}
|
|
@@ -183,6 +248,7 @@ export async function connectWebSocketClient(
|
|
|
183
248
|
settled = true;
|
|
184
249
|
clearConnectTimer();
|
|
185
250
|
opts.signal?.removeEventListener('abort', onAbort);
|
|
251
|
+
recordConnected();
|
|
186
252
|
resolve(buildConnection());
|
|
187
253
|
}
|
|
188
254
|
} else {
|
|
@@ -191,10 +257,11 @@ export async function connectWebSocketClient(
|
|
|
191
257
|
return;
|
|
192
258
|
}
|
|
193
259
|
// Authed: forward bytes to the consumer's ReadableStream.
|
|
194
|
-
if (!readableController) return;
|
|
195
260
|
const bytes = typeof ev.data === 'string'
|
|
196
261
|
? new TextEncoder().encode(ev.data)
|
|
197
262
|
: new Uint8Array(ev.data as ArrayBuffer);
|
|
263
|
+
trackEventIds(lineDecoder.decode(bytes, { stream: true }));
|
|
264
|
+
if (!readableController) return;
|
|
198
265
|
try { readableController.enqueue(bytes); }
|
|
199
266
|
catch { /* stream already closed */ }
|
|
200
267
|
});
|
|
@@ -221,7 +288,7 @@ export async function connectWebSocketClient(
|
|
|
221
288
|
ws.addEventListener('error', () => {
|
|
222
289
|
// Bun/browser WebSocket fires error then close; only treat the
|
|
223
290
|
// pre-settled error as a connect failure. Post-settled errors
|
|
224
|
-
// close the readable stream
|
|
291
|
+
// close the readable stream and notify the dashboard once.
|
|
225
292
|
if (!settled) {
|
|
226
293
|
// ⛔ 「연결 오류」 한 값으로 접지 않는다 — open «전»이면 소켓이 안 열린 것이고,
|
|
227
294
|
// open «뒤»면 서버가 붙여 놓고 인증에 답하지 않은 것이다. 다른 결함이다.
|
|
@@ -229,17 +296,17 @@ export async function connectWebSocketClient(
|
|
|
229
296
|
? `socket never opened: connection error (${opts.url})`
|
|
230
297
|
: `server accepted the socket but never answered the auth handshake: connection error (${opts.url})`);
|
|
231
298
|
} else {
|
|
232
|
-
|
|
299
|
+
disconnect('error');
|
|
233
300
|
}
|
|
234
301
|
});
|
|
235
302
|
|
|
236
|
-
ws.addEventListener('close', () => {
|
|
303
|
+
ws.addEventListener('close', (ev: CloseEvent) => {
|
|
237
304
|
if (!settled) {
|
|
238
305
|
failConnect(stage === 'opening'
|
|
239
306
|
? `socket never opened: closed before ready (${opts.url})`
|
|
240
307
|
: `server accepted the socket but never answered the auth handshake: closed before ready (${opts.url})`);
|
|
241
308
|
} else {
|
|
242
|
-
|
|
309
|
+
disconnect('close', ev.code);
|
|
243
310
|
}
|
|
244
311
|
});
|
|
245
312
|
});
|
package/src/tui.ts
CHANGED
|
@@ -326,7 +326,8 @@ function parseKey(data: string | Buffer): Key {
|
|
|
326
326
|
if (s === '\x1b[3~') return K('delete');
|
|
327
327
|
|
|
328
328
|
// Special keys
|
|
329
|
-
if (s === '\r'
|
|
329
|
+
if (s === '\r') return K('enter');
|
|
330
|
+
if (s === '\n') return K('j', true); // Ctrl+J → LF
|
|
330
331
|
if (s === ' ') return K('space');
|
|
331
332
|
if (s === '\x1b') return K('escape');
|
|
332
333
|
if (s === '\t') return K('tab');
|
|
@@ -341,7 +342,7 @@ function parseKey(data: string | Buffer): Key {
|
|
|
341
342
|
// fallback for plain terminals. Ctrl+1 has no distinct wire form;
|
|
342
343
|
// Ctrl+3 collides with Escape — so those must come via modKeys.
|
|
343
344
|
if (s === '\x00') return K('2', true); // Ctrl+2 → NUL
|
|
344
|
-
if (s === '\x1c') return K('
|
|
345
|
+
if (s === '\x1c') return K('\\', true); // Ctrl+\\ → FS; Ctrl+4 shares this byte, so requires kitty/modifyOtherKeys
|
|
345
346
|
if (s === '\x1d') return K('5', true); // Ctrl+5 → GS
|
|
346
347
|
if (s.length === 1 && s.charCodeAt(0) < 32) {
|
|
347
348
|
return K(String.fromCharCode(s.charCodeAt(0) + 96), true);
|
package/src/user-config.ts
CHANGED
|
@@ -784,6 +784,12 @@ export interface TelegramChannel {
|
|
|
784
784
|
}
|
|
785
785
|
|
|
786
786
|
/** 저장된 telegram.channels[] 파싱. 각 항목은 botToken+chatId 필수. fail-soft. */
|
|
787
|
+
function normalizeKindRoles(raw: unknown): Record<string, string> | undefined {
|
|
788
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return undefined;
|
|
789
|
+
const out = Object.fromEntries(Object.entries(raw as Record<string, unknown>).filter((e): e is [string, string] => typeof e[1] === 'string' && e[1].length > 0));
|
|
790
|
+
return Object.keys(out).length ? out : undefined;
|
|
791
|
+
}
|
|
792
|
+
|
|
787
793
|
function normalizeTelegramChannels(raw: unknown): TelegramChannel[] | undefined {
|
|
788
794
|
if (!Array.isArray(raw)) return undefined;
|
|
789
795
|
const out: TelegramChannel[] = [];
|
|
@@ -824,6 +830,11 @@ export interface TelegramConfig {
|
|
|
824
830
|
/** 누가 Q&A 폴링을 하나 — 기본(없음·`'nexus'`)은 넥서스 데몬, `'standalone'` 이면
|
|
825
831
|
* 넥서스는 폴링하지 않고 `elanous telegram run` 이 맡는다. */
|
|
826
832
|
poller?: 'nexus' | 'standalone';
|
|
833
|
+
/** 발송 목적(kind) → 채널 역할. 명시 `channels` 가 있을 때만 쓴다 — 표에 없는 kind 는 옛 `reportChannel` 로 간다.
|
|
834
|
+
* 기본표는 `DEFAULT_KIND_ROLES`(telegram-report.ts) · 여기 값이 덮어쓴다. */
|
|
835
|
+
kindRoles?: Record<string, string>;
|
|
836
|
+
/** `#현장` 캡션에 행사 슬러그가 없을 때 쓸 기본 행사(예: `marketers-night-2026-10`). 없으면 `field-<로컬 날짜>`. */
|
|
837
|
+
fieldDefaultEvent?: string;
|
|
827
838
|
}
|
|
828
839
|
|
|
829
840
|
const TELEGRAM_DEFAULTS: TelegramConfig = { enabled: false, allowedUsers: [] };
|
|
@@ -3315,12 +3326,12 @@ export function resolveRoleModel(role: ModelRole, config: UserConfig = getUserCo
|
|
|
3315
3326
|
|
|
3316
3327
|
export interface UserConfig {
|
|
3317
3328
|
/** Steward loop: observe-only until an independently approved act implementation exists. */
|
|
3318
|
-
loops?: { steward?: { mode?: 'observe' | 'act'; linearTeam?: string; roles?: Record<string, { maxConcurrent?: number }>; budget?: number } };
|
|
3329
|
+
loops?: { steward?: { mode?: 'observe' | 'act'; linearTeam?: string; roles?: Record<string, { maxConcurrent?: number }>; budget?: number; tracks?: Record<string, string> } };
|
|
3319
3330
|
/** 🔐 Pod 의 Grok API 키 과금은 명시 opt-in 만. 생략·잘못된 값은 false.
|
|
3320
3331
|
* budgetGate 와 같은 `harness` 객체다 — 중복 선언이면 뒤 타입이 앞을 지운다. */
|
|
3321
3332
|
harness?: { pod?: { grokApiKeyOptIn: boolean }; budgetGate?: HarnessBudgetGateConfig; defaultRepo?: string; substrate?: 'local' | 'pod'; podPool?: string };
|
|
3322
3333
|
/** ☸️ Pod 실행 칸 — `pool` = 기존 기본 풀(`컨텍스트[@ssh호스트][:상한]` 쉼표 · 앞이 우선). harness 실행은 인자·ELANOUS_POD_POOL·harness.podPool 다음으로 읽는다. */
|
|
3323
|
-
pod?: { pool?: string; /** 원격 그라운딩 엔드포인트(호스트 nexus · tailnet 주소) — Pod 가 토큰으로 «질의→인용»만 묻는다(P13). */ groundingUrl?: string };
|
|
3334
|
+
pod?: { pool?: string; /** 호스트 Git 미러 디렉터리 — Pod Job 에 읽기 전용으로 마운트한다. */ hostMirror?: string; /** 원격 그라운딩 엔드포인트(호스트 nexus · tailnet 주소) — Pod 가 토큰으로 «질의→인용»만 묻는다(P13). */ groundingUrl?: string };
|
|
3324
3335
|
skillRouter: SkillRouterConfig;
|
|
3325
3336
|
llm: LLMConfig;
|
|
3326
3337
|
skills: SkillsConfig;
|
|
@@ -4106,6 +4117,15 @@ function observeRetiredConfigKeysOnce(raw: Record<string, unknown>): void {
|
|
|
4106
4117
|
} catch { /* observation must never break config loading */ }
|
|
4107
4118
|
}
|
|
4108
4119
|
|
|
4120
|
+
/** Owner tracks the steward may assign (key → what that track owns). The one place the role table lives. */
|
|
4121
|
+
function parseStewardTracks(input: unknown): Record<string, string> | undefined {
|
|
4122
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) return undefined;
|
|
4123
|
+
const out = Object.fromEntries(Object.entries(input as Record<string, unknown>)
|
|
4124
|
+
.filter((e): e is [string, string] => /^[A-Za-z][\w-]{0,15}$/.test(e[0]) && typeof e[1] === 'string' && e[1].trim().length > 0)
|
|
4125
|
+
.map(([k, v]) => [k, v.trim()]));
|
|
4126
|
+
return Object.keys(out).length ? out : undefined;
|
|
4127
|
+
}
|
|
4128
|
+
|
|
4109
4129
|
function parseStewardLoopsConfig(input: unknown): UserConfig['loops'] {
|
|
4110
4130
|
if (!input || typeof input !== 'object' || Array.isArray(input)) return undefined;
|
|
4111
4131
|
const value = (input as Record<string, unknown>).steward;
|
|
@@ -4124,6 +4144,7 @@ function parseStewardLoopsConfig(input: unknown): UserConfig['loops'] {
|
|
|
4124
4144
|
...(typeof s.linearTeam === 'string' && s.linearTeam.trim() ? { linearTeam: s.linearTeam.trim() } : {}),
|
|
4125
4145
|
...(Object.keys(roles).length ? { roles } : {}),
|
|
4126
4146
|
...(typeof s.budget === 'number' && Number.isFinite(s.budget) && s.budget >= 0 ? { budget: s.budget } : {}),
|
|
4147
|
+
...(parseStewardTracks(s.tracks) ? { tracks: parseStewardTracks(s.tracks)! } : {}),
|
|
4127
4148
|
} };
|
|
4128
4149
|
}
|
|
4129
4150
|
|
|
@@ -4233,6 +4254,7 @@ export function buildUserConfig(path: string = defaultPath()): UserConfig {
|
|
|
4233
4254
|
return {
|
|
4234
4255
|
pod: {
|
|
4235
4256
|
...(typeof legacyPod.pool === 'string' && legacyPod.pool.trim() ? { pool: legacyPod.pool.trim() } : {}),
|
|
4257
|
+
...(typeof legacyPod.hostMirror === 'string' && legacyPod.hostMirror.trim() ? { hostMirror: legacyPod.hostMirror.trim() } : {}),
|
|
4236
4258
|
...(typeof legacyPod.groundingUrl === 'string' ? { groundingUrl: legacyPod.groundingUrl } : {}),
|
|
4237
4259
|
},
|
|
4238
4260
|
harness: {
|
|
@@ -4372,6 +4394,8 @@ export function buildUserConfig(path: string = defaultPath()): UserConfig {
|
|
|
4372
4394
|
channels: normalizeTelegramChannels(tg.channels),
|
|
4373
4395
|
testChannel: normalizeTestChannel(tg.testChannel),
|
|
4374
4396
|
...(tg.poller === 'standalone' || tg.poller === 'nexus' ? { poller: tg.poller } : {}),
|
|
4397
|
+
...(normalizeKindRoles(tg.kindRoles) ? { kindRoles: normalizeKindRoles(tg.kindRoles)! } : {}),
|
|
4398
|
+
...(str(tg.fieldDefaultEvent) ? { fieldDefaultEvent: str(tg.fieldDefaultEvent)! } : {}),
|
|
4375
4399
|
},
|
|
4376
4400
|
discord: {
|
|
4377
4401
|
enabled: dc.enabled === true,
|
|
@@ -5426,6 +5450,11 @@ export function saveUserConfig(
|
|
|
5426
5450
|
const rawHarness = rawRest.harness && typeof rawRest.harness === 'object' && !Array.isArray(rawRest.harness)
|
|
5427
5451
|
? rawRest.harness as Record<string, unknown> : {};
|
|
5428
5452
|
delete rawRest.harness;
|
|
5453
|
+
if (cfg.pod?.hostMirror) {
|
|
5454
|
+
const rawPod = rawRest.pod && typeof rawRest.pod === 'object' && !Array.isArray(rawRest.pod)
|
|
5455
|
+
? rawRest.pod as Record<string, unknown> : {};
|
|
5456
|
+
rawRest.pod = { ...rawPod, hostMirror: cfg.pod.hostMirror };
|
|
5457
|
+
}
|
|
5429
5458
|
|
|
5430
5459
|
const out: Record<string, unknown> = {
|
|
5431
5460
|
harness: stripUndef({
|
|
@@ -5539,6 +5568,7 @@ export function saveUserConfig(
|
|
|
5539
5568
|
// ★ 2026-09-25: `poller`(T1 분리 스위치)가 파서엔 있고 이 whitelist 에 없어 `config set telegram.poller` 가
|
|
5540
5569
|
// 「직렬화 드롭」으로 조용히 사라졌다 — 운영 전환 스위치를 켤 방법이 없었다(07-22 botUsername 과 같은 모양).
|
|
5541
5570
|
poller: cfg.telegram.poller,
|
|
5571
|
+
fieldDefaultEvent: cfg.telegram.fieldDefaultEvent,
|
|
5542
5572
|
}),
|
|
5543
5573
|
discord: stripUndef({
|
|
5544
5574
|
enabled: cfg.discord.enabled,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { spawnSync, type SpawnSyncOptionsWithStringEncoding } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
/** Read command text completely or fail closed; never return a partial stdout. */
|
|
4
|
+
export function spawnSyncText(
|
|
5
|
+
cmd: string,
|
|
6
|
+
args: readonly string[],
|
|
7
|
+
opts: SpawnSyncOptionsWithStringEncoding = { encoding: 'utf8' },
|
|
8
|
+
): string {
|
|
9
|
+
const result = spawnSync(cmd, [...args], { ...opts, maxBuffer: opts.maxBuffer ?? 256 * 1024 * 1024 });
|
|
10
|
+
if (result.error || result.status !== 0) {
|
|
11
|
+
const reason = result.error?.message ?? result.stderr?.trim() ?? '';
|
|
12
|
+
throw new Error(`${cmd} ${args.join(' ')} failed (status=${result.status}${result.error ? `, error=${(result.error as NodeJS.ErrnoException).code ?? result.error.name}` : ''}): ${reason}`);
|
|
13
|
+
}
|
|
14
|
+
return result.stdout;
|
|
15
|
+
}
|
|
@@ -54,7 +54,7 @@ function usableInstallMetadataCommit(commit: string): string | undefined {
|
|
|
54
54
|
return revision;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
function readInstallMetadataCommit(metadataPath: string): string | undefined {
|
|
57
|
+
export function readInstallMetadataCommit(metadataPath: string): string | undefined {
|
|
58
58
|
try {
|
|
59
59
|
const parsed: unknown = JSON.parse(readFileSync(metadataPath, 'utf8'));
|
|
60
60
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"commit":"
|
|
1
|
+
{"commit":"65a4c7230c60a536b0f0f0428322c8ddcbcac752"}
|