botmux 3.5.0 → 3.6.0
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.en.md +56 -396
- package/README.md +55 -396
- package/dist/.runtime-build-id +1 -0
- package/dist/adapters/backend/sandbox.d.ts +99 -279
- package/dist/adapters/backend/sandbox.d.ts.map +1 -1
- package/dist/adapters/backend/sandbox.js +424 -826
- package/dist/adapters/backend/sandbox.js.map +1 -1
- package/dist/adapters/cli/claude-code.d.ts +6 -0
- package/dist/adapters/cli/claude-code.d.ts.map +1 -1
- package/dist/adapters/cli/claude-code.js +197 -11
- package/dist/adapters/cli/claude-code.js.map +1 -1
- package/dist/adapters/cli/coco.js +4 -4
- package/dist/adapters/cli/coco.js.map +1 -1
- package/dist/adapters/cli/codex-app.d.ts.map +1 -1
- package/dist/adapters/cli/codex-app.js +9 -6
- package/dist/adapters/cli/codex-app.js.map +1 -1
- package/dist/adapters/cli/codex.d.ts.map +1 -1
- package/dist/adapters/cli/codex.js +9 -5
- package/dist/adapters/cli/codex.js.map +1 -1
- package/dist/adapters/cli/fs-policy.d.ts +244 -0
- package/dist/adapters/cli/fs-policy.d.ts.map +1 -0
- package/dist/adapters/cli/fs-policy.js +537 -0
- package/dist/adapters/cli/fs-policy.js.map +1 -0
- package/dist/adapters/cli/grok.d.ts +2 -1
- package/dist/adapters/cli/grok.d.ts.map +1 -1
- package/dist/adapters/cli/grok.js +2 -1
- package/dist/adapters/cli/grok.js.map +1 -1
- package/dist/adapters/cli/hermes.d.ts.map +1 -1
- package/dist/adapters/cli/hermes.js +8 -4
- package/dist/adapters/cli/hermes.js.map +1 -1
- package/dist/adapters/cli/mtr.d.ts.map +1 -1
- package/dist/adapters/cli/mtr.js +3 -2
- package/dist/adapters/cli/mtr.js.map +1 -1
- package/dist/adapters/cli/opencode.d.ts.map +1 -1
- package/dist/adapters/cli/opencode.js +5 -4
- package/dist/adapters/cli/opencode.js.map +1 -1
- package/dist/adapters/cli/read-isolation.d.ts +14 -200
- package/dist/adapters/cli/read-isolation.d.ts.map +1 -1
- package/dist/adapters/cli/read-isolation.js +18 -477
- package/dist/adapters/cli/read-isolation.js.map +1 -1
- package/dist/adapters/cli/relay.js +2 -2
- package/dist/adapters/cli/relay.js.map +1 -1
- package/dist/adapters/cli/runner-input.d.ts +2 -2
- package/dist/adapters/cli/runner-input.d.ts.map +1 -1
- package/dist/adapters/cli/runner-input.js +9 -6
- package/dist/adapters/cli/runner-input.js.map +1 -1
- package/dist/adapters/cli/seed.d.ts.map +1 -1
- package/dist/adapters/cli/seed.js +2 -1
- package/dist/adapters/cli/seed.js.map +1 -1
- package/dist/adapters/cli/traex.d.ts.map +1 -1
- package/dist/adapters/cli/traex.js +3 -2
- package/dist/adapters/cli/traex.js.map +1 -1
- package/dist/adapters/cli/types.d.ts +25 -18
- package/dist/adapters/cli/types.d.ts.map +1 -1
- package/dist/bot-registry.d.ts +54 -23
- package/dist/bot-registry.d.ts.map +1 -1
- package/dist/bot-registry.js +91 -0
- package/dist/bot-registry.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +297 -104
- package/dist/cli.js.map +1 -1
- package/dist/codex-app-runner.js +85 -192
- package/dist/codex-app-runner.js.map +1 -1
- package/dist/config.d.ts +0 -16
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -30
- package/dist/config.js.map +1 -1
- package/dist/core/ask-broker.d.ts +12 -0
- package/dist/core/ask-broker.d.ts.map +1 -1
- package/dist/core/ask-broker.js +41 -0
- package/dist/core/ask-broker.js.map +1 -1
- package/dist/core/bot-turn-mutation-gate.d.ts +24 -0
- package/dist/core/bot-turn-mutation-gate.d.ts.map +1 -0
- package/dist/core/bot-turn-mutation-gate.js +369 -0
- package/dist/core/bot-turn-mutation-gate.js.map +1 -0
- package/dist/core/command-handler.d.ts +5 -0
- package/dist/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +53 -73
- package/dist/core/command-handler.js.map +1 -1
- package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
- package/dist/core/dashboard-ipc-server.js +356 -84
- package/dist/core/dashboard-ipc-server.js.map +1 -1
- package/dist/core/dashboard-rows.d.ts +8 -0
- package/dist/core/dashboard-rows.d.ts.map +1 -1
- package/dist/core/dashboard-rows.js.map +1 -1
- package/dist/core/host-overload-alert.d.ts +157 -0
- package/dist/core/host-overload-alert.d.ts.map +1 -0
- package/dist/core/host-overload-alert.js +251 -0
- package/dist/core/host-overload-alert.js.map +1 -0
- package/dist/core/passthrough-commands.d.ts +8 -0
- package/dist/core/passthrough-commands.d.ts.map +1 -1
- package/dist/core/passthrough-commands.js +20 -1
- package/dist/core/passthrough-commands.js.map +1 -1
- package/dist/core/plugins/mcp/gateway-installer.d.ts.map +1 -1
- package/dist/core/plugins/mcp/gateway-installer.js +14 -2
- package/dist/core/plugins/mcp/gateway-installer.js.map +1 -1
- package/dist/core/restart-coordinator.d.ts +28 -0
- package/dist/core/restart-coordinator.d.ts.map +1 -0
- package/dist/core/restart-coordinator.js +82 -0
- package/dist/core/restart-coordinator.js.map +1 -0
- package/dist/core/restart-followup-policy.d.ts +112 -0
- package/dist/core/restart-followup-policy.d.ts.map +1 -0
- package/dist/core/restart-followup-policy.js +49 -0
- package/dist/core/restart-followup-policy.js.map +1 -0
- package/dist/core/role-library.d.ts +1 -1
- package/dist/core/role-library.js +1 -1
- package/dist/core/session-cwd.d.ts.map +1 -1
- package/dist/core/session-cwd.js +8 -0
- package/dist/core/session-cwd.js.map +1 -1
- package/dist/core/session-discovery.d.ts +14 -0
- package/dist/core/session-discovery.d.ts.map +1 -1
- package/dist/core/session-discovery.js +25 -0
- package/dist/core/session-discovery.js.map +1 -1
- package/dist/core/session-liveness.d.ts +15 -0
- package/dist/core/session-liveness.d.ts.map +1 -0
- package/dist/core/session-liveness.js +76 -0
- package/dist/core/session-liveness.js.map +1 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +90 -60
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-row-enrichment.d.ts +15 -0
- package/dist/core/session-row-enrichment.d.ts.map +1 -0
- package/dist/core/session-row-enrichment.js +93 -0
- package/dist/core/session-row-enrichment.js.map +1 -0
- package/dist/core/slash-inject.js +1 -1
- package/dist/core/slash-inject.js.map +1 -1
- package/dist/core/trigger-final-suppression.d.ts +24 -0
- package/dist/core/trigger-final-suppression.d.ts.map +1 -0
- package/dist/core/trigger-final-suppression.js +97 -0
- package/dist/core/trigger-final-suppression.js.map +1 -0
- package/dist/core/trigger-session.d.ts.map +1 -1
- package/dist/core/trigger-session.js +64 -4
- package/dist/core/trigger-session.js.map +1 -1
- package/dist/core/types.d.ts +6 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/worker-pool.d.ts +44 -0
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +333 -38
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts +5 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +526 -35
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/aggregator.d.ts +1 -0
- package/dist/dashboard/aggregator.d.ts.map +1 -1
- package/dist/dashboard/aggregator.js +34 -6
- package/dist/dashboard/aggregator.js.map +1 -1
- package/dist/dashboard/bot-payload.d.ts +8 -0
- package/dist/dashboard/bot-payload.d.ts.map +1 -1
- package/dist/dashboard/bot-payload.js +10 -0
- package/dist/dashboard/bot-payload.js.map +1 -1
- package/dist/dashboard/compat.d.ts +102 -2
- package/dist/dashboard/compat.d.ts.map +1 -1
- package/dist/dashboard/compat.js +78 -14
- package/dist/dashboard/compat.js.map +1 -1
- package/dist/dashboard/connector-api.d.ts.map +1 -1
- package/dist/dashboard/connector-api.js +1 -0
- package/dist/dashboard/connector-api.js.map +1 -1
- package/dist/dashboard/desktop-asks.d.ts +21 -0
- package/dist/dashboard/desktop-asks.d.ts.map +1 -0
- package/dist/dashboard/desktop-asks.js +49 -0
- package/dist/dashboard/desktop-asks.js.map +1 -0
- package/dist/dashboard/public-redact.d.ts +7 -0
- package/dist/dashboard/public-redact.d.ts.map +1 -1
- package/dist/dashboard/public-redact.js +35 -3
- package/dist/dashboard/public-redact.js.map +1 -1
- package/dist/dashboard/session-presentation.d.ts +16 -0
- package/dist/dashboard/session-presentation.d.ts.map +1 -0
- package/dist/dashboard/session-presentation.js +62 -0
- package/dist/dashboard/session-presentation.js.map +1 -0
- package/dist/dashboard/settings-write-applier.d.ts +2 -1
- package/dist/dashboard/settings-write-applier.d.ts.map +1 -1
- package/dist/dashboard/settings-write-applier.js +19 -1
- package/dist/dashboard/settings-write-applier.js.map +1 -1
- package/dist/dashboard/trigger-api.d.ts.map +1 -1
- package/dist/dashboard/trigger-api.js +19 -1
- package/dist/dashboard/trigger-api.js.map +1 -1
- package/dist/dashboard/vc-consumer-profiles-api.d.ts +7 -2
- package/dist/dashboard/vc-consumer-profiles-api.d.ts.map +1 -1
- package/dist/dashboard/vc-consumer-profiles-api.js +13 -0
- package/dist/dashboard/vc-consumer-profiles-api.js.map +1 -1
- package/dist/dashboard/web/app.d.ts.map +1 -1
- package/dist/dashboard/web/app.js +18 -5
- package/dist/dashboard/web/app.js.map +1 -1
- package/dist/dashboard/web/bot-defaults-page.d.ts +16 -0
- package/dist/dashboard/web/bot-defaults-page.d.ts.map +1 -1
- package/dist/dashboard/web/bot-defaults-page.js +288 -7
- package/dist/dashboard/web/bot-defaults-page.js.map +1 -1
- package/dist/dashboard/web/bot-defaults.d.ts +10 -0
- package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
- package/dist/dashboard/web/bot-defaults.js.map +1 -1
- package/dist/dashboard/web/client-shell.d.ts +29 -0
- package/dist/dashboard/web/client-shell.d.ts.map +1 -0
- package/dist/dashboard/web/client-shell.js +89 -0
- package/dist/dashboard/web/client-shell.js.map +1 -0
- package/dist/dashboard/web/connectors-page.d.ts.map +1 -1
- package/dist/dashboard/web/connectors-page.js +5 -1
- package/dist/dashboard/web/connectors-page.js.map +1 -1
- package/dist/dashboard/web/i18n.d.ts.map +1 -1
- package/dist/dashboard/web/i18n.js +130 -22
- package/dist/dashboard/web/i18n.js.map +1 -1
- package/dist/dashboard/web/preferences.d.ts +7 -0
- package/dist/dashboard/web/preferences.d.ts.map +1 -1
- package/dist/dashboard/web/preferences.js +32 -0
- package/dist/dashboard/web/preferences.js.map +1 -1
- package/dist/dashboard/web/sessions-kanban.d.ts +1 -1
- package/dist/dashboard/web/sessions-kanban.d.ts.map +1 -1
- package/dist/dashboard/web/sessions-kanban.js +1 -1
- package/dist/dashboard/web/sessions-kanban.js.map +1 -1
- package/dist/dashboard/web/sessions-page.d.ts.map +1 -1
- package/dist/dashboard/web/sessions-page.js +78 -67
- package/dist/dashboard/web/sessions-page.js.map +1 -1
- package/dist/dashboard/web/sessions.d.ts +4 -0
- package/dist/dashboard/web/sessions.d.ts.map +1 -1
- package/dist/dashboard/web/sessions.js +14 -1
- package/dist/dashboard/web/sessions.js.map +1 -1
- package/dist/dashboard/web/settings-page.d.ts +6 -0
- package/dist/dashboard/web/settings-page.d.ts.map +1 -1
- package/dist/dashboard/web/settings-page.js +55 -30
- package/dist/dashboard/web/settings-page.js.map +1 -1
- package/dist/dashboard/web/store.d.ts +1 -2
- package/dist/dashboard/web/store.d.ts.map +1 -1
- package/dist/dashboard/web/store.js +69 -20
- package/dist/dashboard/web/store.js.map +1 -1
- package/dist/dashboard/web/vc-consumer-profiles-section.d.ts.map +1 -1
- package/dist/dashboard/web/vc-consumer-profiles-section.js +128 -42
- package/dist/dashboard/web/vc-consumer-profiles-section.js.map +1 -1
- package/dist/dashboard/webhook-routes.d.ts.map +1 -1
- package/dist/dashboard/webhook-routes.js +9 -2
- package/dist/dashboard/webhook-routes.js.map +1 -1
- package/dist/dashboard-web/app.js +1 -1
- package/dist/dashboard-web/chunks/bot-defaults-page-VQMPUJ4Y.js +10 -0
- package/dist/dashboard-web/chunks/chunk-AGCPF6MT.js +1 -0
- package/dist/dashboard-web/chunks/{chunk-QCPPKCX3.js → chunk-JMRGZQDB.js} +1 -1
- package/dist/dashboard-web/chunks/{chunk-EJDYUINZ.js → chunk-NP4JUBGS.js} +1 -1
- package/dist/dashboard-web/chunks/{chunk-WXPMNEYV.js → chunk-P327LXOT.js} +1 -1
- package/dist/dashboard-web/chunks/chunk-SNKATZWK.js +49 -0
- package/dist/dashboard-web/chunks/connectors-page-DOY4J3ZL.js +1 -0
- package/dist/dashboard-web/chunks/{groups-page-3P4TLUMF.js → groups-page-74TS2DIH.js} +1 -1
- package/dist/dashboard-web/chunks/{insights-page-TDCZV56A.js → insights-page-AYCO65JC.js} +1 -1
- package/dist/dashboard-web/chunks/{monitor-room-QWCWL3SX.js → monitor-room-YKYLTSAN.js} +1 -1
- package/dist/dashboard-web/chunks/{monitoring-page-IOWFSNVC.js → monitoring-page-QBIUNRBR.js} +1 -1
- package/dist/dashboard-web/chunks/{office-page-5AUPEHZP.js → office-page-4PH2A26P.js} +1 -1
- package/dist/dashboard-web/chunks/overview-page-H73D5UT4.js +1 -0
- package/dist/dashboard-web/chunks/{roles-page-WJVB52ET.js → roles-page-U4BTOQ7F.js} +1 -1
- package/dist/dashboard-web/chunks/{schedules-page-RVRWHP4V.js → schedules-page-RHRHX7R7.js} +1 -1
- package/dist/dashboard-web/chunks/sessions-page-VSC6AM5G.js +2 -0
- package/dist/dashboard-web/chunks/settings-page-LF3RKM5P.js +2 -0
- package/dist/dashboard-web/chunks/{skills-page-7YT5TIX5.js → skills-page-SYOIGGXH.js} +1 -1
- package/dist/dashboard-web/chunks/{team-federation-page-NJZKTU7A.js → team-federation-page-RMBLASTW.js} +1 -1
- package/dist/dashboard-web/chunks/{v3-page-NQ73UA2Q.js → v3-page-XJXKKJRN.js} +1 -1
- package/dist/dashboard-web/chunks/{whiteboards-page-DSM3MDMN.js → whiteboards-page-K3YVSTHJ.js} +1 -1
- package/dist/dashboard-web/style.css +571 -3
- package/dist/dashboard.js +238 -9
- package/dist/dashboard.js.map +1 -1
- package/dist/global-config.d.ts +11 -0
- package/dist/global-config.d.ts.map +1 -1
- package/dist/global-config.js +27 -1
- package/dist/global-config.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +6 -19
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +6 -19
- package/dist/i18n/zh.js.map +1 -1
- package/dist/im/lark/card-builder.d.ts +1 -14
- package/dist/im/lark/card-builder.d.ts.map +1 -1
- package/dist/im/lark/card-builder.js +1 -35
- package/dist/im/lark/card-builder.js.map +1 -1
- package/dist/im/lark/card-handler.d.ts +10 -0
- package/dist/im/lark/card-handler.d.ts.map +1 -1
- package/dist/im/lark/card-handler.js +161 -44
- package/dist/im/lark/card-handler.js.map +1 -1
- package/dist/im/lark/client.d.ts +47 -0
- package/dist/im/lark/client.d.ts.map +1 -1
- package/dist/im/lark/client.js +127 -18
- package/dist/im/lark/client.js.map +1 -1
- package/dist/im/lark/event-dispatcher.d.ts +13 -0
- package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
- package/dist/im/lark/event-dispatcher.js +48 -3
- package/dist/im/lark/event-dispatcher.js.map +1 -1
- package/dist/im/lark/identity-cache.d.ts +25 -1
- package/dist/im/lark/identity-cache.d.ts.map +1 -1
- package/dist/im/lark/identity-cache.js +47 -1
- package/dist/im/lark/identity-cache.js.map +1 -1
- package/dist/im/lark/overload-nonce.d.ts +19 -0
- package/dist/im/lark/overload-nonce.d.ts.map +1 -0
- package/dist/im/lark/overload-nonce.js +72 -0
- package/dist/im/lark/overload-nonce.js.map +1 -0
- package/dist/im/lark/v3-progress-card.d.ts.map +1 -1
- package/dist/im/lark/v3-progress-card.js +5 -0
- package/dist/im/lark/v3-progress-card.js.map +1 -1
- package/dist/services/async-trigger-state.d.ts +82 -0
- package/dist/services/async-trigger-state.d.ts.map +1 -0
- package/dist/services/async-trigger-state.js +95 -0
- package/dist/services/async-trigger-state.js.map +1 -0
- package/dist/services/async-trigger-store.d.ts +28 -0
- package/dist/services/async-trigger-store.d.ts.map +1 -0
- package/dist/services/async-trigger-store.js +120 -0
- package/dist/services/async-trigger-store.js.map +1 -0
- package/dist/services/bot-config-store.d.ts.map +1 -1
- package/dist/services/bot-config-store.js +34 -2
- package/dist/services/bot-config-store.js.map +1 -1
- package/dist/services/bridge-fallback-gate.d.ts +32 -0
- package/dist/services/bridge-fallback-gate.d.ts.map +1 -1
- package/dist/services/bridge-fallback-gate.js +39 -0
- package/dist/services/bridge-fallback-gate.js.map +1 -1
- package/dist/services/bridge-turn-queue.d.ts.map +1 -1
- package/dist/services/bridge-turn-queue.js +15 -1
- package/dist/services/bridge-turn-queue.js.map +1 -1
- package/dist/services/card-prefs-store.d.ts +4 -0
- package/dist/services/card-prefs-store.d.ts.map +1 -1
- package/dist/services/card-prefs-store.js +8 -0
- package/dist/services/card-prefs-store.js.map +1 -1
- package/dist/services/claude-transcript.d.ts +23 -0
- package/dist/services/claude-transcript.d.ts.map +1 -1
- package/dist/services/claude-transcript.js +33 -0
- package/dist/services/claude-transcript.js.map +1 -1
- package/dist/services/codex-app-runner-protocol.d.ts +61 -0
- package/dist/services/codex-app-runner-protocol.d.ts.map +1 -0
- package/dist/services/codex-app-runner-protocol.js +170 -0
- package/dist/services/codex-app-runner-protocol.js.map +1 -0
- package/dist/services/codex-app-turn-controller.d.ts +72 -0
- package/dist/services/codex-app-turn-controller.d.ts.map +1 -0
- package/dist/services/codex-app-turn-controller.js +524 -0
- package/dist/services/codex-app-turn-controller.js.map +1 -0
- package/dist/services/codex-runner-freshness.d.ts +39 -0
- package/dist/services/codex-runner-freshness.d.ts.map +1 -0
- package/dist/services/codex-runner-freshness.js +78 -0
- package/dist/services/codex-runner-freshness.js.map +1 -0
- package/dist/services/connector-store.d.ts +6 -0
- package/dist/services/connector-store.d.ts.map +1 -1
- package/dist/services/connector-store.js.map +1 -1
- package/dist/services/grant-store.d.ts.map +1 -1
- package/dist/services/grant-store.js +16 -0
- package/dist/services/grant-store.js.map +1 -1
- package/dist/services/hermes-transcript.d.ts +1 -0
- package/dist/services/hermes-transcript.d.ts.map +1 -1
- package/dist/services/hermes-transcript.js +49 -0
- package/dist/services/hermes-transcript.js.map +1 -1
- package/dist/services/sandbox-migration.d.ts +14 -0
- package/dist/services/sandbox-migration.d.ts.map +1 -0
- package/dist/services/sandbox-migration.js +85 -0
- package/dist/services/sandbox-migration.js.map +1 -0
- package/dist/services/sandbox-store.d.ts +28 -0
- package/dist/services/sandbox-store.d.ts.map +1 -1
- package/dist/services/sandbox-store.js +82 -0
- package/dist/services/sandbox-store.js.map +1 -1
- package/dist/services/schedule-split-migration.d.ts +2 -0
- package/dist/services/schedule-split-migration.d.ts.map +1 -0
- package/dist/services/schedule-split-migration.js +159 -0
- package/dist/services/schedule-split-migration.js.map +1 -0
- package/dist/services/schedule-store.d.ts +28 -4
- package/dist/services/schedule-store.d.ts.map +1 -1
- package/dist/services/schedule-store.js +118 -33
- package/dist/services/schedule-store.js.map +1 -1
- package/dist/services/trigger-types.d.ts +18 -1
- package/dist/services/trigger-types.d.ts.map +1 -1
- package/dist/services/trigger-types.js +3 -0
- package/dist/services/trigger-types.js.map +1 -1
- package/dist/services/vc-meeting-consumer-profile-store.d.ts.map +1 -1
- package/dist/services/vc-meeting-consumer-profile-store.js +1 -0
- package/dist/services/vc-meeting-consumer-profile-store.js.map +1 -1
- package/dist/services/vc-meeting-consumer-profile-templates.d.ts +32 -0
- package/dist/services/vc-meeting-consumer-profile-templates.d.ts.map +1 -0
- package/dist/services/vc-meeting-consumer-profile-templates.js +106 -0
- package/dist/services/vc-meeting-consumer-profile-templates.js.map +1 -0
- package/dist/services/vc-meeting-delivery-feed.d.ts +1 -1
- package/dist/services/vc-meeting-delivery-feed.d.ts.map +1 -1
- package/dist/services/vc-meeting-delivery-feed.js +2 -1
- package/dist/services/vc-meeting-delivery-feed.js.map +1 -1
- package/dist/services/vc-meeting-delivery-hub-store.d.ts +3 -1
- package/dist/services/vc-meeting-delivery-hub-store.d.ts.map +1 -1
- package/dist/services/vc-meeting-delivery-hub-store.js +11 -0
- package/dist/services/vc-meeting-delivery-hub-store.js.map +1 -1
- package/dist/services/vc-meeting-delivery-protocol.d.ts +9 -2
- package/dist/services/vc-meeting-delivery-protocol.d.ts.map +1 -1
- package/dist/services/vc-meeting-delivery-protocol.js +10 -1
- package/dist/services/vc-meeting-delivery-protocol.js.map +1 -1
- package/dist/services/vc-meeting-delivery-receiver.d.ts.map +1 -1
- package/dist/services/vc-meeting-delivery-receiver.js +10 -1
- package/dist/services/vc-meeting-delivery-receiver.js.map +1 -1
- package/dist/services/vc-meeting-delivery-store.d.ts +12 -2
- package/dist/services/vc-meeting-delivery-store.d.ts.map +1 -1
- package/dist/services/vc-meeting-delivery-store.js +14 -2
- package/dist/services/vc-meeting-delivery-store.js.map +1 -1
- package/dist/services/vc-meeting-im-reply.d.ts +2 -1
- package/dist/services/vc-meeting-im-reply.d.ts.map +1 -1
- package/dist/services/vc-meeting-im-reply.js +19 -3
- package/dist/services/vc-meeting-im-reply.js.map +1 -1
- package/dist/services/vc-meeting-listener-output-protocol.d.ts +27 -0
- package/dist/services/vc-meeting-listener-output-protocol.d.ts.map +1 -0
- package/dist/services/vc-meeting-listener-output-protocol.js +58 -0
- package/dist/services/vc-meeting-listener-output-protocol.js.map +1 -0
- package/dist/services/vc-meeting-listener-topic-store.d.ts +31 -0
- package/dist/services/vc-meeting-listener-topic-store.d.ts.map +1 -0
- package/dist/services/vc-meeting-listener-topic-store.js +120 -0
- package/dist/services/vc-meeting-listener-topic-store.js.map +1 -0
- package/dist/services/vc-meeting-runtime-store.d.ts +2 -1
- package/dist/services/vc-meeting-runtime-store.d.ts.map +1 -1
- package/dist/services/vc-meeting-runtime-store.js +8 -0
- package/dist/services/vc-meeting-runtime-store.js.map +1 -1
- package/dist/services/vc-meeting-send-policy.d.ts +8 -1
- package/dist/services/vc-meeting-send-policy.d.ts.map +1 -1
- package/dist/services/vc-meeting-send-policy.js +8 -0
- package/dist/services/vc-meeting-send-policy.js.map +1 -1
- package/dist/types.d.ts +53 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/allowed-users-apply.d.ts +76 -0
- package/dist/utils/allowed-users-apply.d.ts.map +1 -0
- package/dist/utils/allowed-users-apply.js +123 -0
- package/dist/utils/allowed-users-apply.js.map +1 -0
- package/dist/utils/allowed-users-cache.d.ts +29 -0
- package/dist/utils/allowed-users-cache.d.ts.map +1 -0
- package/dist/utils/allowed-users-cache.js +82 -0
- package/dist/utils/allowed-users-cache.js.map +1 -0
- package/dist/utils/child-env.d.ts +4 -3
- package/dist/utils/child-env.d.ts.map +1 -1
- package/dist/utils/child-env.js +16 -1
- package/dist/utils/child-env.js.map +1 -1
- package/dist/utils/cli-usage-limit.d.ts +27 -1
- package/dist/utils/cli-usage-limit.d.ts.map +1 -1
- package/dist/utils/cli-usage-limit.js +92 -20
- package/dist/utils/cli-usage-limit.js.map +1 -1
- package/dist/utils/input-gate.d.ts +2 -0
- package/dist/utils/input-gate.d.ts.map +1 -1
- package/dist/utils/input-gate.js +2 -0
- package/dist/utils/input-gate.js.map +1 -1
- package/dist/utils/runtime-build-id.d.ts +21 -0
- package/dist/utils/runtime-build-id.d.ts.map +1 -0
- package/dist/utils/runtime-build-id.js +83 -0
- package/dist/utils/runtime-build-id.js.map +1 -0
- package/dist/utils/terminal-renderer.d.ts +3 -3
- package/dist/utils/terminal-renderer.js +3 -3
- package/dist/vc-agent/cards.d.ts.map +1 -1
- package/dist/vc-agent/cards.js +21 -1
- package/dist/vc-agent/cards.js.map +1 -1
- package/dist/worker.js +1010 -669
- package/dist/worker.js.map +1 -1
- package/dist/workflows/events/payloads.d.ts +56 -0
- package/dist/workflows/events/payloads.d.ts.map +1 -1
- package/dist/workflows/events/payloads.js +9 -0
- package/dist/workflows/events/payloads.js.map +1 -1
- package/dist/workflows/events/schema.d.ts +86 -0
- package/dist/workflows/events/schema.d.ts.map +1 -1
- package/dist/workflows/hostExecutors/botmux-schedule.d.ts.map +1 -1
- package/dist/workflows/hostExecutors/botmux-schedule.js +13 -1
- package/dist/workflows/hostExecutors/botmux-schedule.js.map +1 -1
- package/dist/workflows/migration/v2-run-archive-schema.d.ts +20 -20
- package/dist/workflows/migration/v2-to-v3.d.ts.map +1 -1
- package/dist/workflows/migration/v2-to-v3.js +5 -1
- package/dist/workflows/migration/v2-to-v3.js.map +1 -1
- package/dist/workflows/shared/sandbox-policy.d.ts +10 -1
- package/dist/workflows/shared/sandbox-policy.d.ts.map +1 -1
- package/dist/workflows/shared/sandbox-policy.js +12 -0
- package/dist/workflows/shared/sandbox-policy.js.map +1 -1
- package/dist/workflows/v3/architect.d.ts.map +1 -1
- package/dist/workflows/v3/architect.js +1 -0
- package/dist/workflows/v3/architect.js.map +1 -1
- package/dist/workflows/v3/bot-resolve.d.ts.map +1 -1
- package/dist/workflows/v3/bot-resolve.js +36 -0
- package/dist/workflows/v3/bot-resolve.js.map +1 -1
- package/dist/workflows/v3/cli-run.d.ts +9 -0
- package/dist/workflows/v3/cli-run.d.ts.map +1 -1
- package/dist/workflows/v3/cli-run.js +17 -2
- package/dist/workflows/v3/cli-run.js.map +1 -1
- package/dist/workflows/v3/contract.d.ts +8 -0
- package/dist/workflows/v3/contract.d.ts.map +1 -1
- package/dist/workflows/v3/contract.js.map +1 -1
- package/dist/workflows/v3/goal-cli.d.ts +58 -0
- package/dist/workflows/v3/goal-cli.d.ts.map +1 -0
- package/dist/workflows/v3/goal-cli.js +430 -0
- package/dist/workflows/v3/goal-cli.js.map +1 -0
- package/dist/workflows/v3/library-materialize.d.ts.map +1 -1
- package/dist/workflows/v3/library-materialize.js +5 -1
- package/dist/workflows/v3/library-materialize.js.map +1 -1
- package/dist/workflows/v3/library-schema.d.ts +21 -0
- package/dist/workflows/v3/library-schema.d.ts.map +1 -1
- package/dist/workflows/v3/library-schema.js +95 -1
- package/dist/workflows/v3/library-schema.js.map +1 -1
- package/dist/workflows/v3/progress-projection.d.ts +3 -0
- package/dist/workflows/v3/progress-projection.d.ts.map +1 -1
- package/dist/workflows/v3/progress-projection.js +23 -1
- package/dist/workflows/v3/progress-projection.js.map +1 -1
- package/dist/workflows/v3/run-envelope.d.ts +46 -1
- package/dist/workflows/v3/run-envelope.d.ts.map +1 -1
- package/dist/workflows/v3/run-envelope.js +32 -1
- package/dist/workflows/v3/run-envelope.js.map +1 -1
- package/package.json +2 -2
- package/dist/dashboard-web/chunks/bot-defaults-page-O376WSER.js +0 -5
- package/dist/dashboard-web/chunks/chunk-H7P5UV4W.js +0 -49
- package/dist/dashboard-web/chunks/chunk-SZOB6VK2.js +0 -1
- package/dist/dashboard-web/chunks/connectors-page-IR54AOAI.js +0 -1
- package/dist/dashboard-web/chunks/overview-page-QJMI6NLW.js +0 -1
- package/dist/dashboard-web/chunks/sessions-page-KWLGBX6G.js +0 -3
- package/dist/dashboard-web/chunks/settings-page-TNZEPC7V.js +0 -2
- package/dist/services/sandbox-land.d.ts +0 -45
- package/dist/services/sandbox-land.d.ts.map +0 -1
- package/dist/services/sandbox-land.js +0 -394
- package/dist/services/sandbox-land.js.map +0 -1
- package/dist/utils/screen-analyzer.d.ts +0 -75
- package/dist/utils/screen-analyzer.d.ts.map +0 -1
- package/dist/utils/screen-analyzer.js +0 -333
- package/dist/utils/screen-analyzer.js.map +0 -1
package/dist/daemon.js
CHANGED
|
@@ -2,8 +2,9 @@ import { execFileSync } from 'node:child_process';
|
|
|
2
2
|
import { createHash, randomUUID } from 'node:crypto';
|
|
3
3
|
import { readFileSync, existsSync, mkdirSync, unlinkSync } from 'node:fs';
|
|
4
4
|
import { atomicWriteFileSync } from './utils/atomic-write.js';
|
|
5
|
+
import { readAllowedUsersResolveCache, writeAllowedUsersResolveCache } from './utils/allowed-users-cache.js';
|
|
5
6
|
import { join, dirname } from 'node:path';
|
|
6
|
-
import { homedir } from 'node:os';
|
|
7
|
+
import { homedir, loadavg, cpus, totalmem, freemem } from 'node:os';
|
|
7
8
|
import { fileURLToPath } from 'node:url';
|
|
8
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
10
|
const __dirname = dirname(__filename);
|
|
@@ -14,19 +15,24 @@ import { resolveBotmuxDataDir } from './core/data-dir.js';
|
|
|
14
15
|
import { reloadExactDaemonBotConfig } from './core/daemon-config-fence.js';
|
|
15
16
|
import { writeHeartbeat } from './core/daemon-heartbeat.js';
|
|
16
17
|
import { botmuxWrapperFiles } from './core/botmux-wrapper.js';
|
|
18
|
+
import { evaluateOverload, formatOverloadAlert, buildOverloadAlertCard, buildOverloadRecoveredCard, initialOverloadCardState, DEFAULT_OVERLOAD_THRESHOLDS, INITIAL_OVERLOAD_STATE, } from './core/host-overload-alert.js';
|
|
19
|
+
import { registerOverloadNonce } from './im/lark/overload-nonce.js';
|
|
20
|
+
import { countHostOverload } from './im/lark/card-handler.js';
|
|
17
21
|
import { startMaintenance, stopMaintenance } from './core/maintenance.js';
|
|
18
22
|
import { selectCodexRuntimeUpdateTargets, startCliRuntimeUpdateMonitor, stopCliRuntimeUpdateMonitor, } from './core/cli-runtime-update.js';
|
|
19
23
|
import { sendRestartReportIfPending } from './core/restart-report.js';
|
|
20
24
|
import { statSync } from 'node:fs';
|
|
21
25
|
import { addReaction, getChatMode, getChatNameAndMode, getMessageChatId, listChatMemberOpenIds, MessageWithdrawnError, replyMessage, resolveAllowedUsersWithMap, sendMessage, sendUserMessage, updateMessage } from './im/lark/client.js';
|
|
22
26
|
import { resolveGroupJoinPrompt, waitForAllowedUserInChat } from './core/auto-start.js';
|
|
23
|
-
import { loadBotConfigAtIndex, loadBotConfigs, isManagedActivationStartingAtIndex, registerBot, getBot, getAllBots, getOwnerOpenId, findOncallChat, effectiveDefaultWorkingDir, effectiveBotDisplayName, resolveVcMeetingConsumerProfiles, } from './bot-registry.js';
|
|
27
|
+
import { loadBotConfigAtIndex, loadBotConfigs, isManagedActivationStartingAtIndex, registerBot, getBot, getAllBots, getOwnerOpenId, findOncallChat, effectiveDefaultWorkingDir, effectiveBotDisplayName, resolveVcMeetingConsumerProfiles, setResolvedAllowedUsersRepublishHook, setAllowedUsersResolveRetryHook, } from './bot-registry.js';
|
|
24
28
|
import { setDisplayNameRefresher, findConfigField, applyConfigField } from './services/bot-config-store.js';
|
|
25
29
|
import { renameBotOnOpenPlatform, changeBotAvatarOnOpenPlatform } from './services/open-platform-rename.js';
|
|
30
|
+
import { migrateSandboxConfigAtStartup } from './services/sandbox-migration.js';
|
|
26
31
|
import * as sessionStore from './services/session-store.js';
|
|
27
32
|
import * as chatFirstSeenStore from './services/chat-first-seen-store.js';
|
|
28
33
|
import { ensureDefaultOncallBound } from './services/oncall-store.js';
|
|
29
34
|
import * as scheduleStore from './services/schedule-store.js';
|
|
35
|
+
import { migrateSharedSchedulesAtStartup } from './services/schedule-split-migration.js';
|
|
30
36
|
import * as messageQueue from './services/message-queue.js';
|
|
31
37
|
import { emitHookEvent, emitHookEventLocal, HOOK_EVENTS } from './services/hook-runner.js';
|
|
32
38
|
import { setSessionLifecycleShutdown } from './services/session-lifecycle-hooks.js';
|
|
@@ -35,7 +41,8 @@ import { expandMergeForward } from './im/lark/merge-forward.js';
|
|
|
35
41
|
import { bindResourcesToMessage, composeForwardFollowupContent, mergeMessageMentions } from './im/lark/forward-followup-content.js';
|
|
36
42
|
import { buildQuoteHint } from './im/lark/quote-hint.js';
|
|
37
43
|
import { logger } from './utils/logger.js';
|
|
38
|
-
import {
|
|
44
|
+
import { applyAllowedUsersResolve } from './utils/allowed-users-apply.js';
|
|
45
|
+
import { withFileLock, withFileLockSync } from './utils/file-lock.js';
|
|
39
46
|
import { delay } from './utils/timing.js';
|
|
40
47
|
import { BoundedMap } from './utils/bounded-map.js';
|
|
41
48
|
import { checkAllowedChatGroupsConfig } from './services/allowed-chat-groups.js';
|
|
@@ -115,8 +122,30 @@ let selfV3BootInstanceId;
|
|
|
115
122
|
/** Generic daemon identity used by internal receiver endpoints. Unlike the
|
|
116
123
|
* VC listener switch, every agent daemon may receive a fenced membership. */
|
|
117
124
|
let selfDaemonLarkAppId;
|
|
125
|
+
/**
|
|
126
|
+
* Live dashboard descriptor for THIS daemon's single bot. Held module-level so
|
|
127
|
+
* the deferred allowedUsers resolve retry (a detached setTimeout that has no
|
|
128
|
+
* `desc` in scope) can patch `resolvedAllowedUsers` and republish coherently —
|
|
129
|
+
* and so the 30s heartbeat, which rewrites this same object, never clobbers a
|
|
130
|
+
* healed value back to stale. One daemon per bot, so a single ref is correct.
|
|
131
|
+
*/
|
|
132
|
+
let selfDaemonDescriptor;
|
|
133
|
+
/**
|
|
134
|
+
* Patch the live descriptor's `resolvedAllowedUsers` (ou_ only) and rewrite it.
|
|
135
|
+
* Safe to call from the deferred resolve retry. Best-effort.
|
|
136
|
+
*/
|
|
137
|
+
function republishResolvedAllowedUsers(larkAppId, resolved) {
|
|
138
|
+
const desc = selfDaemonDescriptor;
|
|
139
|
+
if (!desc || desc.larkAppId !== larkAppId)
|
|
140
|
+
return;
|
|
141
|
+
desc.resolvedAllowedUsers = resolved.filter(u => u.startsWith('ou_'));
|
|
142
|
+
try {
|
|
143
|
+
writeDaemonDescriptor(desc);
|
|
144
|
+
}
|
|
145
|
+
catch { /* best effort */ }
|
|
146
|
+
}
|
|
118
147
|
let vcMeetingTerminalReconciler;
|
|
119
|
-
import { isBotMentioned, probeBotOpenId, startLarkEventDispatcher, markForwardFollowupsSessionsReady, writeBotInfoFile, canOperate, evaluateTalk, grantCommandRestriction, isKnownPeerBot, checkRequiredScopes } from './im/lark/event-dispatcher.js';
|
|
148
|
+
import { isBotMentioned, probeBotOpenId, startLarkEventDispatcher, markForwardFollowupsSessionsReady, writeBotInfoFile, canOperate, canRunDaemonCommand, evaluateTalk, grantCommandRestriction, isKnownPeerBot, checkRequiredScopes } from './im/lark/event-dispatcher.js';
|
|
120
149
|
import { getDocSubscription, listAllDocSubscriptions, removeDocSubscription, setDocCommentPollCursor } from './services/doc-subs-store.js';
|
|
121
150
|
import { BOT_REPLY_SENTINEL, subscribeDocFile, unsubscribeDocFile, addCommentReaction, hasBotSentinel, isBotAuthoredReply, listDocComments } from './im/lark/doc-comment.js';
|
|
122
151
|
import { learnFromMentions, resolveSender, flushIdentityCacheSync } from './im/lark/identity-cache.js';
|
|
@@ -142,7 +171,7 @@ import { createGroupWithBots } from './services/group-creator.js';
|
|
|
142
171
|
import { addBotToChat, isInChat } from './services/groups-store.js';
|
|
143
172
|
import { setChatReplyMode } from './services/chat-reply-mode-store.js';
|
|
144
173
|
import { hasVcMeetingEndedTombstone, listVcMeetingRuntimeSessionsByListenerAndAgent, listVcMeetingRuntimeSessions, pruneExpiredVcMeetingRuntimeSessions, recordVcMeetingEndedTombstone, recordVcMeetingRuntimeSession, removeVcMeetingRuntimeSession, } from './services/vc-meeting-runtime-store.js';
|
|
145
|
-
import { computeVcMeetingConsumerProfileHash } from './services/vc-meeting-profile-instructions.js';
|
|
174
|
+
import { computeVcMeetingConsumerProfileHash, normalizeVcMeetingProfileInstructions, } from './services/vc-meeting-profile-instructions.js';
|
|
146
175
|
import { bootstrapVcMeetingDefaultConsumerProfile } from './services/vc-meeting-consumer-profile-bootstrap.js';
|
|
147
176
|
import { getVcMeetingPreparation, normalizeVcMeetingNumber, } from './services/vc-meeting-preparations-store.js';
|
|
148
177
|
import { abandonPoisonedVcMeetingDelivery, getVcMeetingDeliveryStatus, handleVcMeetingTurnTerminal, handleVcMeetingWorkerGenerationExit, receiveVcMeetingDelivery, registerVcMeetingMember, retryPoisonedVcMeetingDelivery, } from './services/vc-meeting-delivery-receiver.js';
|
|
@@ -153,6 +182,7 @@ import { applyVcMeetingHubMemberProjection, freezeVcMeetingHubDeliveryAssignment
|
|
|
153
182
|
import { getVcMeetingFeedMetadataState, ingestVcMeetingFeedMetadata, } from './services/vc-meeting-feed-metadata-store.js';
|
|
154
183
|
import { VcMeetingTerminalReconciler } from './services/vc-meeting-terminal-reconciler.js';
|
|
155
184
|
import { resolveVcMeetingImTurnOrigin, verifyVcMeetingManagedOriginClaim, } from './services/vc-meeting-send-policy.js';
|
|
185
|
+
import { ensureVcMeetingListenerTopicRoot, } from './services/vc-meeting-listener-topic-store.js';
|
|
156
186
|
import { finishVcMeetingManagedActionProvider, finishVcMeetingManagedApprovalCard, requestVcMeetingManagedImAction, requestVcMeetingManagedAction, resolveVcMeetingManagedActionApproval, } from './services/vc-meeting-action-gate.js';
|
|
157
187
|
import { claimVcMeetingApprovalCardAttempt, findVcMeetingAction, listVcMeetingActionScopes, listVcMeetingActions, reconcileVcMeetingActionsOnBoot, } from './services/vc-meeting-action-store.js';
|
|
158
188
|
import { authorizeVcMeetingDaemonControlRequest, ensureVcMeetingDaemonAuthToken, withVcMeetingDaemonAuthHeader, } from './services/vc-meeting-daemon-auth.js';
|
|
@@ -802,6 +832,8 @@ const VC_MEETING_SYNC_INTERVAL_OPTIONS_MS = [15_000, 30_000, 60_000, 90_000];
|
|
|
802
832
|
const VC_MEETING_CUSTOM_SYNC_INTERVAL_MIN_SECONDS = 10;
|
|
803
833
|
const VC_MEETING_CUSTOM_SYNC_INTERVAL_MAX_SECONDS = 3_600;
|
|
804
834
|
const VC_MEETING_CUSTOM_SYNC_INTERVAL_FIELD = 'vc_meeting_custom_interval_seconds';
|
|
835
|
+
const VC_MEETING_ACTIVATION_CONTEXT_FIELD = 'vc_meeting_activation_context';
|
|
836
|
+
const VC_MEETING_ACTIVATION_CONTEXT_MAX_CHARS = 2_000;
|
|
805
837
|
const vcMeetingEndedTombstones = new BoundedMap(2_000);
|
|
806
838
|
const vcMeetingPendingInvites = new BoundedMap(2_000);
|
|
807
839
|
function vcMeetingSessionKey(larkAppId, meetingId) {
|
|
@@ -898,6 +930,26 @@ function normalizeVcMeetingCustomSyncIntervalMs(value) {
|
|
|
898
930
|
function vcMeetingCustomSyncIntervalFromCard(data) {
|
|
899
931
|
return normalizeVcMeetingCustomSyncIntervalMs(data.action?.form_value?.[VC_MEETING_CUSTOM_SYNC_INTERVAL_FIELD]);
|
|
900
932
|
}
|
|
933
|
+
function normalizeVcMeetingActivationContext(value) {
|
|
934
|
+
if (value === undefined || value === null)
|
|
935
|
+
return { ok: true };
|
|
936
|
+
const text = (Array.isArray(value) ? String(value[0] ?? '') : String(value))
|
|
937
|
+
.replace(/\r\n?/g, '\n')
|
|
938
|
+
.trim();
|
|
939
|
+
if (!text)
|
|
940
|
+
return { ok: true };
|
|
941
|
+
if (text.length > VC_MEETING_ACTIVATION_CONTEXT_MAX_CHARS) {
|
|
942
|
+
return { ok: false, error: `本次会议补充说明不能超过 ${VC_MEETING_ACTIVATION_CONTEXT_MAX_CHARS} 个字符` };
|
|
943
|
+
}
|
|
944
|
+
const normalized = normalizeVcMeetingProfileInstructions(text);
|
|
945
|
+
if (!normalized.ok) {
|
|
946
|
+
return { ok: false, error: `本次会议补充说明无效:${normalized.error}` };
|
|
947
|
+
}
|
|
948
|
+
return { ok: true, context: normalized.instructions };
|
|
949
|
+
}
|
|
950
|
+
function vcMeetingActivationContextFromCard(data) {
|
|
951
|
+
return normalizeVcMeetingActivationContext(data.action?.form_value?.[VC_MEETING_ACTIVATION_CONTEXT_FIELD]);
|
|
952
|
+
}
|
|
901
953
|
function vcMeetingSessionFlushIntervalMs(session, cfg) {
|
|
902
954
|
return session.syncIntervalMs ?? cfg.flushIntervalMs ?? DEFAULT_VC_MEETING_FLUSH_INTERVAL_MS;
|
|
903
955
|
}
|
|
@@ -1888,6 +1940,20 @@ async function sessionReply(anchor, content, msgType = 'text', larkAppId, turnId
|
|
|
1888
1940
|
return sendWithHookPolicy(chatId, content, msgType, opts.uuid);
|
|
1889
1941
|
}
|
|
1890
1942
|
}
|
|
1943
|
+
if (opts?.placement === 'chat') {
|
|
1944
|
+
return sendWithHookPolicy(chatId, content, msgType, opts.uuid);
|
|
1945
|
+
}
|
|
1946
|
+
if (opts?.placement === 'topic') {
|
|
1947
|
+
const topicKey = opts.meetingTopicKey;
|
|
1948
|
+
if (!topicKey || topicKey.targetChatId !== chatId) {
|
|
1949
|
+
throw new Error('meeting topic placement is missing a matching durable topic key');
|
|
1950
|
+
}
|
|
1951
|
+
const root = await ensureVcMeetingListenerTopicRoot(config.session.dataDir, topicKey, () => sendWithHookPolicy(chatId, content, msgType, opts.uuid));
|
|
1952
|
+
if (root.created) {
|
|
1953
|
+
return root.rootMessageId;
|
|
1954
|
+
}
|
|
1955
|
+
return replyWithHookPolicy(root.rootMessageId, content, msgType, true, opts.uuid);
|
|
1956
|
+
}
|
|
1891
1957
|
if (ds?.scope === 'chat') {
|
|
1892
1958
|
const fresh = readSessionFreshFromDisk(ds.session.sessionId, ds.larkAppId);
|
|
1893
1959
|
if (fresh)
|
|
@@ -2102,6 +2168,123 @@ function removeDaemonDescriptor(larkAppId) {
|
|
|
2102
2168
|
catch { /* ignore */ }
|
|
2103
2169
|
}
|
|
2104
2170
|
}
|
|
2171
|
+
/**
|
|
2172
|
+
* Persistent last-known-good `raw entry → ou_` cache for allowedUsers. The
|
|
2173
|
+
* read/write primitives live in utils/allowed-users-cache.ts so the runtime
|
|
2174
|
+
* set/revoke paths (services layer) write through the same sidecar and it never
|
|
2175
|
+
* diverges from the live allowlist. These thin wrappers bind the data dir.
|
|
2176
|
+
*/
|
|
2177
|
+
function readAllowedUsersCache(larkAppId) {
|
|
2178
|
+
return readAllowedUsersResolveCache(config.session.dataDir, larkAppId);
|
|
2179
|
+
}
|
|
2180
|
+
function writeAllowedUsersCache(larkAppId, map, opts = {}) {
|
|
2181
|
+
writeAllowedUsersResolveCache(config.session.dataDir, larkAppId, { map, ...opts });
|
|
2182
|
+
}
|
|
2183
|
+
/** Raw entries the resolver flagged as definitively gone (safe to prune from cache). */
|
|
2184
|
+
function definitiveEntriesOf(entryStatus) {
|
|
2185
|
+
if (!entryStatus)
|
|
2186
|
+
return [];
|
|
2187
|
+
const out = [];
|
|
2188
|
+
for (const [entry, status] of entryStatus.entries()) {
|
|
2189
|
+
if (status === 'definitive')
|
|
2190
|
+
out.push(entry);
|
|
2191
|
+
}
|
|
2192
|
+
return out;
|
|
2193
|
+
}
|
|
2194
|
+
function notifyAllowedUsersResolveFailure(larkAppId, notice, recipients) {
|
|
2195
|
+
logger.error(`[${larkAppId}] ${notice}`);
|
|
2196
|
+
const unique = [...new Set(recipients.filter(u => typeof u === 'string' && u.startsWith('ou_')))];
|
|
2197
|
+
if (unique.length === 0) {
|
|
2198
|
+
logger.error(`[${larkAppId}] allowedUsers resolve failed with no open_id recipient available for DM; ` +
|
|
2199
|
+
`check daemon logs and Feishu contact API, then restart this bot.`);
|
|
2200
|
+
return;
|
|
2201
|
+
}
|
|
2202
|
+
// Fire-and-forget: the owner DM must NOT block daemon startup. sendUserMessage
|
|
2203
|
+
// uses an untimed SDK path; awaiting it on the boot-critical path (before the
|
|
2204
|
+
// WS listener starts) would stall the bot coming online during the very
|
|
2205
|
+
// contact/network partition that triggered this notice. Bound each attempt
|
|
2206
|
+
// with a deadline and detach.
|
|
2207
|
+
void (async () => {
|
|
2208
|
+
for (const openId of unique.slice(0, 5)) {
|
|
2209
|
+
try {
|
|
2210
|
+
await sendUserMessage(larkAppId, openId, `⚠️ Botmux allowedUsers 解析告警\n\n${notice}`, 'text', undefined, { timeoutMs: 10_000 });
|
|
2211
|
+
}
|
|
2212
|
+
catch (err) {
|
|
2213
|
+
logger.warn(`[${larkAppId}] failed to DM allowedUsers resolve notice to ${openId.slice(0, 12)}: ${err?.message ?? err}`);
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
})();
|
|
2217
|
+
}
|
|
2218
|
+
function scheduleAllowedUsersResolveRetry(larkAppId, attempt = 1) {
|
|
2219
|
+
if (attempt > 3)
|
|
2220
|
+
return;
|
|
2221
|
+
const delayMs = attempt === 1 ? 30_000 : attempt === 2 ? 120_000 : 300_000;
|
|
2222
|
+
const timer = setTimeout(() => {
|
|
2223
|
+
void (async () => {
|
|
2224
|
+
let bot;
|
|
2225
|
+
try {
|
|
2226
|
+
bot = getBot(larkAppId);
|
|
2227
|
+
}
|
|
2228
|
+
catch {
|
|
2229
|
+
return;
|
|
2230
|
+
}
|
|
2231
|
+
const configured = bot.config.allowedUsers ?? [];
|
|
2232
|
+
if (configured.length === 0)
|
|
2233
|
+
return;
|
|
2234
|
+
// Fingerprint the config we resolve against. A /revoke or `set allowedUsers`
|
|
2235
|
+
// can land during the cross-network resolve await below; without this fence
|
|
2236
|
+
// the stale retry result would clobber the newer runtime/cache/descriptor —
|
|
2237
|
+
// reviving a just-revoked guest, or forking runtime from the persisted
|
|
2238
|
+
// config until the next restart. If the config changed, drop this result
|
|
2239
|
+
// (the mutation path already wrote through, and if it needs a retry it
|
|
2240
|
+
// schedules its own).
|
|
2241
|
+
const fingerprint = JSON.stringify(configured);
|
|
2242
|
+
try {
|
|
2243
|
+
const resolveResult = await resolveAllowedUsersWithMap(larkAppId, configured);
|
|
2244
|
+
let liveBot;
|
|
2245
|
+
try {
|
|
2246
|
+
liveBot = getBot(larkAppId);
|
|
2247
|
+
}
|
|
2248
|
+
catch {
|
|
2249
|
+
return;
|
|
2250
|
+
}
|
|
2251
|
+
if (JSON.stringify(liveBot.config.allowedUsers ?? []) !== fingerprint) {
|
|
2252
|
+
logger.info(`[${larkAppId}] allowedUsers resolve retry #${attempt} discarded: config changed during resolve`);
|
|
2253
|
+
return;
|
|
2254
|
+
}
|
|
2255
|
+
const applied = applyAllowedUsersResolve({
|
|
2256
|
+
rawEntries: configured,
|
|
2257
|
+
previousResolvedMap: readAllowedUsersCache(larkAppId),
|
|
2258
|
+
resolveResult,
|
|
2259
|
+
});
|
|
2260
|
+
liveBot.resolvedAllowedUsers = applied.resolved;
|
|
2261
|
+
liveBot.rawAllowedUserResolution = applied.map;
|
|
2262
|
+
// Persist the recovered map + republish the descriptor so the dashboard
|
|
2263
|
+
// (create-group creator picker) and the next boot both see the healed
|
|
2264
|
+
// open_ids — otherwise a successful retry only heals in-memory state.
|
|
2265
|
+
// Prune definitively-gone entries + keys no longer configured so a stale
|
|
2266
|
+
// owner can't be revived later.
|
|
2267
|
+
writeAllowedUsersCache(larkAppId, applied.map, {
|
|
2268
|
+
deleteEntries: definitiveEntriesOf(resolveResult.entryStatus),
|
|
2269
|
+
retainKeys: configured,
|
|
2270
|
+
});
|
|
2271
|
+
republishResolvedAllowedUsers(larkAppId, applied.resolved);
|
|
2272
|
+
if (!applied.failed) {
|
|
2273
|
+
logger.info(`[${larkAppId}] allowedUsers resolve retry succeeded: ${applied.resolved.join(', ') || '(empty — entries definitively gone)'}`);
|
|
2274
|
+
return;
|
|
2275
|
+
}
|
|
2276
|
+
logger.warn(`[${larkAppId}] allowedUsers resolve retry #${attempt} still degraded` +
|
|
2277
|
+
`${applied.usedFallback ? ' (on cache)' : applied.resolved.length ? '' : ' (allowlist empty)'}`);
|
|
2278
|
+
scheduleAllowedUsersResolveRetry(larkAppId, attempt + 1);
|
|
2279
|
+
}
|
|
2280
|
+
catch (err) {
|
|
2281
|
+
logger.warn(`[${larkAppId}] allowedUsers resolve retry #${attempt} threw: ${err?.message ?? err}`);
|
|
2282
|
+
scheduleAllowedUsersResolveRetry(larkAppId, attempt + 1);
|
|
2283
|
+
}
|
|
2284
|
+
})();
|
|
2285
|
+
}, delayMs);
|
|
2286
|
+
timer.unref?.();
|
|
2287
|
+
}
|
|
2105
2288
|
// ─── Version tracking ────────────────────────────────────────────────────────
|
|
2106
2289
|
function refreshCliVersion(cliId, cliPathOverride) {
|
|
2107
2290
|
const now = Date.now();
|
|
@@ -7580,11 +7763,29 @@ function vcMeetingRuntimeAgentForProfile(profile, status, opts = {}) {
|
|
|
7580
7763
|
...(opts.activationError ? { activationError: opts.activationError.slice(0, 500) } : {}),
|
|
7581
7764
|
...(filter ? { filter } : {}),
|
|
7582
7765
|
responseMode: profile.responseMode,
|
|
7766
|
+
listenerPlacement: profile.listenerDelivery?.placement ?? 'auto',
|
|
7583
7767
|
capabilities: vcMeetingCanonicalProfileCapabilities(profile),
|
|
7584
7768
|
ownedSinks: vcMeetingCanonicalProfileOwnedSinks(profile),
|
|
7585
7769
|
deliveryProfileHash: opts.deliveryProfileHash ?? vcMeetingConsumerProfileHash(profile),
|
|
7586
7770
|
};
|
|
7587
7771
|
}
|
|
7772
|
+
function vcMeetingProfileWithActivationContext(profile, activationContext) {
|
|
7773
|
+
if (!activationContext)
|
|
7774
|
+
return profile;
|
|
7775
|
+
const contextSection = [
|
|
7776
|
+
'本次会议补充说明(仅适用于本次会议,不修改角色预设):',
|
|
7777
|
+
activationContext,
|
|
7778
|
+
].join('\n');
|
|
7779
|
+
const combined = profile.instructions
|
|
7780
|
+
? `${profile.instructions}\n\n${contextSection}`
|
|
7781
|
+
: contextSection;
|
|
7782
|
+
const normalized = normalizeVcMeetingProfileInstructions(combined);
|
|
7783
|
+
if (!normalized.ok || !normalized.instructions) {
|
|
7784
|
+
throw new Error(`profile ${profile.id} 的预设职责与本次会议补充说明无法合并:`
|
|
7785
|
+
+ `${normalized.ok ? '内容为空' : normalized.error}`);
|
|
7786
|
+
}
|
|
7787
|
+
return { ...profile, instructions: normalized.instructions };
|
|
7788
|
+
}
|
|
7588
7789
|
/** Rehydrate the exact immutable profile snapshot selected for this member
|
|
7589
7790
|
* epoch. Current bots.json is only authoritative for a first join/re-add; an
|
|
7590
7791
|
* edit or deletion must not rewrite an in-flight meeting after restart. */
|
|
@@ -7597,6 +7798,9 @@ function vcMeetingProfileFromRuntimeAgent(selected) {
|
|
|
7597
7798
|
...(selected.instructions ? { instructions: selected.instructions } : {}),
|
|
7598
7799
|
...(selected.filter ? { filter: selected.filter } : {}),
|
|
7599
7800
|
responseMode: selected.responseMode,
|
|
7801
|
+
...(selected.listenerPlacement !== 'auto'
|
|
7802
|
+
? { listenerDelivery: { placement: selected.listenerPlacement } }
|
|
7803
|
+
: {}),
|
|
7600
7804
|
capabilities: [...selected.capabilities],
|
|
7601
7805
|
...(selected.ownedSinks.length > 0 ? { ownedSinks: [...selected.ownedSinks] } : {}),
|
|
7602
7806
|
};
|
|
@@ -7664,6 +7868,7 @@ async function ensureVcMeetingProfileMember(session, profile, listenerChatId, op
|
|
|
7664
7868
|
&& JSON.stringify(prior.ownedSinks) === JSON.stringify(canonicalOwnedSinks);
|
|
7665
7869
|
const samePolicy = sameStreamSemantics
|
|
7666
7870
|
&& prior.responseMode === profile.responseMode
|
|
7871
|
+
&& prior.outputPlacement === (profile.listenerDelivery?.placement ?? 'auto')
|
|
7667
7872
|
&& JSON.stringify(prior.capabilities) === JSON.stringify(canonicalCapabilities)
|
|
7668
7873
|
&& sameOwnedSinks;
|
|
7669
7874
|
// Receiver registration commits before the hub projection. A crash in that
|
|
@@ -7680,6 +7885,7 @@ async function ensureVcMeetingProfileMember(session, profile, listenerChatId, op
|
|
|
7680
7885
|
&& receiverPrior.instructions === instructions
|
|
7681
7886
|
&& receiverPrior.outputChatId === listenerChatId
|
|
7682
7887
|
&& receiverPrior.responseMode === profile.responseMode
|
|
7888
|
+
&& receiverPrior.outputPlacement === (profile.listenerDelivery?.placement ?? 'auto')
|
|
7683
7889
|
&& JSON.stringify(receiverPrior.filter ?? {}) === JSON.stringify(canonicalFilter ?? {})
|
|
7684
7890
|
&& JSON.stringify(receiverPrior.capabilities) === JSON.stringify(canonicalCapabilities)
|
|
7685
7891
|
&& JSON.stringify(receiverPrior.ownedSinks) === JSON.stringify(canonicalOwnedSinks)
|
|
@@ -7744,7 +7950,10 @@ async function ensureVcMeetingProfileMember(session, profile, listenerChatId, op
|
|
|
7744
7950
|
responseMode: orphanedReceiver?.responseMode ?? profile.responseMode,
|
|
7745
7951
|
...policy,
|
|
7746
7952
|
},
|
|
7747
|
-
outputRoute: {
|
|
7953
|
+
outputRoute: {
|
|
7954
|
+
chatId: orphanedReceiver?.outputChatId ?? listenerChatId,
|
|
7955
|
+
placement: orphanedReceiver?.outputPlacement ?? profile.listenerDelivery?.placement ?? 'auto',
|
|
7956
|
+
},
|
|
7748
7957
|
};
|
|
7749
7958
|
const receiver = await postVcMeetingMemberProjection(profile.agentAppId, projection);
|
|
7750
7959
|
const body = vcMeetingResponseRecord(receiver.body);
|
|
@@ -7777,6 +7986,7 @@ async function ensureVcMeetingProfileMember(session, profile, listenerChatId, op
|
|
|
7777
7986
|
membershipGeneration,
|
|
7778
7987
|
status: 'active',
|
|
7779
7988
|
responseMode: orphanedReceiver?.responseMode ?? profile.responseMode,
|
|
7989
|
+
outputPlacement: orphanedReceiver?.outputPlacement ?? profile.listenerDelivery?.placement ?? 'auto',
|
|
7780
7990
|
...policy,
|
|
7781
7991
|
joinedAtIngestSeq,
|
|
7782
7992
|
receiverSessionId,
|
|
@@ -7889,6 +8099,7 @@ async function pauseVcMeetingSingleConsumerMembership(session) {
|
|
|
7889
8099
|
membershipGeneration,
|
|
7890
8100
|
status: 'paused',
|
|
7891
8101
|
responseMode: prior.responseMode,
|
|
8102
|
+
outputPlacement: prior.outputPlacement ?? 'auto',
|
|
7892
8103
|
joinedAtIngestSeq: prior.joinedAtIngestSeq,
|
|
7893
8104
|
receiverSessionId: prior.receiverSessionId,
|
|
7894
8105
|
outputChatId: prior.outputChatId,
|
|
@@ -7914,7 +8125,7 @@ async function pauseVcMeetingSingleConsumerMembership(session) {
|
|
|
7914
8125
|
joinedAtIngestSeq: prior.joinedAtIngestSeq,
|
|
7915
8126
|
responseMode: prior.responseMode,
|
|
7916
8127
|
},
|
|
7917
|
-
outputRoute: { chatId: prior.outputChatId },
|
|
8128
|
+
outputRoute: { chatId: prior.outputChatId, placement: prior.outputPlacement ?? 'auto' },
|
|
7918
8129
|
});
|
|
7919
8130
|
const body = vcMeetingResponseRecord(receiver.body);
|
|
7920
8131
|
if (receiver.status < 200 || receiver.status >= 300 || body?.ok !== true) {
|
|
@@ -7960,6 +8171,7 @@ async function pauseVcMeetingProfileMembership(session, selected) {
|
|
|
7960
8171
|
membershipGeneration,
|
|
7961
8172
|
status: 'paused',
|
|
7962
8173
|
responseMode: prior.responseMode,
|
|
8174
|
+
outputPlacement: prior.outputPlacement ?? 'auto',
|
|
7963
8175
|
...policy,
|
|
7964
8176
|
joinedAtIngestSeq: prior.joinedAtIngestSeq,
|
|
7965
8177
|
receiverSessionId: prior.receiverSessionId,
|
|
@@ -7987,7 +8199,7 @@ async function pauseVcMeetingProfileMembership(session, selected) {
|
|
|
7987
8199
|
responseMode: prior.responseMode,
|
|
7988
8200
|
...policy,
|
|
7989
8201
|
},
|
|
7990
|
-
outputRoute: { chatId: prior.outputChatId },
|
|
8202
|
+
outputRoute: { chatId: prior.outputChatId, placement: prior.outputPlacement ?? 'auto' },
|
|
7991
8203
|
});
|
|
7992
8204
|
const body = vcMeetingResponseRecord(receiver.body);
|
|
7993
8205
|
if (receiver.status < 200 || receiver.status >= 300 || body?.ok !== true) {
|
|
@@ -8018,6 +8230,7 @@ function retireVcMeetingSingleConsumerForRecovery(session, reason) {
|
|
|
8018
8230
|
membershipGeneration,
|
|
8019
8231
|
status: 'removed',
|
|
8020
8232
|
responseMode: member.responseMode,
|
|
8233
|
+
outputPlacement: member.outputPlacement ?? 'auto',
|
|
8021
8234
|
joinedAtIngestSeq: member.joinedAtIngestSeq,
|
|
8022
8235
|
receiverSessionId: member.receiverSessionId,
|
|
8023
8236
|
outputChatId: member.outputChatId,
|
|
@@ -8037,6 +8250,7 @@ function retireVcMeetingSingleConsumerForRecovery(session, reason) {
|
|
|
8037
8250
|
membershipGeneration,
|
|
8038
8251
|
status: 'removed',
|
|
8039
8252
|
responseMode: member.responseMode,
|
|
8253
|
+
outputPlacement: member.outputPlacement ?? 'auto',
|
|
8040
8254
|
joinedAtIngestSeq: member.joinedAtIngestSeq,
|
|
8041
8255
|
receiverSessionId: member.receiverSessionId,
|
|
8042
8256
|
outputChatId: member.outputChatId,
|
|
@@ -8078,6 +8292,7 @@ function retireVcMeetingProfileMemberForRecovery(member, reason) {
|
|
|
8078
8292
|
membershipGeneration,
|
|
8079
8293
|
status: 'removed',
|
|
8080
8294
|
responseMode: member.responseMode,
|
|
8295
|
+
outputPlacement: member.outputPlacement ?? 'auto',
|
|
8081
8296
|
...policy,
|
|
8082
8297
|
joinedAtIngestSeq: member.joinedAtIngestSeq,
|
|
8083
8298
|
receiverSessionId: member.receiverSessionId,
|
|
@@ -8099,6 +8314,7 @@ function retireVcMeetingProfileMemberForRecovery(member, reason) {
|
|
|
8099
8314
|
membershipGeneration,
|
|
8100
8315
|
status: 'removed',
|
|
8101
8316
|
responseMode: member.responseMode,
|
|
8317
|
+
outputPlacement: member.outputPlacement ?? 'auto',
|
|
8102
8318
|
...policy,
|
|
8103
8319
|
joinedAtIngestSeq: member.joinedAtIngestSeq,
|
|
8104
8320
|
receiverSessionId: member.receiverSessionId,
|
|
@@ -8269,6 +8485,7 @@ function vcMeetingProfileMemberSemanticsMatchRuntime(member, selected, listenerC
|
|
|
8269
8485
|
&& member.outputChatId === listenerChatId
|
|
8270
8486
|
&& member.deliveryProfileHash === selected.deliveryProfileHash
|
|
8271
8487
|
&& member.responseMode === selected.responseMode
|
|
8488
|
+
&& (member.outputPlacement ?? 'auto') === selected.listenerPlacement
|
|
8272
8489
|
&& JSON.stringify(member.filter ?? {}) === JSON.stringify(selected.filter ?? {})
|
|
8273
8490
|
&& JSON.stringify(member.capabilities) === JSON.stringify(selected.capabilities)
|
|
8274
8491
|
&& JSON.stringify(member.ownedSinks) === JSON.stringify(selected.ownedSinks);
|
|
@@ -8310,6 +8527,7 @@ function refreshVcMeetingProfileOwnerBoot(session) {
|
|
|
8310
8527
|
joinedAtIngestSeq: member.joinedAtIngestSeq,
|
|
8311
8528
|
receiverSessionId: member.receiverSessionId,
|
|
8312
8529
|
outputChatId: member.outputChatId,
|
|
8530
|
+
outputPlacement: member.outputPlacement ?? 'auto',
|
|
8313
8531
|
});
|
|
8314
8532
|
if (!applied.ok)
|
|
8315
8533
|
throw new Error(`receiver ${member.memberId}: ${applied.reason}`);
|
|
@@ -8335,6 +8553,7 @@ function refreshVcMeetingProfileOwnerBoot(session) {
|
|
|
8335
8553
|
membershipGeneration: member.membershipGeneration,
|
|
8336
8554
|
status: member.status,
|
|
8337
8555
|
responseMode: member.responseMode,
|
|
8556
|
+
outputPlacement: member.outputPlacement ?? 'auto',
|
|
8338
8557
|
...policy,
|
|
8339
8558
|
joinedAtIngestSeq: member.joinedAtIngestSeq,
|
|
8340
8559
|
receiverSessionId: member.receiverSessionId,
|
|
@@ -9515,7 +9734,7 @@ async function removeVcMeetingProfileMember(session, selected) {
|
|
|
9515
9734
|
ownedSinks: prior.ownedSinks,
|
|
9516
9735
|
sinkOwnerGeneration: prior.sinkOwnerGeneration,
|
|
9517
9736
|
},
|
|
9518
|
-
outputRoute: { chatId: prior.outputChatId },
|
|
9737
|
+
outputRoute: { chatId: prior.outputChatId, placement: prior.outputPlacement ?? 'auto' },
|
|
9519
9738
|
};
|
|
9520
9739
|
const receiver = await postVcMeetingMemberProjection(prior.agentAppId, projection);
|
|
9521
9740
|
const body = vcMeetingResponseRecord(receiver.body);
|
|
@@ -9536,6 +9755,7 @@ async function removeVcMeetingProfileMember(session, selected) {
|
|
|
9536
9755
|
membershipGeneration,
|
|
9537
9756
|
status: 'removed',
|
|
9538
9757
|
responseMode: prior.responseMode,
|
|
9758
|
+
outputPlacement: prior.outputPlacement ?? 'auto',
|
|
9539
9759
|
...(prior.filter ? { filter: prior.filter } : {}),
|
|
9540
9760
|
capabilities: prior.capabilities,
|
|
9541
9761
|
ownedSinks: prior.ownedSinks,
|
|
@@ -9742,7 +9962,9 @@ async function applyVcMeetingConsumerProfileSelection(key, session, cfg, selecte
|
|
|
9742
9962
|
const existingByProfileId = new Map(session.selectedAgents.map(selected => [selected.profileId, selected]));
|
|
9743
9963
|
const requestedProfiles = resolution.selectedProfiles.map((profile) => {
|
|
9744
9964
|
const existing = existingByProfileId.get(profile.id);
|
|
9745
|
-
return existing
|
|
9965
|
+
return existing
|
|
9966
|
+
? vcMeetingProfileFromRuntimeAgent(existing)
|
|
9967
|
+
: vcMeetingProfileWithActivationContext(profile, opts.activationContext);
|
|
9746
9968
|
});
|
|
9747
9969
|
const retainedAfterRemovalFailure = [];
|
|
9748
9970
|
const removalErrors = [];
|
|
@@ -9835,6 +10057,7 @@ async function applyVcMeetingConsumerProfileSelection(key, session, cfg, selecte
|
|
|
9835
10057
|
session.consumerSelectionApplying = false;
|
|
9836
10058
|
session.consumerPendingProfileIds = undefined;
|
|
9837
10059
|
session.consumerPendingIntervalMs = undefined;
|
|
10060
|
+
session.consumerPendingActivationContext = undefined;
|
|
9838
10061
|
}
|
|
9839
10062
|
}
|
|
9840
10063
|
async function activateVcMeetingPreparationBinding(session, cfg, preparation) {
|
|
@@ -9982,9 +10205,10 @@ async function applyVcMeetingConsumerProfileStagedState(key, session, cfg, fallb
|
|
|
9982
10205
|
const selectedProfileIds = session.consumerPendingProfileIds
|
|
9983
10206
|
?? (fallbackProfileIds ? [...fallbackProfileIds] : undefined);
|
|
9984
10207
|
const stagedIntervalMs = session.consumerPendingIntervalMs;
|
|
10208
|
+
const activationContext = session.consumerPendingActivationContext;
|
|
9985
10209
|
if (stagedIntervalMs)
|
|
9986
10210
|
session.syncIntervalMs = stagedIntervalMs;
|
|
9987
|
-
let result = await applyVcMeetingConsumerProfileSelection(key, session, cfg, selectedProfileIds, opts);
|
|
10211
|
+
let result = await applyVcMeetingConsumerProfileSelection(key, session, cfg, selectedProfileIds, { ...opts, ...(activationContext ? { activationContext } : {}) });
|
|
9988
10212
|
if (!result.ok && selectedProfileIds === undefined && session.consumerMode === 'pending') {
|
|
9989
10213
|
// A conflicting/invalid hand-edited default must never leave a pending
|
|
9990
10214
|
// timer retrying or partially activate a subset. Fail closed to
|
|
@@ -11565,6 +11789,11 @@ async function handleVcMeetingCardAction(data, larkAppId) {
|
|
|
11565
11789
|
session.consumerSelectionApplying = false;
|
|
11566
11790
|
}
|
|
11567
11791
|
}
|
|
11792
|
+
const activationContext = vcMeetingActivationContextFromCard(data);
|
|
11793
|
+
if (!activationContext.ok) {
|
|
11794
|
+
return { toast: { type: 'error', content: activationContext.error } };
|
|
11795
|
+
}
|
|
11796
|
+
session.consumerPendingActivationContext = activationContext.context;
|
|
11568
11797
|
return applyVcMeetingConsumerSelectionInBackground(key, session, cfg, () => applyVcMeetingConsumerProfileStagedState(key, session, cfg, undefined, { claimed: true }), { cardMessageId: data.context?.open_message_id ?? data.open_message_id });
|
|
11569
11798
|
}
|
|
11570
11799
|
const selection = session.consumerPendingChoice ?? { mode: 'default' };
|
|
@@ -12193,7 +12422,7 @@ async function startInitialPassthroughSession(args) {
|
|
|
12193
12422
|
const botCfg = getBot(larkAppId).config;
|
|
12194
12423
|
refreshCliVersion(botCfg.cliId, botCfg.cliPathOverride);
|
|
12195
12424
|
const directChatSender = chatType === 'p2p'
|
|
12196
|
-
? await resolveSender(larkAppId, senderOpenId, parsed.senderType)
|
|
12425
|
+
? await resolveSender(larkAppId, senderOpenId, parsed.senderType, { messageId })
|
|
12197
12426
|
: undefined;
|
|
12198
12427
|
// Group cold-start passthroughs only need a stable caller identity while the
|
|
12199
12428
|
// repo picker is pending. Avoid adding a contact lookup to every /goal start;
|
|
@@ -12512,7 +12741,9 @@ async function handleNewTopic(data, ctx) {
|
|
|
12512
12741
|
// chat-granted users (who only pass canTalk) management commands like
|
|
12513
12742
|
// /cd /restart /oncall bind. Previously this gate only fired in oncall chats,
|
|
12514
12743
|
// which left a hole once per-chat grants flow through canTalk.
|
|
12515
|
-
|
|
12744
|
+
// canRunDaemonCommand = canOperate ∪(cmd ∈ canTalkDaemonCommands && canTalk):
|
|
12745
|
+
// bot 可通过名单把选定命令(如 /status)降到 canTalk;未配置时与 canOperate 全等。
|
|
12746
|
+
if (!canRunDaemonCommand(larkAppId, chatId, senderOpenId, teamTrustUnionId, cmd, senderUnionId, chatType)) {
|
|
12516
12747
|
await sessionReply(anchor, tr('daemon.cmd_allowed_users_only', { cmd }, localeForBot(larkAppId)), 'text', larkAppId);
|
|
12517
12748
|
return;
|
|
12518
12749
|
}
|
|
@@ -12546,7 +12777,7 @@ async function handleNewTopic(data, ctx) {
|
|
|
12546
12777
|
const session = sessionStore.createSession(chatId, cmdRootIdForStore, cmdContent.substring(0, 50), chatType);
|
|
12547
12778
|
const now = Date.now();
|
|
12548
12779
|
if (chatType === 'p2p') {
|
|
12549
|
-
setDirectChatDisplayNameFromSender(session, chatType, await resolveSender(larkAppId, senderOpenId, parsed.senderType));
|
|
12780
|
+
setDirectChatDisplayNameFromSender(session, chatType, await resolveSender(larkAppId, senderOpenId, parsed.senderType, { messageId }));
|
|
12550
12781
|
}
|
|
12551
12782
|
session.larkAppId = larkAppId;
|
|
12552
12783
|
session.ownerOpenId = senderOpenId;
|
|
@@ -12621,7 +12852,7 @@ async function handleNewTopic(data, ctx) {
|
|
|
12621
12852
|
// resolveSender for an unseen open_id may await contact.v3.user.get with a
|
|
12622
12853
|
// short budget; subsequent calls hit the cache and are sync-fast.
|
|
12623
12854
|
const groupChatNamePromise = resolveGroupChatNameForNativeTitle(larkAppId, chatId, chatType, botCfg.cliId, parsed.content, parsed.mentions);
|
|
12624
|
-
const newTopicSender = await resolveSender(larkAppId, senderOpenId, parsed.senderType);
|
|
12855
|
+
const newTopicSender = await resolveSender(larkAppId, senderOpenId, parsed.senderType, { messageId });
|
|
12625
12856
|
refreshCliVersion(botCfg.cliId, botCfg.cliPathOverride);
|
|
12626
12857
|
// Pin the working dir via the layered oncall / inherit / default lookup
|
|
12627
12858
|
// (auto-binds a defaultOncall chat as a side effect). Shared with the
|
|
@@ -13083,7 +13314,9 @@ async function handleThreadReply(data, ctx) {
|
|
|
13083
13314
|
if (threadSenderResolved)
|
|
13084
13315
|
return threadSenderCached;
|
|
13085
13316
|
threadSenderResolved = true;
|
|
13086
|
-
threadSenderCached = await resolveSender(larkAppId, senderOpenIdForPrefix, parsed.senderType, isForeignBot
|
|
13317
|
+
threadSenderCached = await resolveSender(larkAppId, senderOpenIdForPrefix, parsed.senderType, isForeignBot
|
|
13318
|
+
? { type: 'bot', name: foreignBotName !== 'Bot' ? foreignBotName : undefined, messageId: parsed.messageId }
|
|
13319
|
+
: { messageId: parsed.messageId });
|
|
13087
13320
|
return threadSenderCached;
|
|
13088
13321
|
};
|
|
13089
13322
|
const content = parsed.content.trim();
|
|
@@ -13285,7 +13518,9 @@ async function handleThreadReply(data, ctx) {
|
|
|
13285
13518
|
}
|
|
13286
13519
|
// canOperate gate for thread-reply daemon commands — required in every chat
|
|
13287
13520
|
// (see spawn-path gate above). Denies chat-granted users management commands.
|
|
13288
|
-
|
|
13521
|
+
// canRunDaemonCommand:canTalkDaemonCommands 名单内的命令降到 canTalk,
|
|
13522
|
+
// 与 new-topic 路径的统一闸同款(未配置时与 canOperate 全等)。
|
|
13523
|
+
if (!canRunDaemonCommand(larkAppId, effectiveThreadChatId, threadSenderOpenId, threadTeamTrustUnionId, cmd, threadSenderUnionId, ctxChatType)) {
|
|
13289
13524
|
sessionReply(anchor, tr('daemon.cmd_allowed_users_only', { cmd }, localeForBot(larkAppId)), 'text', larkAppId);
|
|
13290
13525
|
return;
|
|
13291
13526
|
}
|
|
@@ -14198,6 +14433,111 @@ function resolvePrimaryOwnerOpenId(larkAppId) {
|
|
|
14198
14433
|
return undefined;
|
|
14199
14434
|
}
|
|
14200
14435
|
}
|
|
14436
|
+
/** Parse a positive float env override, falling back to `dflt` when unset/invalid. */
|
|
14437
|
+
function envFloat(name, dflt) {
|
|
14438
|
+
const raw = process.env[name];
|
|
14439
|
+
if (raw === undefined || raw.trim() === '')
|
|
14440
|
+
return dflt;
|
|
14441
|
+
const n = Number(raw);
|
|
14442
|
+
return Number.isFinite(n) && n >= 0 ? n : dflt;
|
|
14443
|
+
}
|
|
14444
|
+
/**
|
|
14445
|
+
* Host-overload thresholds, seeded from module defaults and overridable via env
|
|
14446
|
+
* so an operator can tune sensitivity without a code change:
|
|
14447
|
+
* BOTMUX_OVERLOAD_ENTER_LOAD_RATIO / _EXIT_LOAD_RATIO (× logical CPU count)
|
|
14448
|
+
* BOTMUX_OVERLOAD_ENTER_MEM_FRAC / _EXIT_MEM_FRAC (0..1)
|
|
14449
|
+
* BOTMUX_OVERLOAD_MIN_REALERT_MS
|
|
14450
|
+
* Setting BOTMUX_OVERLOAD_ALERT=0 disables the watcher entirely (checked at the
|
|
14451
|
+
* call site). Swap is not read on this platform, so its thresholds stay default
|
|
14452
|
+
* but are inert (reading passes swap=undefined).
|
|
14453
|
+
*/
|
|
14454
|
+
function resolveOverloadThresholds() {
|
|
14455
|
+
const cpuCount = Math.max(1, cpus().length || 1);
|
|
14456
|
+
const enterLoadRatio = envFloat('BOTMUX_OVERLOAD_ENTER_LOAD_RATIO', DEFAULT_OVERLOAD_THRESHOLDS.enterLoadRatio);
|
|
14457
|
+
let exitLoadRatio = envFloat('BOTMUX_OVERLOAD_EXIT_LOAD_RATIO', DEFAULT_OVERLOAD_THRESHOLDS.exitLoadRatio);
|
|
14458
|
+
const enterMemUsedFrac = envFloat('BOTMUX_OVERLOAD_ENTER_MEM_FRAC', DEFAULT_OVERLOAD_THRESHOLDS.enterMemUsedFrac);
|
|
14459
|
+
let exitMemUsedFrac = envFloat('BOTMUX_OVERLOAD_EXIT_MEM_FRAC', DEFAULT_OVERLOAD_THRESHOLDS.exitMemUsedFrac);
|
|
14460
|
+
// Hysteresis only works when the recover line sits STRICTLY below the enter
|
|
14461
|
+
// line. Clamping a misconfigured exit down to *equal* enter is not enough: at
|
|
14462
|
+
// enter == exit the enter test (mem `>=`) and the recover test (mem `<=`) both
|
|
14463
|
+
// fire at the exact threshold, so a reading pinned there flaps entered/
|
|
14464
|
+
// recovered every tick. Force a small gap below enter (5%) and warn.
|
|
14465
|
+
const HYSTERESIS_MARGIN = 0.95; // exit = 95% of enter when misconfigured
|
|
14466
|
+
if (exitLoadRatio >= enterLoadRatio) {
|
|
14467
|
+
const clamped = enterLoadRatio * HYSTERESIS_MARGIN;
|
|
14468
|
+
logger.warn(`[overload] BOTMUX_OVERLOAD_EXIT_LOAD_RATIO (${exitLoadRatio}) >= ENTER (${enterLoadRatio}); clamping exit to ${clamped}`);
|
|
14469
|
+
exitLoadRatio = clamped;
|
|
14470
|
+
}
|
|
14471
|
+
if (exitMemUsedFrac >= enterMemUsedFrac) {
|
|
14472
|
+
const clamped = enterMemUsedFrac * HYSTERESIS_MARGIN;
|
|
14473
|
+
logger.warn(`[overload] BOTMUX_OVERLOAD_EXIT_MEM_FRAC (${exitMemUsedFrac}) >= ENTER (${enterMemUsedFrac}); clamping exit to ${clamped}`);
|
|
14474
|
+
exitMemUsedFrac = clamped;
|
|
14475
|
+
}
|
|
14476
|
+
return {
|
|
14477
|
+
cpuCount,
|
|
14478
|
+
enterLoadRatio,
|
|
14479
|
+
exitLoadRatio,
|
|
14480
|
+
enterMemUsedFrac,
|
|
14481
|
+
exitMemUsedFrac,
|
|
14482
|
+
enterSwapUsedFrac: DEFAULT_OVERLOAD_THRESHOLDS.enterSwapUsedFrac,
|
|
14483
|
+
exitSwapUsedFrac: DEFAULT_OVERLOAD_THRESHOLDS.exitSwapUsedFrac,
|
|
14484
|
+
minReAlertMs: envFloat('BOTMUX_OVERLOAD_MIN_REALERT_MS', DEFAULT_OVERLOAD_THRESHOLDS.minReAlertMs),
|
|
14485
|
+
};
|
|
14486
|
+
}
|
|
14487
|
+
/**
|
|
14488
|
+
* Machine-wide de-dup for the host-overload alert. load/mem are host-wide, so if
|
|
14489
|
+
* more than one bot has the `overloadAlert` toggle on, every such daemon would
|
|
14490
|
+
* independently detect the same edge and DM its owner. This claims a short-lived
|
|
14491
|
+
* episode marker in the shared botmux data dir so only the first daemon to see a
|
|
14492
|
+
* given edge actually sends; siblings within the dedup window back off.
|
|
14493
|
+
*
|
|
14494
|
+
* The key is just the edge kind (`entered` / `recovered`): within DEDUP_WINDOW_MS
|
|
14495
|
+
* the first daemon to see an edge claims it and siblings back off. (An earlier
|
|
14496
|
+
* version bucketed `entered` by `Math.round(load15)`, but sibling daemons
|
|
14497
|
+
* sampling load15 either side of an X.5 boundary rounded to different bands,
|
|
14498
|
+
* got different keys, and each DMed — so the band is dropped.)
|
|
14499
|
+
*
|
|
14500
|
+
* The claim is a real mutual exclusion: the ENTIRE read-check-write runs inside
|
|
14501
|
+
* one `withFileLockSync` critical section on the marker. An earlier version used
|
|
14502
|
+
* `openSync(marker, 'wx')`, but that only serializes the FIRST claim — once the
|
|
14503
|
+
* marker exists (it persists after the first alert), every later edge
|
|
14504
|
+
* (recovered / expired) fell back to an unlocked read→replace, so two daemons
|
|
14505
|
+
* both detecting the same `recovered` edge could both read the stale key, both
|
|
14506
|
+
* replace it, and each DM. Serializing the whole section closes that hole. Best-
|
|
14507
|
+
* effort: if the lock itself can't be taken (FS error), allow the DM (better a
|
|
14508
|
+
* rare duplicate than a silent miss).
|
|
14509
|
+
*/
|
|
14510
|
+
function claimOverloadEpisode(kind) {
|
|
14511
|
+
const DEDUP_WINDOW_MS = 60_000; // ≥ two 30s ticks; covers sibling daemons racing the same edge.
|
|
14512
|
+
const marker = join(config.session.dataDir, '.overload-episode.json');
|
|
14513
|
+
const key = kind;
|
|
14514
|
+
try {
|
|
14515
|
+
return withFileLockSync(marker, () => {
|
|
14516
|
+
const now = Date.now();
|
|
14517
|
+
if (existsSync(marker)) {
|
|
14518
|
+
try {
|
|
14519
|
+
const prev = JSON.parse(readFileSync(marker, 'utf8'));
|
|
14520
|
+
if (prev.key === key && typeof prev.at === 'number' && now - prev.at < DEDUP_WINDOW_MS) {
|
|
14521
|
+
return false; // Same edge already claimed within the window.
|
|
14522
|
+
}
|
|
14523
|
+
}
|
|
14524
|
+
catch {
|
|
14525
|
+
// Corrupt marker → fall through and take it over.
|
|
14526
|
+
}
|
|
14527
|
+
}
|
|
14528
|
+
// Winner: claim the episode. atomicWriteFileSync is fine here — the lock,
|
|
14529
|
+
// not the write, provides mutual exclusion.
|
|
14530
|
+
atomicWriteFileSync(marker, JSON.stringify({ key, at: now }));
|
|
14531
|
+
return true;
|
|
14532
|
+
}, { maxWaitMs: 2_000 });
|
|
14533
|
+
}
|
|
14534
|
+
catch (err) {
|
|
14535
|
+
// Couldn't acquire the lock (timeout / FS error) → don't silently drop the
|
|
14536
|
+
// edge; allow this DM (a rare duplicate is better than a missed alert).
|
|
14537
|
+
logger.warn(`[overload] episode claim lock failed (${kind}): ${err instanceof Error ? err.message : String(err)}; allowing DM`);
|
|
14538
|
+
return true;
|
|
14539
|
+
}
|
|
14540
|
+
}
|
|
14201
14541
|
/** Build the current dashboard URL (active token, not a rotation) from the
|
|
14202
14542
|
* dashboard process's persisted `.dashboard-port` / `.dashboard-token`. Falls
|
|
14203
14543
|
* back to a token-less base URL if the dashboard hasn't published a token yet. */
|
|
@@ -14253,6 +14593,11 @@ export async function startDaemon(botIndex) {
|
|
|
14253
14593
|
// 首次启动时后台尝试安装 CJK 字体(Debian/Ubuntu),避免截图中文显示豆腐块。
|
|
14254
14594
|
// 不阻塞:首张截图可能仍是豆腐块,装完重启 daemon 即可正常。
|
|
14255
14595
|
ensureCjkFontsInstalled();
|
|
14596
|
+
// Auto-migrate legacy sandbox fields (readIsolation / sandboxHidePaths /
|
|
14597
|
+
// sandboxReadonlyPaths / readDenyExtraPaths → sandbox + sandboxPaths) BEFORE
|
|
14598
|
+
// loading, so the parsed configs below already carry the new model. Writes
|
|
14599
|
+
// new fields, keeps old ones (downgrade = zero-op), backs up once. Idempotent.
|
|
14600
|
+
await migrateSandboxConfigAtStartup();
|
|
14256
14601
|
// Load the assigned bot (one daemon per bot)
|
|
14257
14602
|
let botConfigs = loadBotConfigs();
|
|
14258
14603
|
const idx = botIndex ?? 0;
|
|
@@ -14393,8 +14738,12 @@ export async function startDaemon(botIndex) {
|
|
|
14393
14738
|
}
|
|
14394
14739
|
}, VC_MEETING_DELIVERY_LEASE_SCAN_MS);
|
|
14395
14740
|
vcMeetingDeliveryLeaseTimer.unref?.();
|
|
14396
|
-
//
|
|
14741
|
+
// Bind the schedule store to this daemon's bot (per-bot stores live in each
|
|
14742
|
+
// BOT_HOME), split a legacy shared data/schedules.json if one still exists,
|
|
14743
|
+
// then watch our own store for external writes (e.g. `botmux schedule add`
|
|
14397
14744
|
// running in a separate node process) so dashboard event bus stays in sync.
|
|
14745
|
+
scheduleStore.setScheduleScope(cfg.larkAppId);
|
|
14746
|
+
migrateSharedSchedulesAtStartup(botConfigs.map(b => b.larkAppId), botConfigs[0]?.larkAppId ?? cfg.larkAppId);
|
|
14398
14747
|
scheduleStore.startExternalWriteWatcher();
|
|
14399
14748
|
logger.info(`Bot ${idx}/${botConfigs.length}: ${cfg.larkAppId} (cli: ${cfg.cliId})`);
|
|
14400
14749
|
setAskCardDispatcher(createLarkAskCardDispatcher());
|
|
@@ -14428,6 +14777,16 @@ export async function startDaemon(botIndex) {
|
|
|
14428
14777
|
// briefly sees an unusable on_/email (the resolution below rewrites this field).
|
|
14429
14778
|
resolvedAllowedUsers: getBot(cfg.larkAppId).resolvedAllowedUsers.filter(u => u.startsWith('ou_')),
|
|
14430
14779
|
};
|
|
14780
|
+
// Expose the live descriptor module-level so the deferred allowedUsers resolve
|
|
14781
|
+
// retry can republish healed open_ids coherently (see republishResolvedAllowedUsers).
|
|
14782
|
+
selfDaemonDescriptor = desc;
|
|
14783
|
+
// Let runtime allowedUsers mutations (set / revoke, in the services layer)
|
|
14784
|
+
// republish the descriptor through the same path without importing daemon
|
|
14785
|
+
// internals. Registered once per daemon; one daemon per bot.
|
|
14786
|
+
setResolvedAllowedUsersRepublishHook(republishResolvedAllowedUsers);
|
|
14787
|
+
// And let a mutation that hit a transient contact failure schedule the same
|
|
14788
|
+
// background resolve-retry the startup path uses (heal-when-API-recovers).
|
|
14789
|
+
setAllowedUsersResolveRetryHook((appId) => scheduleAllowedUsersResolveRetry(appId));
|
|
14431
14790
|
// 名称状态刷新:displayName 或飞书真名变化后,用有效展示名刷新 descriptor +
|
|
14432
14791
|
// SessionRow.botName,无需重启 daemon。displayName 路径经 bot-config-store 的
|
|
14433
14792
|
// 钩子触发;真·改名路径在下面的 renamer 里直接调用。
|
|
@@ -14677,22 +15036,68 @@ export async function startDaemon(botIndex) {
|
|
|
14677
15036
|
// Refresh CLI version per bot's cliId
|
|
14678
15037
|
refreshCliVersion(cfg.cliId, cfg.cliPathOverride);
|
|
14679
15038
|
// Resolve allowed users per bot
|
|
14680
|
-
if (bot.resolvedAllowedUsers.length > 0) {
|
|
15039
|
+
if ((bot.config.allowedUsers?.length ?? 0) > 0 || bot.resolvedAllowedUsers.length > 0) {
|
|
14681
15040
|
// 含邮箱或 union_id(on_) 都要重解析成本 app 的 open_id —— 否则 canTalk/canOperate
|
|
14682
15041
|
// 拿 sender 的 ou_ 对不上 on_,owner 会被自己的 bot 锁死(PR#72)。
|
|
14683
15042
|
// literal ou_ 也走 best-effort 校验,用于诊断把其他 app 视角 open_id
|
|
14684
15043
|
// 误填到本 bot 的配置。
|
|
14685
|
-
|
|
15044
|
+
//
|
|
15045
|
+
// Transient contact failures must NOT blank the runtime list: an empty
|
|
15046
|
+
// resolved allowlist under a configured allowedUsers is fail-closed and
|
|
15047
|
+
// silently drops even the real owner (@ with no group reply). Reuse the
|
|
15048
|
+
// last-known-good `raw → ou_` cache PER ENTRY (only for still-configured,
|
|
15049
|
+
// transient-failed entries), log loudly, DM owners (fire-and-forget), and
|
|
15050
|
+
// schedule retries. The cache is a persistent sidecar — NOT the dashboard
|
|
15051
|
+
// descriptor, which is overwritten early in boot and deleted on shutdown.
|
|
15052
|
+
const configured = bot.config.allowedUsers ?? bot.resolvedAllowedUsers;
|
|
15053
|
+
const needsResolve = configured.some(u => u.includes('@') || u.startsWith('on_') || u.startsWith('ou_'));
|
|
14686
15054
|
if (needsResolve) {
|
|
15055
|
+
const previousResolvedMap = readAllowedUsersCache(cfg.larkAppId);
|
|
14687
15056
|
try {
|
|
14688
|
-
// 同时拿到 raw→open_id
|
|
14689
|
-
const
|
|
14690
|
-
|
|
14691
|
-
|
|
14692
|
-
|
|
15057
|
+
// 同时拿到 raw→open_id 映射,供 /revoke 反查删除 email 形式的 raw 条目(R2#2)。
|
|
15058
|
+
const resolveResult = await resolveAllowedUsersWithMap(cfg.larkAppId, configured);
|
|
15059
|
+
const applied = applyAllowedUsersResolve({
|
|
15060
|
+
rawEntries: configured,
|
|
15061
|
+
previousResolvedMap,
|
|
15062
|
+
resolveResult,
|
|
15063
|
+
});
|
|
15064
|
+
bot.resolvedAllowedUsers = applied.resolved;
|
|
15065
|
+
bot.rawAllowedUserResolution = applied.map;
|
|
15066
|
+
// Persist the fresh/recovered map so the next boot (and a clean
|
|
15067
|
+
// restart, which deletes the descriptor) can fall back per-entry.
|
|
15068
|
+
// Prune definitively-gone entries + keys no longer configured.
|
|
15069
|
+
writeAllowedUsersCache(cfg.larkAppId, applied.map, {
|
|
15070
|
+
deleteEntries: definitiveEntriesOf(resolveResult.entryStatus),
|
|
15071
|
+
retainKeys: configured,
|
|
15072
|
+
});
|
|
15073
|
+
logger.info(`[${cfg.larkAppId}] Resolved allowedUsers: ${bot.resolvedAllowedUsers.join(', ') || '(empty)'}${applied.usedFallback ? ' [some from cache]' : ''}`);
|
|
15074
|
+
if (applied.failed && applied.notice) {
|
|
15075
|
+
notifyAllowedUsersResolveFailure(cfg.larkAppId, applied.notice, applied.resolved);
|
|
15076
|
+
scheduleAllowedUsersResolveRetry(cfg.larkAppId);
|
|
15077
|
+
}
|
|
14693
15078
|
}
|
|
14694
15079
|
catch (err) {
|
|
14695
|
-
|
|
15080
|
+
// A full throw is a transient outage: mark every contact-resolvable
|
|
15081
|
+
// entry transient so the pure fn can recover each from cache.
|
|
15082
|
+
const throwStatus = new Map();
|
|
15083
|
+
for (const e of configured) {
|
|
15084
|
+
if (e.includes('@') || e.startsWith('on_') || e.startsWith('ou_'))
|
|
15085
|
+
throwStatus.set(e, 'transient');
|
|
15086
|
+
}
|
|
15087
|
+
const applied = applyAllowedUsersResolve({
|
|
15088
|
+
rawEntries: configured,
|
|
15089
|
+
previousResolvedMap,
|
|
15090
|
+
resolveResult: { resolved: [], map: new Map(), errored: true, entryStatus: throwStatus },
|
|
15091
|
+
});
|
|
15092
|
+
bot.resolvedAllowedUsers = applied.resolved;
|
|
15093
|
+
bot.rawAllowedUserResolution = applied.map;
|
|
15094
|
+
if (applied.usedFallback) {
|
|
15095
|
+
writeAllowedUsersCache(cfg.larkAppId, applied.map, { retainKeys: configured });
|
|
15096
|
+
}
|
|
15097
|
+
const notice = applied.notice
|
|
15098
|
+
?? `Failed to resolve allowedUsers: ${err?.message ?? err}`;
|
|
15099
|
+
notifyAllowedUsersResolveFailure(cfg.larkAppId, `${notice} (throw: ${err?.message ?? err})`, applied.resolved);
|
|
15100
|
+
scheduleAllowedUsersResolveRetry(cfg.larkAppId);
|
|
14696
15101
|
}
|
|
14697
15102
|
}
|
|
14698
15103
|
// Republish the descriptor with the post-resolution open_ids so the
|
|
@@ -14892,10 +15297,10 @@ export async function startDaemon(botIndex) {
|
|
|
14892
15297
|
}, 5_000);
|
|
14893
15298
|
docCommentPollTimer.unref?.();
|
|
14894
15299
|
void pollWatchedDocComments(cfg.larkAppId);
|
|
14895
|
-
// Sweep orphan sandbox
|
|
15300
|
+
// Sweep orphan sandbox trees left by a previous run's crash/kill: any
|
|
14896
15301
|
// <dataDir>/sandboxes/<sid> whose session is no longer active gets its
|
|
14897
|
-
//
|
|
14898
|
-
// Active sessions keep theirs — a same-topic worker reuses the
|
|
15302
|
+
// deny-mask mountpoints reclaimed and its dirs removed.
|
|
15303
|
+
// Active sessions keep theirs — a same-topic worker reuses the tree.
|
|
14899
15304
|
try {
|
|
14900
15305
|
sweepOrphanSandboxes(config.session.dataDir, new Set([...activeSessions.values()].map(ds => ds.session.sessionId)));
|
|
14901
15306
|
}
|
|
@@ -14909,12 +15314,12 @@ export async function startDaemon(botIndex) {
|
|
|
14909
15314
|
}, 60_000);
|
|
14910
15315
|
idleWorkerSweepTimer.unref?.();
|
|
14911
15316
|
// Periodic sandbox reconciler: the daemon's SIGKILL straggler-reaper (and any
|
|
14912
|
-
// worker SIGKILL) bypasses worker-side killCli(), so the
|
|
14913
|
-
//
|
|
14914
|
-
// the rest of this daemon's lifetime (one daemon per bot can run
|
|
14915
|
-
// startup sweep alone can't catch a session that dies AFTER boot.
|
|
14916
|
-
// the sweep on a timer: it reclaims
|
|
14917
|
-
//
|
|
15317
|
+
// worker SIGKILL) bypasses worker-side killCli(), so the pre-created deny-mask
|
|
15318
|
+
// mountpoints + per-session tree of a killed-but-still-active sandboxed session
|
|
15319
|
+
// would leak for the rest of this daemon's lifetime (one daemon per bot can run
|
|
15320
|
+
// for days). The startup sweep alone can't catch a session that dies AFTER boot.
|
|
15321
|
+
// This re-runs the sweep on a timer: it reclaims sessions whose worker/CLI is
|
|
15322
|
+
// dead without ever tearing down a live one.
|
|
14918
15323
|
const sandboxReconcileTimer = setInterval(() => {
|
|
14919
15324
|
try {
|
|
14920
15325
|
sweepOrphanSandboxes(config.session.dataDir, new Set([...activeSessions.values()].map(ds => ds.session.sessionId)));
|
|
@@ -14984,6 +15389,92 @@ export async function startDaemon(botIndex) {
|
|
|
14984
15389
|
});
|
|
14985
15390
|
}, 5_000).unref?.();
|
|
14986
15391
|
}
|
|
15392
|
+
// Host-overload watcher. Enabled per-bot via the `overloadAlert` config toggle
|
|
15393
|
+
// (dashboard → Groups & Bots → bot card), NOT hardcoded to the primary daemon:
|
|
15394
|
+
// any one bot can be designated the machine's alerter. load/mem are host-wide,
|
|
15395
|
+
// so if more than one bot has it on, a cross-process file lock keyed on the
|
|
15396
|
+
// overload episode ensures the machine only DMs once per edge (whichever bot's
|
|
15397
|
+
// daemon wins the lock speaks). Reads os.loadavg()[2] + mem every 30s; on a
|
|
15398
|
+
// healthy→overloaded or overloaded→healthy edge it DMs that bot's owner.
|
|
15399
|
+
// Hysteresis + a min re-alert window (see host-overload-alert.ts) keep a load
|
|
15400
|
+
// hovering near the line from spamming. Set BOTMUX_OVERLOAD_ALERT=0 to force
|
|
15401
|
+
// the whole feature off regardless of per-bot config.
|
|
15402
|
+
if (process.env.BOTMUX_OVERLOAD_ALERT !== '0') {
|
|
15403
|
+
const overloadThresholds = resolveOverloadThresholds();
|
|
15404
|
+
let overloadState = INITIAL_OVERLOAD_STATE;
|
|
15405
|
+
const overloadTimer = setInterval(() => {
|
|
15406
|
+
void (async () => {
|
|
15407
|
+
try {
|
|
15408
|
+
// Per-bot热开关:关了就不采样,但 timer 常驻,用户在 web 打开后下个 tick 即生效。
|
|
15409
|
+
let enabled = false;
|
|
15410
|
+
try {
|
|
15411
|
+
enabled = getBot(cfg.larkAppId).config.overloadAlert === true;
|
|
15412
|
+
}
|
|
15413
|
+
catch {
|
|
15414
|
+
enabled = false;
|
|
15415
|
+
}
|
|
15416
|
+
if (!enabled) {
|
|
15417
|
+
overloadState = INITIAL_OVERLOAD_STATE;
|
|
15418
|
+
return;
|
|
15419
|
+
}
|
|
15420
|
+
const reading = {
|
|
15421
|
+
load15: loadavg()[2] ?? 0,
|
|
15422
|
+
memTotalBytes: totalmem(),
|
|
15423
|
+
memFreeBytes: freemem(),
|
|
15424
|
+
};
|
|
15425
|
+
const { nextState, action } = evaluateOverload(overloadState, reading, overloadThresholds, Date.now());
|
|
15426
|
+
overloadState = nextState;
|
|
15427
|
+
if (!action)
|
|
15428
|
+
return;
|
|
15429
|
+
// Machine-wide de-dup: multiple bots may have the toggle on, but the
|
|
15430
|
+
// host is one machine. Claim a short-lived episode lock so only one bot
|
|
15431
|
+
// DMs per edge. Losing the claim = another bot already alerted this edge.
|
|
15432
|
+
if (!claimOverloadEpisode(action.kind)) {
|
|
15433
|
+
logger.info(`[overload] ${action.kind} edge already claimed by a sibling bot; skipping DM (${cfg.larkAppId})`);
|
|
15434
|
+
return;
|
|
15435
|
+
}
|
|
15436
|
+
const ownerOpenId = resolvePrimaryOwnerOpenId(cfg.larkAppId);
|
|
15437
|
+
logger.info(`[overload] ${action.kind}: load15=${action.metrics.load15.toFixed(2)} `
|
|
15438
|
+
+ `perCpu=${action.metrics.loadPerCpu.toFixed(2)} mem=${(action.metrics.memUsedFrac * 100).toFixed(0)}% `
|
|
15439
|
+
+ `reasons=[${action.reasons.join(',')}] owner=${ownerOpenId ? 'yes' : 'none'} bot=${cfg.larkAppId}`);
|
|
15440
|
+
if (!ownerOpenId)
|
|
15441
|
+
return; // No resolvable owner to DM; the log above still records the edge.
|
|
15442
|
+
// Interactive card. `entered`: register a one-shot nonce, count the
|
|
15443
|
+
// machine-wide zombie/idle candidates so the buttons can show「(N)」
|
|
15444
|
+
// before a click, and send the stateful two-button card (clicking one
|
|
15445
|
+
// never removes the other — the handler rebuilds this same card).
|
|
15446
|
+
// `recovered`: display-only card. On any send failure, fall back to the
|
|
15447
|
+
// plain-text alert so the owner still hears about it.
|
|
15448
|
+
let cardJson;
|
|
15449
|
+
if (action.kind === 'entered') {
|
|
15450
|
+
const nonce = randomUUID();
|
|
15451
|
+
registerOverloadNonce(nonce);
|
|
15452
|
+
let counts = { stopped: 0, idle: 0 };
|
|
15453
|
+
try {
|
|
15454
|
+
counts = await countHostOverload();
|
|
15455
|
+
}
|
|
15456
|
+
catch (err) {
|
|
15457
|
+
logger.warn(`[overload] count failed, showing 0: ${err instanceof Error ? err.message : String(err)}`);
|
|
15458
|
+
}
|
|
15459
|
+
cardJson = buildOverloadAlertCard(initialOverloadCardState(action, counts, nonce));
|
|
15460
|
+
}
|
|
15461
|
+
else {
|
|
15462
|
+
cardJson = buildOverloadRecoveredCard(action);
|
|
15463
|
+
}
|
|
15464
|
+
void sendUserMessage(cfg.larkAppId, ownerOpenId, cardJson, 'interactive')
|
|
15465
|
+
.catch((err) => {
|
|
15466
|
+
logger.warn(`[overload] card DM failed, falling back to text: ${err instanceof Error ? err.message : String(err)}`);
|
|
15467
|
+
return sendUserMessage(cfg.larkAppId, ownerOpenId, formatOverloadAlert(action));
|
|
15468
|
+
})
|
|
15469
|
+
.catch((err) => logger.warn(`[overload] text fallback DM also failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
15470
|
+
}
|
|
15471
|
+
catch (err) {
|
|
15472
|
+
logger.warn(`[overload] sample failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
15473
|
+
}
|
|
15474
|
+
})();
|
|
15475
|
+
}, 30_000);
|
|
15476
|
+
overloadTimer.unref?.();
|
|
15477
|
+
}
|
|
14987
15478
|
// Graceful shutdown. Sends SIGTERM (or `{type:'close'}` IPC via killWorker)
|
|
14988
15479
|
// to every worker, then waits up to SHUTDOWN_GRACE_MS for them to exit
|
|
14989
15480
|
// before sending SIGKILL to stragglers. Without the wait, daemon
|