botmux 3.5.2 → 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/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.js +5 -5
- package/dist/adapters/cli/codex.js.map +1 -1
- package/dist/adapters/cli/fs-policy.d.ts.map +1 -1
- package/dist/adapters/cli/fs-policy.js +5 -7
- package/dist/adapters/cli/fs-policy.js.map +1 -1
- 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/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 +28 -0
- package/dist/bot-registry.d.ts.map +1 -1
- package/dist/bot-registry.js +75 -0
- package/dist/bot-registry.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +272 -86
- 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 +38 -16
- 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 +330 -57
- 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 +19 -0
- package/dist/core/passthrough-commands.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-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 +20 -0
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +283 -32
- 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 +520 -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 +3 -0
- package/dist/dashboard/bot-payload.d.ts.map +1 -1
- package/dist/dashboard/bot-payload.js +3 -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 +1 -1
- package/dist/dashboard/web/bot-defaults-page.d.ts.map +1 -1
- package/dist/dashboard/web/bot-defaults-page.js +97 -35
- package/dist/dashboard/web/bot-defaults-page.js.map +1 -1
- package/dist/dashboard/web/bot-defaults.d.ts +3 -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 +100 -8
- 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 +77 -23
- 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-KPQAUX7V.js → chunk-JMRGZQDB.js} +1 -1
- package/dist/dashboard-web/chunks/{chunk-R7IAKAYX.js → chunk-NP4JUBGS.js} +1 -1
- package/dist/dashboard-web/chunks/{chunk-NRL6CVAS.js → chunk-P327LXOT.js} +1 -1
- package/dist/dashboard-web/chunks/{chunk-X2L65IOM.js → chunk-SNKATZWK.js} +15 -15
- package/dist/dashboard-web/chunks/connectors-page-DOY4J3ZL.js +1 -0
- package/dist/dashboard-web/chunks/{groups-page-6DKXBYOM.js → groups-page-74TS2DIH.js} +1 -1
- package/dist/dashboard-web/chunks/{insights-page-NWARTJ6J.js → insights-page-AYCO65JC.js} +1 -1
- package/dist/dashboard-web/chunks/{monitor-room-AGX3XRJX.js → monitor-room-YKYLTSAN.js} +1 -1
- package/dist/dashboard-web/chunks/{monitoring-page-GGGIWN4C.js → monitoring-page-QBIUNRBR.js} +1 -1
- package/dist/dashboard-web/chunks/{office-page-6SIVKSK3.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-WE2YB7KN.js → roles-page-U4BTOQ7F.js} +1 -1
- package/dist/dashboard-web/chunks/{schedules-page-TB3I24XN.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-3QAT2IWD.js → skills-page-SYOIGGXH.js} +1 -1
- package/dist/dashboard-web/chunks/{team-federation-page-K4VVAVAK.js → team-federation-page-RMBLASTW.js} +1 -1
- package/dist/dashboard-web/chunks/{v3-page-V6IOJR2B.js → v3-page-XJXKKJRN.js} +1 -1
- package/dist/dashboard-web/chunks/{whiteboards-page-V6CGKRNX.js → whiteboards-page-K3YVSTHJ.js} +1 -1
- package/dist/dashboard-web/style.css +551 -0
- package/dist/dashboard.js +153 -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 +23 -0
- package/dist/global-config.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +5 -0
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +5 -0
- package/dist/i18n/zh.js.map +1 -1
- package/dist/im/lark/card-builder.d.ts +1 -1
- package/dist/im/lark/card-builder.js +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 +163 -17
- package/dist/im/lark/card-handler.js.map +1 -1
- package/dist/im/lark/client.d.ts +11 -0
- package/dist/im/lark/client.d.ts.map +1 -1
- package/dist/im/lark/client.js +47 -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 +20 -0
- 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/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 +40 -3
- 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 +2 -1
- package/dist/utils/child-env.d.ts.map +1 -1
- package/dist/utils/child-env.js +2 -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 +601 -179
- package/dist/worker.js.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/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/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/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-AUKV7P4L.js +0 -10
- package/dist/dashboard-web/chunks/chunk-SZOB6VK2.js +0 -1
- package/dist/dashboard-web/chunks/connectors-page-HDQFEXZI.js +0 -1
- package/dist/dashboard-web/chunks/overview-page-FVR3NKAF.js +0 -1
- package/dist/dashboard-web/chunks/sessions-page-KOOOASIL.js +0 -2
- package/dist/dashboard-web/chunks/settings-page-IEBR7TGN.js +0 -2
- 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,13 +15,16 @@ 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';
|
|
26
30
|
import { migrateSandboxConfigAtStartup } from './services/sandbox-migration.js';
|
|
@@ -28,6 +32,7 @@ import * as sessionStore from './services/session-store.js';
|
|
|
28
32
|
import * as chatFirstSeenStore from './services/chat-first-seen-store.js';
|
|
29
33
|
import { ensureDefaultOncallBound } from './services/oncall-store.js';
|
|
30
34
|
import * as scheduleStore from './services/schedule-store.js';
|
|
35
|
+
import { migrateSharedSchedulesAtStartup } from './services/schedule-split-migration.js';
|
|
31
36
|
import * as messageQueue from './services/message-queue.js';
|
|
32
37
|
import { emitHookEvent, emitHookEventLocal, HOOK_EVENTS } from './services/hook-runner.js';
|
|
33
38
|
import { setSessionLifecycleShutdown } from './services/session-lifecycle-hooks.js';
|
|
@@ -36,7 +41,8 @@ import { expandMergeForward } from './im/lark/merge-forward.js';
|
|
|
36
41
|
import { bindResourcesToMessage, composeForwardFollowupContent, mergeMessageMentions } from './im/lark/forward-followup-content.js';
|
|
37
42
|
import { buildQuoteHint } from './im/lark/quote-hint.js';
|
|
38
43
|
import { logger } from './utils/logger.js';
|
|
39
|
-
import {
|
|
44
|
+
import { applyAllowedUsersResolve } from './utils/allowed-users-apply.js';
|
|
45
|
+
import { withFileLock, withFileLockSync } from './utils/file-lock.js';
|
|
40
46
|
import { delay } from './utils/timing.js';
|
|
41
47
|
import { BoundedMap } from './utils/bounded-map.js';
|
|
42
48
|
import { checkAllowedChatGroupsConfig } from './services/allowed-chat-groups.js';
|
|
@@ -116,8 +122,30 @@ let selfV3BootInstanceId;
|
|
|
116
122
|
/** Generic daemon identity used by internal receiver endpoints. Unlike the
|
|
117
123
|
* VC listener switch, every agent daemon may receive a fenced membership. */
|
|
118
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
|
+
}
|
|
119
147
|
let vcMeetingTerminalReconciler;
|
|
120
|
-
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';
|
|
121
149
|
import { getDocSubscription, listAllDocSubscriptions, removeDocSubscription, setDocCommentPollCursor } from './services/doc-subs-store.js';
|
|
122
150
|
import { BOT_REPLY_SENTINEL, subscribeDocFile, unsubscribeDocFile, addCommentReaction, hasBotSentinel, isBotAuthoredReply, listDocComments } from './im/lark/doc-comment.js';
|
|
123
151
|
import { learnFromMentions, resolveSender, flushIdentityCacheSync } from './im/lark/identity-cache.js';
|
|
@@ -143,7 +171,7 @@ import { createGroupWithBots } from './services/group-creator.js';
|
|
|
143
171
|
import { addBotToChat, isInChat } from './services/groups-store.js';
|
|
144
172
|
import { setChatReplyMode } from './services/chat-reply-mode-store.js';
|
|
145
173
|
import { hasVcMeetingEndedTombstone, listVcMeetingRuntimeSessionsByListenerAndAgent, listVcMeetingRuntimeSessions, pruneExpiredVcMeetingRuntimeSessions, recordVcMeetingEndedTombstone, recordVcMeetingRuntimeSession, removeVcMeetingRuntimeSession, } from './services/vc-meeting-runtime-store.js';
|
|
146
|
-
import { computeVcMeetingConsumerProfileHash } from './services/vc-meeting-profile-instructions.js';
|
|
174
|
+
import { computeVcMeetingConsumerProfileHash, normalizeVcMeetingProfileInstructions, } from './services/vc-meeting-profile-instructions.js';
|
|
147
175
|
import { bootstrapVcMeetingDefaultConsumerProfile } from './services/vc-meeting-consumer-profile-bootstrap.js';
|
|
148
176
|
import { getVcMeetingPreparation, normalizeVcMeetingNumber, } from './services/vc-meeting-preparations-store.js';
|
|
149
177
|
import { abandonPoisonedVcMeetingDelivery, getVcMeetingDeliveryStatus, handleVcMeetingTurnTerminal, handleVcMeetingWorkerGenerationExit, receiveVcMeetingDelivery, registerVcMeetingMember, retryPoisonedVcMeetingDelivery, } from './services/vc-meeting-delivery-receiver.js';
|
|
@@ -154,6 +182,7 @@ import { applyVcMeetingHubMemberProjection, freezeVcMeetingHubDeliveryAssignment
|
|
|
154
182
|
import { getVcMeetingFeedMetadataState, ingestVcMeetingFeedMetadata, } from './services/vc-meeting-feed-metadata-store.js';
|
|
155
183
|
import { VcMeetingTerminalReconciler } from './services/vc-meeting-terminal-reconciler.js';
|
|
156
184
|
import { resolveVcMeetingImTurnOrigin, verifyVcMeetingManagedOriginClaim, } from './services/vc-meeting-send-policy.js';
|
|
185
|
+
import { ensureVcMeetingListenerTopicRoot, } from './services/vc-meeting-listener-topic-store.js';
|
|
157
186
|
import { finishVcMeetingManagedActionProvider, finishVcMeetingManagedApprovalCard, requestVcMeetingManagedImAction, requestVcMeetingManagedAction, resolveVcMeetingManagedActionApproval, } from './services/vc-meeting-action-gate.js';
|
|
158
187
|
import { claimVcMeetingApprovalCardAttempt, findVcMeetingAction, listVcMeetingActionScopes, listVcMeetingActions, reconcileVcMeetingActionsOnBoot, } from './services/vc-meeting-action-store.js';
|
|
159
188
|
import { authorizeVcMeetingDaemonControlRequest, ensureVcMeetingDaemonAuthToken, withVcMeetingDaemonAuthHeader, } from './services/vc-meeting-daemon-auth.js';
|
|
@@ -803,6 +832,8 @@ const VC_MEETING_SYNC_INTERVAL_OPTIONS_MS = [15_000, 30_000, 60_000, 90_000];
|
|
|
803
832
|
const VC_MEETING_CUSTOM_SYNC_INTERVAL_MIN_SECONDS = 10;
|
|
804
833
|
const VC_MEETING_CUSTOM_SYNC_INTERVAL_MAX_SECONDS = 3_600;
|
|
805
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;
|
|
806
837
|
const vcMeetingEndedTombstones = new BoundedMap(2_000);
|
|
807
838
|
const vcMeetingPendingInvites = new BoundedMap(2_000);
|
|
808
839
|
function vcMeetingSessionKey(larkAppId, meetingId) {
|
|
@@ -899,6 +930,26 @@ function normalizeVcMeetingCustomSyncIntervalMs(value) {
|
|
|
899
930
|
function vcMeetingCustomSyncIntervalFromCard(data) {
|
|
900
931
|
return normalizeVcMeetingCustomSyncIntervalMs(data.action?.form_value?.[VC_MEETING_CUSTOM_SYNC_INTERVAL_FIELD]);
|
|
901
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
|
+
}
|
|
902
953
|
function vcMeetingSessionFlushIntervalMs(session, cfg) {
|
|
903
954
|
return session.syncIntervalMs ?? cfg.flushIntervalMs ?? DEFAULT_VC_MEETING_FLUSH_INTERVAL_MS;
|
|
904
955
|
}
|
|
@@ -1889,6 +1940,20 @@ async function sessionReply(anchor, content, msgType = 'text', larkAppId, turnId
|
|
|
1889
1940
|
return sendWithHookPolicy(chatId, content, msgType, opts.uuid);
|
|
1890
1941
|
}
|
|
1891
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
|
+
}
|
|
1892
1957
|
if (ds?.scope === 'chat') {
|
|
1893
1958
|
const fresh = readSessionFreshFromDisk(ds.session.sessionId, ds.larkAppId);
|
|
1894
1959
|
if (fresh)
|
|
@@ -2103,6 +2168,123 @@ function removeDaemonDescriptor(larkAppId) {
|
|
|
2103
2168
|
catch { /* ignore */ }
|
|
2104
2169
|
}
|
|
2105
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
|
+
}
|
|
2106
2288
|
// ─── Version tracking ────────────────────────────────────────────────────────
|
|
2107
2289
|
function refreshCliVersion(cliId, cliPathOverride) {
|
|
2108
2290
|
const now = Date.now();
|
|
@@ -7581,11 +7763,29 @@ function vcMeetingRuntimeAgentForProfile(profile, status, opts = {}) {
|
|
|
7581
7763
|
...(opts.activationError ? { activationError: opts.activationError.slice(0, 500) } : {}),
|
|
7582
7764
|
...(filter ? { filter } : {}),
|
|
7583
7765
|
responseMode: profile.responseMode,
|
|
7766
|
+
listenerPlacement: profile.listenerDelivery?.placement ?? 'auto',
|
|
7584
7767
|
capabilities: vcMeetingCanonicalProfileCapabilities(profile),
|
|
7585
7768
|
ownedSinks: vcMeetingCanonicalProfileOwnedSinks(profile),
|
|
7586
7769
|
deliveryProfileHash: opts.deliveryProfileHash ?? vcMeetingConsumerProfileHash(profile),
|
|
7587
7770
|
};
|
|
7588
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
|
+
}
|
|
7589
7789
|
/** Rehydrate the exact immutable profile snapshot selected for this member
|
|
7590
7790
|
* epoch. Current bots.json is only authoritative for a first join/re-add; an
|
|
7591
7791
|
* edit or deletion must not rewrite an in-flight meeting after restart. */
|
|
@@ -7598,6 +7798,9 @@ function vcMeetingProfileFromRuntimeAgent(selected) {
|
|
|
7598
7798
|
...(selected.instructions ? { instructions: selected.instructions } : {}),
|
|
7599
7799
|
...(selected.filter ? { filter: selected.filter } : {}),
|
|
7600
7800
|
responseMode: selected.responseMode,
|
|
7801
|
+
...(selected.listenerPlacement !== 'auto'
|
|
7802
|
+
? { listenerDelivery: { placement: selected.listenerPlacement } }
|
|
7803
|
+
: {}),
|
|
7601
7804
|
capabilities: [...selected.capabilities],
|
|
7602
7805
|
...(selected.ownedSinks.length > 0 ? { ownedSinks: [...selected.ownedSinks] } : {}),
|
|
7603
7806
|
};
|
|
@@ -7665,6 +7868,7 @@ async function ensureVcMeetingProfileMember(session, profile, listenerChatId, op
|
|
|
7665
7868
|
&& JSON.stringify(prior.ownedSinks) === JSON.stringify(canonicalOwnedSinks);
|
|
7666
7869
|
const samePolicy = sameStreamSemantics
|
|
7667
7870
|
&& prior.responseMode === profile.responseMode
|
|
7871
|
+
&& prior.outputPlacement === (profile.listenerDelivery?.placement ?? 'auto')
|
|
7668
7872
|
&& JSON.stringify(prior.capabilities) === JSON.stringify(canonicalCapabilities)
|
|
7669
7873
|
&& sameOwnedSinks;
|
|
7670
7874
|
// Receiver registration commits before the hub projection. A crash in that
|
|
@@ -7681,6 +7885,7 @@ async function ensureVcMeetingProfileMember(session, profile, listenerChatId, op
|
|
|
7681
7885
|
&& receiverPrior.instructions === instructions
|
|
7682
7886
|
&& receiverPrior.outputChatId === listenerChatId
|
|
7683
7887
|
&& receiverPrior.responseMode === profile.responseMode
|
|
7888
|
+
&& receiverPrior.outputPlacement === (profile.listenerDelivery?.placement ?? 'auto')
|
|
7684
7889
|
&& JSON.stringify(receiverPrior.filter ?? {}) === JSON.stringify(canonicalFilter ?? {})
|
|
7685
7890
|
&& JSON.stringify(receiverPrior.capabilities) === JSON.stringify(canonicalCapabilities)
|
|
7686
7891
|
&& JSON.stringify(receiverPrior.ownedSinks) === JSON.stringify(canonicalOwnedSinks)
|
|
@@ -7745,7 +7950,10 @@ async function ensureVcMeetingProfileMember(session, profile, listenerChatId, op
|
|
|
7745
7950
|
responseMode: orphanedReceiver?.responseMode ?? profile.responseMode,
|
|
7746
7951
|
...policy,
|
|
7747
7952
|
},
|
|
7748
|
-
outputRoute: {
|
|
7953
|
+
outputRoute: {
|
|
7954
|
+
chatId: orphanedReceiver?.outputChatId ?? listenerChatId,
|
|
7955
|
+
placement: orphanedReceiver?.outputPlacement ?? profile.listenerDelivery?.placement ?? 'auto',
|
|
7956
|
+
},
|
|
7749
7957
|
};
|
|
7750
7958
|
const receiver = await postVcMeetingMemberProjection(profile.agentAppId, projection);
|
|
7751
7959
|
const body = vcMeetingResponseRecord(receiver.body);
|
|
@@ -7778,6 +7986,7 @@ async function ensureVcMeetingProfileMember(session, profile, listenerChatId, op
|
|
|
7778
7986
|
membershipGeneration,
|
|
7779
7987
|
status: 'active',
|
|
7780
7988
|
responseMode: orphanedReceiver?.responseMode ?? profile.responseMode,
|
|
7989
|
+
outputPlacement: orphanedReceiver?.outputPlacement ?? profile.listenerDelivery?.placement ?? 'auto',
|
|
7781
7990
|
...policy,
|
|
7782
7991
|
joinedAtIngestSeq,
|
|
7783
7992
|
receiverSessionId,
|
|
@@ -7890,6 +8099,7 @@ async function pauseVcMeetingSingleConsumerMembership(session) {
|
|
|
7890
8099
|
membershipGeneration,
|
|
7891
8100
|
status: 'paused',
|
|
7892
8101
|
responseMode: prior.responseMode,
|
|
8102
|
+
outputPlacement: prior.outputPlacement ?? 'auto',
|
|
7893
8103
|
joinedAtIngestSeq: prior.joinedAtIngestSeq,
|
|
7894
8104
|
receiverSessionId: prior.receiverSessionId,
|
|
7895
8105
|
outputChatId: prior.outputChatId,
|
|
@@ -7915,7 +8125,7 @@ async function pauseVcMeetingSingleConsumerMembership(session) {
|
|
|
7915
8125
|
joinedAtIngestSeq: prior.joinedAtIngestSeq,
|
|
7916
8126
|
responseMode: prior.responseMode,
|
|
7917
8127
|
},
|
|
7918
|
-
outputRoute: { chatId: prior.outputChatId },
|
|
8128
|
+
outputRoute: { chatId: prior.outputChatId, placement: prior.outputPlacement ?? 'auto' },
|
|
7919
8129
|
});
|
|
7920
8130
|
const body = vcMeetingResponseRecord(receiver.body);
|
|
7921
8131
|
if (receiver.status < 200 || receiver.status >= 300 || body?.ok !== true) {
|
|
@@ -7961,6 +8171,7 @@ async function pauseVcMeetingProfileMembership(session, selected) {
|
|
|
7961
8171
|
membershipGeneration,
|
|
7962
8172
|
status: 'paused',
|
|
7963
8173
|
responseMode: prior.responseMode,
|
|
8174
|
+
outputPlacement: prior.outputPlacement ?? 'auto',
|
|
7964
8175
|
...policy,
|
|
7965
8176
|
joinedAtIngestSeq: prior.joinedAtIngestSeq,
|
|
7966
8177
|
receiverSessionId: prior.receiverSessionId,
|
|
@@ -7988,7 +8199,7 @@ async function pauseVcMeetingProfileMembership(session, selected) {
|
|
|
7988
8199
|
responseMode: prior.responseMode,
|
|
7989
8200
|
...policy,
|
|
7990
8201
|
},
|
|
7991
|
-
outputRoute: { chatId: prior.outputChatId },
|
|
8202
|
+
outputRoute: { chatId: prior.outputChatId, placement: prior.outputPlacement ?? 'auto' },
|
|
7992
8203
|
});
|
|
7993
8204
|
const body = vcMeetingResponseRecord(receiver.body);
|
|
7994
8205
|
if (receiver.status < 200 || receiver.status >= 300 || body?.ok !== true) {
|
|
@@ -8019,6 +8230,7 @@ function retireVcMeetingSingleConsumerForRecovery(session, reason) {
|
|
|
8019
8230
|
membershipGeneration,
|
|
8020
8231
|
status: 'removed',
|
|
8021
8232
|
responseMode: member.responseMode,
|
|
8233
|
+
outputPlacement: member.outputPlacement ?? 'auto',
|
|
8022
8234
|
joinedAtIngestSeq: member.joinedAtIngestSeq,
|
|
8023
8235
|
receiverSessionId: member.receiverSessionId,
|
|
8024
8236
|
outputChatId: member.outputChatId,
|
|
@@ -8038,6 +8250,7 @@ function retireVcMeetingSingleConsumerForRecovery(session, reason) {
|
|
|
8038
8250
|
membershipGeneration,
|
|
8039
8251
|
status: 'removed',
|
|
8040
8252
|
responseMode: member.responseMode,
|
|
8253
|
+
outputPlacement: member.outputPlacement ?? 'auto',
|
|
8041
8254
|
joinedAtIngestSeq: member.joinedAtIngestSeq,
|
|
8042
8255
|
receiverSessionId: member.receiverSessionId,
|
|
8043
8256
|
outputChatId: member.outputChatId,
|
|
@@ -8079,6 +8292,7 @@ function retireVcMeetingProfileMemberForRecovery(member, reason) {
|
|
|
8079
8292
|
membershipGeneration,
|
|
8080
8293
|
status: 'removed',
|
|
8081
8294
|
responseMode: member.responseMode,
|
|
8295
|
+
outputPlacement: member.outputPlacement ?? 'auto',
|
|
8082
8296
|
...policy,
|
|
8083
8297
|
joinedAtIngestSeq: member.joinedAtIngestSeq,
|
|
8084
8298
|
receiverSessionId: member.receiverSessionId,
|
|
@@ -8100,6 +8314,7 @@ function retireVcMeetingProfileMemberForRecovery(member, reason) {
|
|
|
8100
8314
|
membershipGeneration,
|
|
8101
8315
|
status: 'removed',
|
|
8102
8316
|
responseMode: member.responseMode,
|
|
8317
|
+
outputPlacement: member.outputPlacement ?? 'auto',
|
|
8103
8318
|
...policy,
|
|
8104
8319
|
joinedAtIngestSeq: member.joinedAtIngestSeq,
|
|
8105
8320
|
receiverSessionId: member.receiverSessionId,
|
|
@@ -8270,6 +8485,7 @@ function vcMeetingProfileMemberSemanticsMatchRuntime(member, selected, listenerC
|
|
|
8270
8485
|
&& member.outputChatId === listenerChatId
|
|
8271
8486
|
&& member.deliveryProfileHash === selected.deliveryProfileHash
|
|
8272
8487
|
&& member.responseMode === selected.responseMode
|
|
8488
|
+
&& (member.outputPlacement ?? 'auto') === selected.listenerPlacement
|
|
8273
8489
|
&& JSON.stringify(member.filter ?? {}) === JSON.stringify(selected.filter ?? {})
|
|
8274
8490
|
&& JSON.stringify(member.capabilities) === JSON.stringify(selected.capabilities)
|
|
8275
8491
|
&& JSON.stringify(member.ownedSinks) === JSON.stringify(selected.ownedSinks);
|
|
@@ -8311,6 +8527,7 @@ function refreshVcMeetingProfileOwnerBoot(session) {
|
|
|
8311
8527
|
joinedAtIngestSeq: member.joinedAtIngestSeq,
|
|
8312
8528
|
receiverSessionId: member.receiverSessionId,
|
|
8313
8529
|
outputChatId: member.outputChatId,
|
|
8530
|
+
outputPlacement: member.outputPlacement ?? 'auto',
|
|
8314
8531
|
});
|
|
8315
8532
|
if (!applied.ok)
|
|
8316
8533
|
throw new Error(`receiver ${member.memberId}: ${applied.reason}`);
|
|
@@ -8336,6 +8553,7 @@ function refreshVcMeetingProfileOwnerBoot(session) {
|
|
|
8336
8553
|
membershipGeneration: member.membershipGeneration,
|
|
8337
8554
|
status: member.status,
|
|
8338
8555
|
responseMode: member.responseMode,
|
|
8556
|
+
outputPlacement: member.outputPlacement ?? 'auto',
|
|
8339
8557
|
...policy,
|
|
8340
8558
|
joinedAtIngestSeq: member.joinedAtIngestSeq,
|
|
8341
8559
|
receiverSessionId: member.receiverSessionId,
|
|
@@ -9516,7 +9734,7 @@ async function removeVcMeetingProfileMember(session, selected) {
|
|
|
9516
9734
|
ownedSinks: prior.ownedSinks,
|
|
9517
9735
|
sinkOwnerGeneration: prior.sinkOwnerGeneration,
|
|
9518
9736
|
},
|
|
9519
|
-
outputRoute: { chatId: prior.outputChatId },
|
|
9737
|
+
outputRoute: { chatId: prior.outputChatId, placement: prior.outputPlacement ?? 'auto' },
|
|
9520
9738
|
};
|
|
9521
9739
|
const receiver = await postVcMeetingMemberProjection(prior.agentAppId, projection);
|
|
9522
9740
|
const body = vcMeetingResponseRecord(receiver.body);
|
|
@@ -9537,6 +9755,7 @@ async function removeVcMeetingProfileMember(session, selected) {
|
|
|
9537
9755
|
membershipGeneration,
|
|
9538
9756
|
status: 'removed',
|
|
9539
9757
|
responseMode: prior.responseMode,
|
|
9758
|
+
outputPlacement: prior.outputPlacement ?? 'auto',
|
|
9540
9759
|
...(prior.filter ? { filter: prior.filter } : {}),
|
|
9541
9760
|
capabilities: prior.capabilities,
|
|
9542
9761
|
ownedSinks: prior.ownedSinks,
|
|
@@ -9743,7 +9962,9 @@ async function applyVcMeetingConsumerProfileSelection(key, session, cfg, selecte
|
|
|
9743
9962
|
const existingByProfileId = new Map(session.selectedAgents.map(selected => [selected.profileId, selected]));
|
|
9744
9963
|
const requestedProfiles = resolution.selectedProfiles.map((profile) => {
|
|
9745
9964
|
const existing = existingByProfileId.get(profile.id);
|
|
9746
|
-
return existing
|
|
9965
|
+
return existing
|
|
9966
|
+
? vcMeetingProfileFromRuntimeAgent(existing)
|
|
9967
|
+
: vcMeetingProfileWithActivationContext(profile, opts.activationContext);
|
|
9747
9968
|
});
|
|
9748
9969
|
const retainedAfterRemovalFailure = [];
|
|
9749
9970
|
const removalErrors = [];
|
|
@@ -9836,6 +10057,7 @@ async function applyVcMeetingConsumerProfileSelection(key, session, cfg, selecte
|
|
|
9836
10057
|
session.consumerSelectionApplying = false;
|
|
9837
10058
|
session.consumerPendingProfileIds = undefined;
|
|
9838
10059
|
session.consumerPendingIntervalMs = undefined;
|
|
10060
|
+
session.consumerPendingActivationContext = undefined;
|
|
9839
10061
|
}
|
|
9840
10062
|
}
|
|
9841
10063
|
async function activateVcMeetingPreparationBinding(session, cfg, preparation) {
|
|
@@ -9983,9 +10205,10 @@ async function applyVcMeetingConsumerProfileStagedState(key, session, cfg, fallb
|
|
|
9983
10205
|
const selectedProfileIds = session.consumerPendingProfileIds
|
|
9984
10206
|
?? (fallbackProfileIds ? [...fallbackProfileIds] : undefined);
|
|
9985
10207
|
const stagedIntervalMs = session.consumerPendingIntervalMs;
|
|
10208
|
+
const activationContext = session.consumerPendingActivationContext;
|
|
9986
10209
|
if (stagedIntervalMs)
|
|
9987
10210
|
session.syncIntervalMs = stagedIntervalMs;
|
|
9988
|
-
let result = await applyVcMeetingConsumerProfileSelection(key, session, cfg, selectedProfileIds, opts);
|
|
10211
|
+
let result = await applyVcMeetingConsumerProfileSelection(key, session, cfg, selectedProfileIds, { ...opts, ...(activationContext ? { activationContext } : {}) });
|
|
9989
10212
|
if (!result.ok && selectedProfileIds === undefined && session.consumerMode === 'pending') {
|
|
9990
10213
|
// A conflicting/invalid hand-edited default must never leave a pending
|
|
9991
10214
|
// timer retrying or partially activate a subset. Fail closed to
|
|
@@ -11566,6 +11789,11 @@ async function handleVcMeetingCardAction(data, larkAppId) {
|
|
|
11566
11789
|
session.consumerSelectionApplying = false;
|
|
11567
11790
|
}
|
|
11568
11791
|
}
|
|
11792
|
+
const activationContext = vcMeetingActivationContextFromCard(data);
|
|
11793
|
+
if (!activationContext.ok) {
|
|
11794
|
+
return { toast: { type: 'error', content: activationContext.error } };
|
|
11795
|
+
}
|
|
11796
|
+
session.consumerPendingActivationContext = activationContext.context;
|
|
11569
11797
|
return applyVcMeetingConsumerSelectionInBackground(key, session, cfg, () => applyVcMeetingConsumerProfileStagedState(key, session, cfg, undefined, { claimed: true }), { cardMessageId: data.context?.open_message_id ?? data.open_message_id });
|
|
11570
11798
|
}
|
|
11571
11799
|
const selection = session.consumerPendingChoice ?? { mode: 'default' };
|
|
@@ -12194,7 +12422,7 @@ async function startInitialPassthroughSession(args) {
|
|
|
12194
12422
|
const botCfg = getBot(larkAppId).config;
|
|
12195
12423
|
refreshCliVersion(botCfg.cliId, botCfg.cliPathOverride);
|
|
12196
12424
|
const directChatSender = chatType === 'p2p'
|
|
12197
|
-
? await resolveSender(larkAppId, senderOpenId, parsed.senderType)
|
|
12425
|
+
? await resolveSender(larkAppId, senderOpenId, parsed.senderType, { messageId })
|
|
12198
12426
|
: undefined;
|
|
12199
12427
|
// Group cold-start passthroughs only need a stable caller identity while the
|
|
12200
12428
|
// repo picker is pending. Avoid adding a contact lookup to every /goal start;
|
|
@@ -12513,7 +12741,9 @@ async function handleNewTopic(data, ctx) {
|
|
|
12513
12741
|
// chat-granted users (who only pass canTalk) management commands like
|
|
12514
12742
|
// /cd /restart /oncall bind. Previously this gate only fired in oncall chats,
|
|
12515
12743
|
// which left a hole once per-chat grants flow through canTalk.
|
|
12516
|
-
|
|
12744
|
+
// canRunDaemonCommand = canOperate ∪(cmd ∈ canTalkDaemonCommands && canTalk):
|
|
12745
|
+
// bot 可通过名单把选定命令(如 /status)降到 canTalk;未配置时与 canOperate 全等。
|
|
12746
|
+
if (!canRunDaemonCommand(larkAppId, chatId, senderOpenId, teamTrustUnionId, cmd, senderUnionId, chatType)) {
|
|
12517
12747
|
await sessionReply(anchor, tr('daemon.cmd_allowed_users_only', { cmd }, localeForBot(larkAppId)), 'text', larkAppId);
|
|
12518
12748
|
return;
|
|
12519
12749
|
}
|
|
@@ -12547,7 +12777,7 @@ async function handleNewTopic(data, ctx) {
|
|
|
12547
12777
|
const session = sessionStore.createSession(chatId, cmdRootIdForStore, cmdContent.substring(0, 50), chatType);
|
|
12548
12778
|
const now = Date.now();
|
|
12549
12779
|
if (chatType === 'p2p') {
|
|
12550
|
-
setDirectChatDisplayNameFromSender(session, chatType, await resolveSender(larkAppId, senderOpenId, parsed.senderType));
|
|
12780
|
+
setDirectChatDisplayNameFromSender(session, chatType, await resolveSender(larkAppId, senderOpenId, parsed.senderType, { messageId }));
|
|
12551
12781
|
}
|
|
12552
12782
|
session.larkAppId = larkAppId;
|
|
12553
12783
|
session.ownerOpenId = senderOpenId;
|
|
@@ -12622,7 +12852,7 @@ async function handleNewTopic(data, ctx) {
|
|
|
12622
12852
|
// resolveSender for an unseen open_id may await contact.v3.user.get with a
|
|
12623
12853
|
// short budget; subsequent calls hit the cache and are sync-fast.
|
|
12624
12854
|
const groupChatNamePromise = resolveGroupChatNameForNativeTitle(larkAppId, chatId, chatType, botCfg.cliId, parsed.content, parsed.mentions);
|
|
12625
|
-
const newTopicSender = await resolveSender(larkAppId, senderOpenId, parsed.senderType);
|
|
12855
|
+
const newTopicSender = await resolveSender(larkAppId, senderOpenId, parsed.senderType, { messageId });
|
|
12626
12856
|
refreshCliVersion(botCfg.cliId, botCfg.cliPathOverride);
|
|
12627
12857
|
// Pin the working dir via the layered oncall / inherit / default lookup
|
|
12628
12858
|
// (auto-binds a defaultOncall chat as a side effect). Shared with the
|
|
@@ -13084,7 +13314,9 @@ async function handleThreadReply(data, ctx) {
|
|
|
13084
13314
|
if (threadSenderResolved)
|
|
13085
13315
|
return threadSenderCached;
|
|
13086
13316
|
threadSenderResolved = true;
|
|
13087
|
-
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 });
|
|
13088
13320
|
return threadSenderCached;
|
|
13089
13321
|
};
|
|
13090
13322
|
const content = parsed.content.trim();
|
|
@@ -13286,7 +13518,9 @@ async function handleThreadReply(data, ctx) {
|
|
|
13286
13518
|
}
|
|
13287
13519
|
// canOperate gate for thread-reply daemon commands — required in every chat
|
|
13288
13520
|
// (see spawn-path gate above). Denies chat-granted users management commands.
|
|
13289
|
-
|
|
13521
|
+
// canRunDaemonCommand:canTalkDaemonCommands 名单内的命令降到 canTalk,
|
|
13522
|
+
// 与 new-topic 路径的统一闸同款(未配置时与 canOperate 全等)。
|
|
13523
|
+
if (!canRunDaemonCommand(larkAppId, effectiveThreadChatId, threadSenderOpenId, threadTeamTrustUnionId, cmd, threadSenderUnionId, ctxChatType)) {
|
|
13290
13524
|
sessionReply(anchor, tr('daemon.cmd_allowed_users_only', { cmd }, localeForBot(larkAppId)), 'text', larkAppId);
|
|
13291
13525
|
return;
|
|
13292
13526
|
}
|
|
@@ -14199,6 +14433,111 @@ function resolvePrimaryOwnerOpenId(larkAppId) {
|
|
|
14199
14433
|
return undefined;
|
|
14200
14434
|
}
|
|
14201
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
|
+
}
|
|
14202
14541
|
/** Build the current dashboard URL (active token, not a rotation) from the
|
|
14203
14542
|
* dashboard process's persisted `.dashboard-port` / `.dashboard-token`. Falls
|
|
14204
14543
|
* back to a token-less base URL if the dashboard hasn't published a token yet. */
|
|
@@ -14399,8 +14738,12 @@ export async function startDaemon(botIndex) {
|
|
|
14399
14738
|
}
|
|
14400
14739
|
}, VC_MEETING_DELIVERY_LEASE_SCAN_MS);
|
|
14401
14740
|
vcMeetingDeliveryLeaseTimer.unref?.();
|
|
14402
|
-
//
|
|
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`
|
|
14403
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);
|
|
14404
14747
|
scheduleStore.startExternalWriteWatcher();
|
|
14405
14748
|
logger.info(`Bot ${idx}/${botConfigs.length}: ${cfg.larkAppId} (cli: ${cfg.cliId})`);
|
|
14406
14749
|
setAskCardDispatcher(createLarkAskCardDispatcher());
|
|
@@ -14434,6 +14777,16 @@ export async function startDaemon(botIndex) {
|
|
|
14434
14777
|
// briefly sees an unusable on_/email (the resolution below rewrites this field).
|
|
14435
14778
|
resolvedAllowedUsers: getBot(cfg.larkAppId).resolvedAllowedUsers.filter(u => u.startsWith('ou_')),
|
|
14436
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));
|
|
14437
14790
|
// 名称状态刷新:displayName 或飞书真名变化后,用有效展示名刷新 descriptor +
|
|
14438
14791
|
// SessionRow.botName,无需重启 daemon。displayName 路径经 bot-config-store 的
|
|
14439
14792
|
// 钩子触发;真·改名路径在下面的 renamer 里直接调用。
|
|
@@ -14683,22 +15036,68 @@ export async function startDaemon(botIndex) {
|
|
|
14683
15036
|
// Refresh CLI version per bot's cliId
|
|
14684
15037
|
refreshCliVersion(cfg.cliId, cfg.cliPathOverride);
|
|
14685
15038
|
// Resolve allowed users per bot
|
|
14686
|
-
if (bot.resolvedAllowedUsers.length > 0) {
|
|
15039
|
+
if ((bot.config.allowedUsers?.length ?? 0) > 0 || bot.resolvedAllowedUsers.length > 0) {
|
|
14687
15040
|
// 含邮箱或 union_id(on_) 都要重解析成本 app 的 open_id —— 否则 canTalk/canOperate
|
|
14688
15041
|
// 拿 sender 的 ou_ 对不上 on_,owner 会被自己的 bot 锁死(PR#72)。
|
|
14689
15042
|
// literal ou_ 也走 best-effort 校验,用于诊断把其他 app 视角 open_id
|
|
14690
15043
|
// 误填到本 bot 的配置。
|
|
14691
|
-
|
|
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_'));
|
|
14692
15054
|
if (needsResolve) {
|
|
15055
|
+
const previousResolvedMap = readAllowedUsersCache(cfg.larkAppId);
|
|
14693
15056
|
try {
|
|
14694
|
-
// 同时拿到 raw→open_id
|
|
14695
|
-
const
|
|
14696
|
-
|
|
14697
|
-
|
|
14698
|
-
|
|
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
|
+
}
|
|
14699
15078
|
}
|
|
14700
15079
|
catch (err) {
|
|
14701
|
-
|
|
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);
|
|
14702
15101
|
}
|
|
14703
15102
|
}
|
|
14704
15103
|
// Republish the descriptor with the post-resolution open_ids so the
|
|
@@ -14898,10 +15297,10 @@ export async function startDaemon(botIndex) {
|
|
|
14898
15297
|
}, 5_000);
|
|
14899
15298
|
docCommentPollTimer.unref?.();
|
|
14900
15299
|
void pollWatchedDocComments(cfg.larkAppId);
|
|
14901
|
-
// Sweep orphan sandbox
|
|
15300
|
+
// Sweep orphan sandbox trees left by a previous run's crash/kill: any
|
|
14902
15301
|
// <dataDir>/sandboxes/<sid> whose session is no longer active gets its
|
|
14903
|
-
//
|
|
14904
|
-
// 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.
|
|
14905
15304
|
try {
|
|
14906
15305
|
sweepOrphanSandboxes(config.session.dataDir, new Set([...activeSessions.values()].map(ds => ds.session.sessionId)));
|
|
14907
15306
|
}
|
|
@@ -14915,12 +15314,12 @@ export async function startDaemon(botIndex) {
|
|
|
14915
15314
|
}, 60_000);
|
|
14916
15315
|
idleWorkerSweepTimer.unref?.();
|
|
14917
15316
|
// Periodic sandbox reconciler: the daemon's SIGKILL straggler-reaper (and any
|
|
14918
|
-
// worker SIGKILL) bypasses worker-side killCli(), so the
|
|
14919
|
-
//
|
|
14920
|
-
// the rest of this daemon's lifetime (one daemon per bot can run
|
|
14921
|
-
// startup sweep alone can't catch a session that dies AFTER boot.
|
|
14922
|
-
// the sweep on a timer: it reclaims
|
|
14923
|
-
//
|
|
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.
|
|
14924
15323
|
const sandboxReconcileTimer = setInterval(() => {
|
|
14925
15324
|
try {
|
|
14926
15325
|
sweepOrphanSandboxes(config.session.dataDir, new Set([...activeSessions.values()].map(ds => ds.session.sessionId)));
|
|
@@ -14990,6 +15389,92 @@ export async function startDaemon(botIndex) {
|
|
|
14990
15389
|
});
|
|
14991
15390
|
}, 5_000).unref?.();
|
|
14992
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
|
+
}
|
|
14993
15478
|
// Graceful shutdown. Sends SIGTERM (or `{type:'close'}` IPC via killWorker)
|
|
14994
15479
|
// to every worker, then waits up to SHUTDOWN_GRACE_MS for them to exit
|
|
14995
15480
|
// before sending SIGKILL to stragglers. Without the wait, daemon
|