botmux 3.7.1 → 3.8.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/dist/.runtime-build-id +1 -1
- package/dist/adapters/backend/capabilities.d.ts +17 -0
- package/dist/adapters/backend/capabilities.d.ts.map +1 -0
- package/dist/adapters/backend/capabilities.js +22 -0
- package/dist/adapters/backend/capabilities.js.map +1 -0
- package/dist/adapters/backend/critical-control-key.d.ts +19 -0
- package/dist/adapters/backend/critical-control-key.d.ts.map +1 -0
- package/dist/adapters/backend/critical-control-key.js +34 -0
- package/dist/adapters/backend/critical-control-key.js.map +1 -0
- package/dist/adapters/backend/herdr-backend.d.ts +9 -0
- package/dist/adapters/backend/herdr-backend.d.ts.map +1 -1
- package/dist/adapters/backend/herdr-backend.js +23 -4
- package/dist/adapters/backend/herdr-backend.js.map +1 -1
- package/dist/adapters/backend/riff-backend.d.ts.map +1 -1
- package/dist/adapters/backend/riff-backend.js +3 -2
- package/dist/adapters/backend/riff-backend.js.map +1 -1
- package/dist/adapters/backend/session-backend-selector.d.ts +61 -6
- package/dist/adapters/backend/session-backend-selector.d.ts.map +1 -1
- package/dist/adapters/backend/session-backend-selector.js +173 -9
- package/dist/adapters/backend/session-backend-selector.js.map +1 -1
- package/dist/adapters/backend/tmux-backend.d.ts +10 -3
- package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
- package/dist/adapters/backend/tmux-backend.js +25 -8
- package/dist/adapters/backend/tmux-backend.js.map +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.d.ts.map +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.js +3 -2
- package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
- package/dist/adapters/backend/types.d.ts +45 -4
- package/dist/adapters/backend/types.d.ts.map +1 -1
- package/dist/adapters/backend/types.js +11 -0
- package/dist/adapters/backend/types.js.map +1 -1
- package/dist/adapters/backend/zellij-backend.d.ts.map +1 -1
- package/dist/adapters/backend/zellij-backend.js +3 -2
- package/dist/adapters/backend/zellij-backend.js.map +1 -1
- package/dist/adapters/backend/zmx-backend.d.ts +286 -0
- package/dist/adapters/backend/zmx-backend.d.ts.map +1 -0
- package/dist/adapters/backend/zmx-backend.js +2396 -0
- package/dist/adapters/backend/zmx-backend.js.map +1 -0
- package/dist/adapters/cli/claude-code.d.ts.map +1 -1
- package/dist/adapters/cli/claude-code.js +3 -0
- package/dist/adapters/cli/claude-code.js.map +1 -1
- package/dist/adapters/cli/codex-app.d.ts.map +1 -1
- package/dist/adapters/cli/codex-app.js +5 -1
- 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 +26 -1
- package/dist/adapters/cli/codex.js.map +1 -1
- package/dist/adapters/cli/fs-policy.d.ts +79 -1
- package/dist/adapters/cli/fs-policy.d.ts.map +1 -1
- package/dist/adapters/cli/fs-policy.js +290 -13
- package/dist/adapters/cli/fs-policy.js.map +1 -1
- package/dist/adapters/cli/grok.d.ts.map +1 -1
- package/dist/adapters/cli/grok.js +4 -1
- package/dist/adapters/cli/grok.js.map +1 -1
- package/dist/adapters/cli/oh-my-pi.d.ts.map +1 -1
- package/dist/adapters/cli/oh-my-pi.js +5 -3
- package/dist/adapters/cli/oh-my-pi.js.map +1 -1
- package/dist/adapters/cli/shared-hints.d.ts +7 -4
- package/dist/adapters/cli/shared-hints.d.ts.map +1 -1
- package/dist/adapters/cli/shared-hints.js +52 -35
- package/dist/adapters/cli/shared-hints.js.map +1 -1
- package/dist/adapters/cli/strict-input-handle.d.ts +13 -0
- package/dist/adapters/cli/strict-input-handle.d.ts.map +1 -0
- package/dist/adapters/cli/strict-input-handle.js +39 -0
- package/dist/adapters/cli/strict-input-handle.js.map +1 -0
- package/dist/adapters/cli/traex.d.ts.map +1 -1
- package/dist/adapters/cli/traex.js +6 -4
- package/dist/adapters/cli/traex.js.map +1 -1
- package/dist/adapters/cli/types.d.ts +16 -0
- package/dist/adapters/cli/types.d.ts.map +1 -1
- package/dist/bot-registry.d.ts +137 -8
- package/dist/bot-registry.d.ts.map +1 -1
- package/dist/bot-registry.js +298 -12
- package/dist/bot-registry.js.map +1 -1
- package/dist/cli/session-list-liveness.d.ts +1 -1
- package/dist/cli/session-list-liveness.js +1 -1
- package/dist/cli/zmx-managed-attach.d.ts +41 -0
- package/dist/cli/zmx-managed-attach.d.ts.map +1 -0
- package/dist/cli/zmx-managed-attach.js +72 -0
- package/dist/cli/zmx-managed-attach.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +780 -131
- package/dist/cli.js.map +1 -1
- package/dist/codex-app-runner.js +90 -2
- package/dist/codex-app-runner.js.map +1 -1
- package/dist/codex-rpc-engine.d.ts.map +1 -1
- package/dist/codex-rpc-engine.js +22 -6
- package/dist/codex-rpc-engine.js.map +1 -1
- package/dist/config.d.ts +5 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +29 -1
- package/dist/config.js.map +1 -1
- package/dist/core/ask-broker.d.ts +22 -1
- package/dist/core/ask-broker.d.ts.map +1 -1
- package/dist/core/ask-broker.js +12 -5
- package/dist/core/ask-broker.js.map +1 -1
- package/dist/core/botmux-wrapper.d.ts +18 -0
- package/dist/core/botmux-wrapper.d.ts.map +1 -1
- package/dist/core/botmux-wrapper.js +25 -1
- package/dist/core/botmux-wrapper.js.map +1 -1
- package/dist/core/chat-rename.d.ts +27 -0
- package/dist/core/chat-rename.d.ts.map +1 -0
- package/dist/core/chat-rename.js +53 -0
- package/dist/core/chat-rename.js.map +1 -0
- package/dist/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +160 -12
- package/dist/core/command-handler.js.map +1 -1
- package/dist/core/cost-calculator.d.ts +25 -1
- package/dist/core/cost-calculator.d.ts.map +1 -1
- package/dist/core/cost-calculator.js +102 -4
- package/dist/core/cost-calculator.js.map +1 -1
- package/dist/core/dashboard-ipc-server.d.ts +16 -2
- package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
- package/dist/core/dashboard-ipc-server.js +611 -62
- package/dist/core/dashboard-ipc-server.js.map +1 -1
- package/dist/core/dashboard-rows.d.ts +22 -1
- package/dist/core/dashboard-rows.d.ts.map +1 -1
- package/dist/core/dashboard-rows.js +61 -0
- package/dist/core/dashboard-rows.js.map +1 -1
- package/dist/core/dashboard-url.d.ts +6 -0
- package/dist/core/dashboard-url.d.ts.map +1 -1
- package/dist/core/dashboard-url.js +11 -1
- package/dist/core/dashboard-url.js.map +1 -1
- package/dist/core/device-isolation-daemon.d.ts +28 -6
- package/dist/core/device-isolation-daemon.d.ts.map +1 -1
- package/dist/core/device-isolation-daemon.js +96 -13
- package/dist/core/device-isolation-daemon.js.map +1 -1
- package/dist/core/host-overload-alert.d.ts +75 -0
- package/dist/core/host-overload-alert.d.ts.map +1 -1
- package/dist/core/host-overload-alert.js +103 -0
- package/dist/core/host-overload-alert.js.map +1 -1
- package/dist/core/idle-worker-sweeper.js +1 -1
- package/dist/core/idle-worker-sweeper.js.map +1 -1
- package/dist/core/inflight-input-tracker.d.ts +3 -0
- package/dist/core/inflight-input-tracker.d.ts.map +1 -1
- package/dist/core/inflight-input-tracker.js +9 -0
- package/dist/core/inflight-input-tracker.js.map +1 -1
- package/dist/core/passthrough-commands.d.ts.map +1 -1
- package/dist/core/passthrough-commands.js +10 -0
- package/dist/core/passthrough-commands.js.map +1 -1
- package/dist/core/persistent-backend.d.ts +32 -4
- package/dist/core/persistent-backend.d.ts.map +1 -1
- package/dist/core/persistent-backend.js +75 -6
- package/dist/core/persistent-backend.js.map +1 -1
- package/dist/core/plugins/mcp/gateway.js +2 -2
- package/dist/core/plugins/mcp/gateway.js.map +1 -1
- package/dist/core/plugins/runtime.d.ts +3 -0
- package/dist/core/plugins/runtime.d.ts.map +1 -1
- package/dist/core/plugins/runtime.js.map +1 -1
- package/dist/core/plugins/service-manager.d.ts +4 -1
- package/dist/core/plugins/service-manager.d.ts.map +1 -1
- package/dist/core/plugins/service-manager.js +11 -6
- package/dist/core/plugins/service-manager.js.map +1 -1
- package/dist/core/role-library.d.ts +48 -1
- package/dist/core/role-library.d.ts.map +1 -1
- package/dist/core/role-library.js +111 -3
- package/dist/core/role-library.js.map +1 -1
- package/dist/core/role-resolver.d.ts +14 -2
- package/dist/core/role-resolver.d.ts.map +1 -1
- package/dist/core/role-resolver.js +47 -4
- package/dist/core/role-resolver.js.map +1 -1
- package/dist/core/session-activity.d.ts +14 -0
- package/dist/core/session-activity.d.ts.map +1 -1
- package/dist/core/session-activity.js +46 -1
- package/dist/core/session-activity.js.map +1 -1
- package/dist/core/session-board.d.ts +5 -2
- package/dist/core/session-board.d.ts.map +1 -1
- package/dist/core/session-board.js +5 -2
- package/dist/core/session-board.js.map +1 -1
- package/dist/core/session-liveness.d.ts +6 -5
- package/dist/core/session-liveness.d.ts.map +1 -1
- package/dist/core/session-liveness.js +34 -13
- package/dist/core/session-liveness.js.map +1 -1
- package/dist/core/session-manager.d.ts +3 -2
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +397 -103
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-marker.d.ts +11 -2
- package/dist/core/session-marker.d.ts.map +1 -1
- package/dist/core/session-marker.js +50 -11
- package/dist/core/session-marker.js.map +1 -1
- package/dist/core/session-message-preview.d.ts +20 -0
- package/dist/core/session-message-preview.d.ts.map +1 -0
- package/dist/core/session-message-preview.js +188 -0
- package/dist/core/session-message-preview.js.map +1 -0
- package/dist/core/session-title.d.ts +5 -1
- package/dist/core/session-title.d.ts.map +1 -1
- package/dist/core/session-title.js +20 -3
- package/dist/core/session-title.js.map +1 -1
- package/dist/core/startup-commands.d.ts +1 -1
- package/dist/core/startup-commands.js +1 -1
- package/dist/core/terminal-url.d.ts.map +1 -1
- package/dist/core/terminal-url.js +3 -2
- package/dist/core/terminal-url.js.map +1 -1
- package/dist/core/trigger-session.d.ts.map +1 -1
- package/dist/core/trigger-session.js +75 -12
- package/dist/core/trigger-session.js.map +1 -1
- package/dist/core/types.d.ts +35 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +23 -0
- package/dist/core/types.js.map +1 -1
- package/dist/core/worker-ipc.d.ts +11 -0
- package/dist/core/worker-ipc.d.ts.map +1 -0
- package/dist/core/worker-ipc.js +26 -0
- package/dist/core/worker-ipc.js.map +1 -0
- package/dist/core/worker-pool.d.ts +179 -29
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +2118 -331
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts +132 -3
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +1327 -449
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/auth.d.ts +2 -0
- package/dist/dashboard/auth.d.ts.map +1 -1
- package/dist/dashboard/auth.js +9 -0
- package/dist/dashboard/auth.js.map +1 -1
- package/dist/dashboard/bot-payload.d.ts +4 -0
- package/dist/dashboard/bot-payload.d.ts.map +1 -1
- package/dist/dashboard/bot-payload.js +5 -0
- package/dist/dashboard/bot-payload.js.map +1 -1
- package/dist/dashboard/federation-api.d.ts.map +1 -1
- package/dist/dashboard/federation-api.js +4 -0
- package/dist/dashboard/federation-api.js.map +1 -1
- package/dist/dashboard/federation-spoke-api.d.ts.map +1 -1
- package/dist/dashboard/federation-spoke-api.js +32 -5
- package/dist/dashboard/federation-spoke-api.js.map +1 -1
- package/dist/dashboard/public-redact.d.ts +8 -2
- package/dist/dashboard/public-redact.d.ts.map +1 -1
- package/dist/dashboard/public-redact.js +34 -7
- package/dist/dashboard/public-redact.js.map +1 -1
- package/dist/dashboard/session-card-model.d.ts.map +1 -1
- package/dist/dashboard/session-card-model.js +10 -2
- package/dist/dashboard/session-card-model.js.map +1 -1
- package/dist/dashboard/settings-write-applier.d.ts +35 -1
- package/dist/dashboard/settings-write-applier.d.ts.map +1 -1
- package/dist/dashboard/settings-write-applier.js +88 -1
- package/dist/dashboard/settings-write-applier.js.map +1 -1
- package/dist/dashboard/web/app.d.ts.map +1 -1
- package/dist/dashboard/web/app.js +0 -1
- package/dist/dashboard/web/app.js.map +1 -1
- package/dist/dashboard/web/bot-defaults-page.d.ts +15 -0
- package/dist/dashboard/web/bot-defaults-page.d.ts.map +1 -1
- package/dist/dashboard/web/bot-defaults-page.js +212 -58
- 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/bot-onboarding.d.ts +2 -1
- package/dist/dashboard/web/bot-onboarding.d.ts.map +1 -1
- package/dist/dashboard/web/bot-onboarding.js.map +1 -1
- package/dist/dashboard/web/clipboard.d.ts +2 -0
- package/dist/dashboard/web/clipboard.d.ts.map +1 -0
- package/dist/dashboard/web/clipboard.js +42 -0
- package/dist/dashboard/web/clipboard.js.map +1 -0
- package/dist/dashboard/web/connectors-page.d.ts.map +1 -1
- package/dist/dashboard/web/connectors-page.js +12 -8
- package/dist/dashboard/web/connectors-page.js.map +1 -1
- package/dist/dashboard/web/dashboard-components.d.ts +17 -16
- package/dist/dashboard/web/dashboard-components.d.ts.map +1 -1
- package/dist/dashboard/web/dashboard-components.js.map +1 -1
- package/dist/dashboard/web/groups-page.d.ts.map +1 -1
- package/dist/dashboard/web/groups-page.js +7 -3
- package/dist/dashboard/web/groups-page.js.map +1 -1
- package/dist/dashboard/web/i18n.d.ts.map +1 -1
- package/dist/dashboard/web/i18n.js +205 -12
- package/dist/dashboard/web/i18n.js.map +1 -1
- package/dist/dashboard/web/icon-tooltip.d.ts.map +1 -1
- package/dist/dashboard/web/icon-tooltip.js +16 -1
- package/dist/dashboard/web/icon-tooltip.js.map +1 -1
- package/dist/dashboard/web/listener-filters.d.ts +23 -0
- package/dist/dashboard/web/listener-filters.d.ts.map +1 -0
- package/dist/dashboard/web/listener-filters.js +36 -0
- package/dist/dashboard/web/listener-filters.js.map +1 -0
- package/dist/dashboard/web/overview-page.d.ts.map +1 -1
- package/dist/dashboard/web/overview-page.js.map +1 -1
- package/dist/dashboard/web/plugin-page.d.ts.map +1 -1
- package/dist/dashboard/web/plugin-page.js.map +1 -1
- package/dist/dashboard/web/preview-markdown.d.ts +5 -0
- package/dist/dashboard/web/preview-markdown.d.ts.map +1 -0
- package/dist/dashboard/web/preview-markdown.js +52 -0
- package/dist/dashboard/web/preview-markdown.js.map +1 -0
- package/dist/dashboard/web/roles-page.d.ts.map +1 -1
- package/dist/dashboard/web/roles-page.js +535 -10
- package/dist/dashboard/web/roles-page.js.map +1 -1
- package/dist/dashboard/web/roles.d.ts +77 -0
- package/dist/dashboard/web/roles.d.ts.map +1 -1
- package/dist/dashboard/web/roles.js +98 -0
- package/dist/dashboard/web/roles.js.map +1 -1
- package/dist/dashboard/web/session-terminal.d.ts.map +1 -1
- package/dist/dashboard/web/session-terminal.js +6 -4
- package/dist/dashboard/web/session-terminal.js.map +1 -1
- package/dist/dashboard/web/sessions-kanban.d.ts +4 -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 +3 -2
- package/dist/dashboard/web/sessions-page.d.ts.map +1 -1
- package/dist/dashboard/web/sessions-page.js +163 -28
- package/dist/dashboard/web/sessions-page.js.map +1 -1
- package/dist/dashboard/web/sessions.d.ts +25 -0
- package/dist/dashboard/web/sessions.d.ts.map +1 -1
- package/dist/dashboard/web/sessions.js +58 -7
- package/dist/dashboard/web/sessions.js.map +1 -1
- package/dist/dashboard/web/settings-page.d.ts +28 -3
- package/dist/dashboard/web/settings-page.d.ts.map +1 -1
- package/dist/dashboard/web/settings-page.js +61 -1
- package/dist/dashboard/web/settings-page.js.map +1 -1
- package/dist/dashboard/web/skills-page.d.ts +6 -5
- package/dist/dashboard/web/skills-page.d.ts.map +1 -1
- package/dist/dashboard/web/skills-page.js +2 -0
- package/dist/dashboard/web/skills-page.js.map +1 -1
- package/dist/dashboard/web/v3-components.d.ts +3 -2
- package/dist/dashboard/web/v3-components.d.ts.map +1 -1
- package/dist/dashboard/web/v3-components.js.map +1 -1
- package/dist/dashboard/web/vc-consumer-profiles-section.d.ts +12 -2
- package/dist/dashboard/web/vc-consumer-profiles-section.d.ts.map +1 -1
- package/dist/dashboard/web/vc-consumer-profiles-section.js +0 -9
- package/dist/dashboard/web/vc-consumer-profiles-section.js.map +1 -1
- package/dist/dashboard/web/webhook-logs-page.d.ts +2 -1
- package/dist/dashboard/web/webhook-logs-page.d.ts.map +1 -1
- package/dist/dashboard/web/webhook-logs-page.js +4 -1
- package/dist/dashboard/web/webhook-logs-page.js.map +1 -1
- package/dist/dashboard-web/app.js +1 -1
- package/dist/dashboard-web/chunks/bot-defaults-page-FAPIMQV3.js +10 -0
- package/dist/dashboard-web/chunks/{chunk-QFMH6ITB.js → chunk-5CJMHTT4.js} +1 -1
- package/dist/dashboard-web/chunks/{chunk-KQB42ML5.js → chunk-DLZK52JU.js} +14 -12
- package/dist/dashboard-web/chunks/chunk-FDZ7WOBW.js +16 -0
- package/dist/dashboard-web/chunks/{chunk-3QAUJXAS.js → chunk-GTAJV5YC.js} +1 -1
- package/dist/dashboard-web/chunks/{chunk-FNPGOZMX.js → chunk-IHNG6KE6.js} +1 -1
- package/dist/dashboard-web/chunks/{chunk-FLGXZJTD.js → chunk-VA6QWSGQ.js} +1 -1
- package/dist/dashboard-web/chunks/chunk-XJ7BGPJH.js +1 -0
- package/dist/dashboard-web/chunks/chunk-ZJVTGU5X.js +1 -0
- package/dist/dashboard-web/chunks/connectors-page-LKWRT3GS.js +1 -0
- package/dist/dashboard-web/chunks/groups-page-V6654ICQ.js +8 -0
- package/dist/dashboard-web/chunks/insights-page-237RMFQ5.js +7 -0
- package/dist/dashboard-web/chunks/{monitor-room-7IVIS75W.js → monitor-room-OO5Y4FRQ.js} +1 -1
- package/dist/dashboard-web/chunks/{monitoring-page-5SSJRQZF.js → monitoring-page-OUEITM5P.js} +1 -1
- package/dist/dashboard-web/chunks/{office-page-EOND3L5D.js → office-page-4KNAHQ4Q.js} +1 -1
- package/dist/dashboard-web/chunks/{overview-page-NISBVBBJ.js → overview-page-QDP4YK2S.js} +1 -1
- package/dist/dashboard-web/chunks/{plugin-page-JXC5F5YV.js → plugin-page-VEGTCEAS.js} +1 -1
- package/dist/dashboard-web/chunks/roles-page-PGXNZZNP.js +1 -0
- package/dist/dashboard-web/chunks/{schedules-page-AVOOKLUH.js → schedules-page-BJNC5TID.js} +1 -1
- package/dist/dashboard-web/chunks/sessions-page-M2ZYYCMO.js +6 -0
- package/dist/dashboard-web/chunks/settings-page-IJFZKUUW.js +2 -0
- package/dist/dashboard-web/chunks/{skills-page-5MW54PCC.js → skills-page-YRAIRZOQ.js} +3 -3
- package/dist/dashboard-web/chunks/{team-federation-page-2SNIMVXI.js → team-federation-page-DEU27GRU.js} +1 -1
- package/dist/dashboard-web/chunks/{v3-page-W6SBXXQC.js → v3-page-6KHEW2YL.js} +5 -5
- package/dist/dashboard-web/chunks/{whiteboards-page-STC54KTP.js → whiteboards-page-VDECAALH.js} +1 -1
- package/dist/dashboard-web/style.css +12545 -11432
- package/dist/dashboard.js +215 -36
- package/dist/dashboard.js.map +1 -1
- package/dist/global-config.d.ts +41 -2
- package/dist/global-config.d.ts.map +1 -1
- package/dist/global-config.js +47 -0
- package/dist/global-config.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +71 -19
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +71 -19
- package/dist/i18n/zh.js.map +1 -1
- package/dist/im/lark/callback-button-marker.d.ts +34 -0
- package/dist/im/lark/callback-button-marker.d.ts.map +1 -0
- package/dist/im/lark/callback-button-marker.js +99 -0
- package/dist/im/lark/callback-button-marker.js.map +1 -0
- package/dist/im/lark/card-builder.d.ts +46 -10
- package/dist/im/lark/card-builder.d.ts.map +1 -1
- package/dist/im/lark/card-builder.js +299 -78
- package/dist/im/lark/card-builder.js.map +1 -1
- package/dist/im/lark/card-handler.d.ts +3 -4
- package/dist/im/lark/card-handler.d.ts.map +1 -1
- package/dist/im/lark/card-handler.js +380 -148
- package/dist/im/lark/card-handler.js.map +1 -1
- package/dist/im/lark/client.d.ts +24 -1
- package/dist/im/lark/client.d.ts.map +1 -1
- package/dist/im/lark/client.js +101 -8
- package/dist/im/lark/client.js.map +1 -1
- package/dist/im/lark/doc-comment.d.ts.map +1 -1
- package/dist/im/lark/doc-comment.js +6 -1
- package/dist/im/lark/doc-comment.js.map +1 -1
- package/dist/im/lark/event-dispatcher.d.ts +115 -3
- package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
- package/dist/im/lark/event-dispatcher.js +534 -147
- package/dist/im/lark/event-dispatcher.js.map +1 -1
- package/dist/im/lark/grant-command.d.ts +16 -12
- package/dist/im/lark/grant-command.d.ts.map +1 -1
- package/dist/im/lark/grant-command.js +48 -151
- package/dist/im/lark/grant-command.js.map +1 -1
- package/dist/im/lark/grant-pending.d.ts +12 -4
- package/dist/im/lark/grant-pending.d.ts.map +1 -1
- package/dist/im/lark/grant-pending.js +31 -8
- package/dist/im/lark/grant-pending.js.map +1 -1
- package/dist/im/lark/invite-command.d.ts +20 -0
- package/dist/im/lark/invite-command.d.ts.map +1 -0
- package/dist/im/lark/invite-command.js +239 -0
- package/dist/im/lark/invite-command.js.map +1 -0
- package/dist/im/lark/md-card.d.ts +66 -4
- package/dist/im/lark/md-card.d.ts.map +1 -1
- package/dist/im/lark/md-card.js +201 -31
- package/dist/im/lark/md-card.js.map +1 -1
- package/dist/im/lark/mention-targets.d.ts +43 -0
- package/dist/im/lark/mention-targets.d.ts.map +1 -0
- package/dist/im/lark/mention-targets.js +190 -0
- package/dist/im/lark/mention-targets.js.map +1 -0
- package/dist/im/lark/message-parser.d.ts +31 -0
- package/dist/im/lark/message-parser.d.ts.map +1 -1
- package/dist/im/lark/message-parser.js +305 -32
- package/dist/im/lark/message-parser.js.map +1 -1
- package/dist/im/lark/reply-card-footer-signature.d.ts +7 -0
- package/dist/im/lark/reply-card-footer-signature.d.ts.map +1 -0
- package/dist/im/lark/reply-card-footer-signature.js +7 -0
- package/dist/im/lark/reply-card-footer-signature.js.map +1 -0
- package/dist/im/lark/sessions-card.d.ts.map +1 -1
- package/dist/im/lark/sessions-card.js +4 -1
- package/dist/im/lark/sessions-card.js.map +1 -1
- package/dist/im/lark/substitute-command.d.ts.map +1 -1
- package/dist/im/lark/substitute-command.js +8 -0
- package/dist/im/lark/substitute-command.js.map +1 -1
- package/dist/im/lark/topic-root-context.d.ts +35 -0
- package/dist/im/lark/topic-root-context.d.ts.map +1 -0
- package/dist/im/lark/topic-root-context.js +37 -0
- package/dist/im/lark/topic-root-context.js.map +1 -0
- package/dist/im/lark/v3-blocked-card.d.ts.map +1 -1
- package/dist/im/lark/v3-blocked-card.js +2 -1
- package/dist/im/lark/v3-blocked-card.js.map +1 -1
- package/dist/im/lark/v3-gate-card.d.ts.map +1 -1
- package/dist/im/lark/v3-gate-card.js +2 -1
- package/dist/im/lark/v3-gate-card.js.map +1 -1
- package/dist/im/lark/v3-loop-grant-card.d.ts.map +1 -1
- package/dist/im/lark/v3-loop-grant-card.js +2 -1
- package/dist/im/lark/v3-loop-grant-card.js.map +1 -1
- package/dist/im/lark/v3-progress-card.d.ts.map +1 -1
- package/dist/im/lark/v3-progress-card.js +2 -1
- package/dist/im/lark/v3-progress-card.js.map +1 -1
- package/dist/im/lark/v3-revisit-grant-card.d.ts.map +1 -1
- package/dist/im/lark/v3-revisit-grant-card.js +2 -1
- package/dist/im/lark/v3-revisit-grant-card.js.map +1 -1
- package/dist/index-core-only.d.ts +3 -0
- package/dist/index-core-only.d.ts.map +1 -0
- package/dist/index-core-only.js +107 -0
- package/dist/index-core-only.js.map +1 -0
- package/dist/services/async-trigger-state.d.ts +8 -0
- package/dist/services/async-trigger-state.d.ts.map +1 -1
- package/dist/services/async-trigger-state.js +3 -2
- package/dist/services/async-trigger-state.js.map +1 -1
- package/dist/services/async-trigger-store.d.ts +10 -2
- package/dist/services/async-trigger-store.d.ts.map +1 -1
- package/dist/services/async-trigger-store.js +3 -2
- package/dist/services/async-trigger-store.js.map +1 -1
- package/dist/services/backend-availability.d.ts +3 -1
- package/dist/services/backend-availability.d.ts.map +1 -1
- package/dist/services/backend-availability.js +14 -1
- package/dist/services/backend-availability.js.map +1 -1
- package/dist/services/backend-type-store.d.ts.map +1 -1
- package/dist/services/backend-type-store.js +1 -1
- package/dist/services/backend-type-store.js.map +1 -1
- package/dist/services/bot-config-store.d.ts.map +1 -1
- package/dist/services/bot-config-store.js +2 -1
- package/dist/services/bot-config-store.js.map +1 -1
- package/dist/services/bridge-fallback-gate.d.ts +14 -1
- package/dist/services/bridge-fallback-gate.d.ts.map +1 -1
- package/dist/services/bridge-fallback-gate.js +65 -1
- package/dist/services/bridge-fallback-gate.js.map +1 -1
- package/dist/services/card-prefs-store.d.ts +7 -2
- package/dist/services/card-prefs-store.d.ts.map +1 -1
- package/dist/services/card-prefs-store.js +30 -4
- package/dist/services/card-prefs-store.js.map +1 -1
- package/dist/services/codex-app-runner-protocol.d.ts +9 -0
- package/dist/services/codex-app-runner-protocol.d.ts.map +1 -1
- package/dist/services/codex-app-runner-protocol.js +17 -0
- package/dist/services/codex-app-runner-protocol.js.map +1 -1
- package/dist/services/codex-app-token-usage.d.ts +93 -0
- package/dist/services/codex-app-token-usage.d.ts.map +1 -0
- package/dist/services/codex-app-token-usage.js +190 -0
- package/dist/services/codex-app-token-usage.js.map +1 -0
- package/dist/services/codex-transcript.d.ts +6 -1
- package/dist/services/codex-transcript.d.ts.map +1 -1
- package/dist/services/codex-transcript.js +103 -32
- package/dist/services/codex-transcript.js.map +1 -1
- package/dist/services/federation-roster.d.ts +4 -0
- package/dist/services/federation-roster.d.ts.map +1 -1
- package/dist/services/federation-roster.js +3 -0
- package/dist/services/federation-roster.js.map +1 -1
- package/dist/services/federation-store.d.ts +6 -0
- package/dist/services/federation-store.d.ts.map +1 -1
- package/dist/services/federation-store.js.map +1 -1
- package/dist/services/grant-policy.d.ts +13 -0
- package/dist/services/grant-policy.d.ts.map +1 -0
- package/dist/services/grant-policy.js +40 -0
- package/dist/services/grant-policy.js.map +1 -0
- package/dist/services/grant-store.d.ts +24 -3
- package/dist/services/grant-store.d.ts.map +1 -1
- package/dist/services/grant-store.js +196 -12
- package/dist/services/grant-store.js.map +1 -1
- package/dist/services/groups-store.d.ts +36 -0
- package/dist/services/groups-store.d.ts.map +1 -1
- package/dist/services/groups-store.js +123 -1
- package/dist/services/groups-store.js.map +1 -1
- package/dist/services/jsonl-cursor.d.ts.map +1 -1
- package/dist/services/jsonl-cursor.js +8 -2
- package/dist/services/jsonl-cursor.js.map +1 -1
- package/dist/services/local-cli-opener.d.ts.map +1 -1
- package/dist/services/local-cli-opener.js +32 -0
- package/dist/services/local-cli-opener.js.map +1 -1
- package/dist/services/message-listener-run-preview-store.d.ts +43 -0
- package/dist/services/message-listener-run-preview-store.d.ts.map +1 -0
- package/dist/services/message-listener-run-preview-store.js +152 -0
- package/dist/services/message-listener-run-preview-store.js.map +1 -0
- package/dist/services/message-listener-store.d.ts +26 -0
- package/dist/services/message-listener-store.d.ts.map +1 -0
- package/dist/services/message-listener-store.js +138 -0
- package/dist/services/message-listener-store.js.map +1 -0
- package/dist/services/message-listener.d.ts +112 -0
- package/dist/services/message-listener.d.ts.map +1 -0
- package/dist/services/message-listener.js +391 -0
- package/dist/services/message-listener.js.map +1 -0
- package/dist/services/overload-alert-migration.d.ts +15 -0
- package/dist/services/overload-alert-migration.d.ts.map +1 -0
- package/dist/services/overload-alert-migration.js +115 -0
- package/dist/services/overload-alert-migration.js.map +1 -0
- package/dist/services/send-policy.d.ts +38 -0
- package/dist/services/send-policy.d.ts.map +1 -1
- package/dist/services/send-policy.js +34 -0
- package/dist/services/send-policy.js.map +1 -1
- package/dist/services/session-store.d.ts +12 -1
- package/dist/services/session-store.d.ts.map +1 -1
- package/dist/services/session-store.js +44 -2
- package/dist/services/session-store.js.map +1 -1
- package/dist/services/skill-registry-store.d.ts.map +1 -1
- package/dist/services/skill-registry-store.js +87 -12
- package/dist/services/skill-registry-store.js.map +1 -1
- package/dist/services/substitute-mode-normalize.d.ts +3 -0
- package/dist/services/substitute-mode-normalize.d.ts.map +1 -1
- package/dist/services/substitute-mode-normalize.js +8 -0
- package/dist/services/substitute-mode-normalize.js.map +1 -1
- package/dist/services/substitute-mode-store.d.ts.map +1 -1
- package/dist/services/substitute-mode-store.js +8 -1
- package/dist/services/substitute-mode-store.js.map +1 -1
- package/dist/services/transcript-resolver.d.ts +6 -1
- package/dist/services/transcript-resolver.d.ts.map +1 -1
- package/dist/services/transcript-resolver.js +145 -29
- package/dist/services/transcript-resolver.js.map +1 -1
- package/dist/services/trigger-types.d.ts +16 -0
- package/dist/services/trigger-types.d.ts.map +1 -1
- package/dist/services/trigger-types.js +6 -0
- package/dist/services/trigger-types.js.map +1 -1
- package/dist/setup/bot-config-editor.js +2 -2
- package/dist/setup/bot-config-editor.js.map +1 -1
- package/dist/setup/ensure-zmx.d.ts +31 -0
- package/dist/setup/ensure-zmx.d.ts.map +1 -0
- package/dist/setup/ensure-zmx.js +161 -0
- package/dist/setup/ensure-zmx.js.map +1 -0
- package/dist/setup/index.d.ts +1 -1
- package/dist/setup/index.js +1 -1
- package/dist/setup/open-platform-automation.d.ts +9 -0
- package/dist/setup/open-platform-automation.d.ts.map +1 -1
- package/dist/setup/open-platform-automation.js +15 -2
- package/dist/setup/open-platform-automation.js.map +1 -1
- package/dist/setup/setup-args.d.ts +1 -1
- package/dist/setup/setup-args.d.ts.map +1 -1
- package/dist/setup/setup-args.js +1 -1
- package/dist/skills/definitions.d.ts.map +1 -1
- package/dist/skills/definitions.js +32 -0
- package/dist/skills/definitions.js.map +1 -1
- package/dist/skills/injection-mode.d.ts +6 -2
- package/dist/skills/injection-mode.d.ts.map +1 -1
- package/dist/skills/injection-mode.js +10 -5
- package/dist/skills/injection-mode.js.map +1 -1
- package/dist/types.d.ts +79 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/child-env.d.ts +1 -1
- package/dist/utils/child-env.d.ts.map +1 -1
- package/dist/utils/child-env.js +4 -0
- package/dist/utils/child-env.js.map +1 -1
- package/dist/utils/effort-confirm-dialog.d.ts +40 -0
- package/dist/utils/effort-confirm-dialog.d.ts.map +1 -0
- package/dist/utils/effort-confirm-dialog.js +74 -0
- package/dist/utils/effort-confirm-dialog.js.map +1 -0
- package/dist/utils/pending-input-queue.d.ts +56 -0
- package/dist/utils/pending-input-queue.d.ts.map +1 -1
- package/dist/utils/pending-input-queue.js +80 -0
- package/dist/utils/pending-input-queue.js.map +1 -1
- package/dist/utils/render-dimensions.d.ts +8 -0
- package/dist/utils/render-dimensions.d.ts.map +1 -1
- package/dist/utils/render-dimensions.js +10 -0
- package/dist/utils/render-dimensions.js.map +1 -1
- package/dist/utils/stuck-key-guard.d.ts +3 -2
- package/dist/utils/stuck-key-guard.d.ts.map +1 -1
- package/dist/utils/stuck-key-guard.js +3 -3
- package/dist/utils/stuck-key-guard.js.map +1 -1
- package/dist/utils/terminal-renderer.d.ts +6 -0
- package/dist/utils/terminal-renderer.d.ts.map +1 -1
- package/dist/utils/terminal-renderer.js +15 -0
- package/dist/utils/terminal-renderer.js.map +1 -1
- package/dist/utils/tui-input-delivery.d.ts +32 -0
- package/dist/utils/tui-input-delivery.d.ts.map +1 -0
- package/dist/utils/tui-input-delivery.js +53 -0
- package/dist/utils/tui-input-delivery.js.map +1 -0
- package/dist/utils/xml.d.ts +19 -0
- package/dist/utils/xml.d.ts.map +1 -0
- package/dist/utils/xml.js +26 -0
- package/dist/utils/xml.js.map +1 -0
- package/dist/worker.js +1852 -374
- package/dist/worker.js.map +1 -1
- package/dist/workflows/definition.d.ts +8 -8
- package/dist/workflows/events/payloads.d.ts +8 -8
- package/dist/workflows/events/schema.d.ts +160 -160
- package/dist/workflows/v3/session-relay-client.js +1 -1
- package/dist/workflows/v3/session-relay-client.js.map +1 -1
- package/dist/workflows/v3/worker-fence.d.ts.map +1 -1
- package/dist/workflows/v3/worker-fence.js +15 -10
- package/dist/workflows/v3/worker-fence.js.map +1 -1
- package/package.json +1 -1
- package/dist/dashboard-web/chunks/bot-defaults-page-UKA7K4JV.js +0 -10
- package/dist/dashboard-web/chunks/chunk-DFLNXQNT.js +0 -1
- package/dist/dashboard-web/chunks/connectors-page-A77YAB5Y.js +0 -1
- package/dist/dashboard-web/chunks/groups-page-RUZNVXQB.js +0 -8
- package/dist/dashboard-web/chunks/insights-page-RF7U7RTA.js +0 -22
- package/dist/dashboard-web/chunks/roles-page-BUMKD7Y4.js +0 -1
- package/dist/dashboard-web/chunks/sessions-page-6HLSEAB7.js +0 -2
- package/dist/dashboard-web/chunks/settings-page-MJDZHQ4X.js +0 -2
|
@@ -9,6 +9,7 @@ import { V3_SESSION_RUN_MUTATION_ROUTE_PREFIX } from '../workflows/v3/session-re
|
|
|
9
9
|
import { listenWithProbe } from '../utils/listen-with-probe.js';
|
|
10
10
|
import { dashboardSecretPath } from './dashboard-secret.js';
|
|
11
11
|
import * as sessionStore from '../services/session-store.js';
|
|
12
|
+
import { cliSupportsNativeUsage } from '../services/transcript-resolver.js';
|
|
12
13
|
import * as asyncTriggerStore from '../services/async-trigger-store.js';
|
|
13
14
|
import { resolveAsyncTriggerState, decideAsyncOwnership } from '../services/async-trigger-state.js';
|
|
14
15
|
import * as scheduleStore from '../services/schedule-store.js';
|
|
@@ -68,11 +69,12 @@ import { readGlobalConfig } from '../global-config.js';
|
|
|
68
69
|
import { normalizeChatReplyMode, setChatReplyMode } from '../services/chat-reply-mode-store.js';
|
|
69
70
|
import * as chatFirstSeenStore from '../services/chat-first-seen-store.js';
|
|
70
71
|
import * as scheduler from './scheduler.js';
|
|
71
|
-
import { listActiveSessions, findActiveBySessionId, closeSession, getActiveSessionsRegistry, transferSession, deliverWriteLinkCardToOwners, forkWorker, suspendWorker, killWorker } from './worker-pool.js';
|
|
72
|
+
import { listActiveSessions, findActiveBySessionId, closeSession, getActiveSessionsRegistry, transferSession, deliverWriteLinkCardToOwners, forkWorker, suspendWorker, killWorker, latestPerBotEnvForRestart, getDaemonReplyCardUsageSnapshot, sessionSupportsWebTerminal, sendWorkerSessionInput, isSessionTransferring } from './worker-pool.js';
|
|
72
73
|
import { listOnlineDaemons } from '../utils/daemon-discovery.js';
|
|
73
74
|
import { isSessionStopped } from './session-liveness.js';
|
|
74
|
-
import {
|
|
75
|
-
import {
|
|
75
|
+
import { isSuspendableBackendType } from './persistent-backend.js';
|
|
76
|
+
import { getChatMode, replyMessage, sendMessage, resolveUnionIdFromOpenId, listThreadMessages, listChatMessages, listChatMessagesUntil, listChatBotMembers, getUserProfile, getUserProfileStrict, resolveAllowedUsersWithMap } from '../im/lark/client.js';
|
|
77
|
+
import { parseApiMessage, cardContentHasUpgradeFallback, resolveMergedCardContent, messageMentionsBot } from '../im/lark/message-parser.js';
|
|
76
78
|
import { resumeSession, spawnDashboardSession, activateQueuedSession, closeCliMismatchedSessionsForBot, suspendActiveSessionsForBot } from './session-manager.js';
|
|
77
79
|
import { parseSpawnRequest } from './session-create.js';
|
|
78
80
|
import { cleanupMaterializedDashboardImages, materializeDashboardImages } from './dashboard-images.js';
|
|
@@ -89,6 +91,11 @@ import { resolveCliSelection, selectionKeyForBot } from '../setup/cli-selection.
|
|
|
89
91
|
import { checkCliAvailability } from '../setup/cli-availability.js';
|
|
90
92
|
import { enrichHistorySenders } from '../dashboard/history-senders.js';
|
|
91
93
|
import { listPendingAsks, submitAskFromDesktop } from './ask-broker.js';
|
|
94
|
+
import { getMessageListenerConfig, sanitizeMessageListenerUpdate, updateMessageListenerConfig, validateMessageListenerUpdate } from '../services/message-listener-store.js';
|
|
95
|
+
import { MAX_MESSAGE_LISTENER_PROMPT_BYTES, normalizeMessageListenerPreviewLimit, previewMessageListenerMatches, buildListenerBotAppIdToOpenId, collectListenerBotAppIds, renderMessageListenerInstruction, } from '../services/message-listener.js';
|
|
96
|
+
import { createMessageListenerRunPreview, createMessageListenerRunPreviewTurnId, getMessageListenerRunPreview, markMessageListenerRunPreviewFailed, markMessageListenerRunPreviewTriggered, } from '../services/message-listener-run-preview-store.js';
|
|
97
|
+
import { listChatMemberDisplays } from '../services/groups-store.js';
|
|
98
|
+
const MESSAGE_LISTENER_PREVIEW_WINDOW_MS = 24 * 60 * 60 * 1000;
|
|
92
99
|
let exactChatGrantHandler = applyExactChatGrantRequest;
|
|
93
100
|
/** Test seam: replace the exact-grant service without touching live Feishu/config state. */
|
|
94
101
|
export function setExactChatGrantHandler(handler) {
|
|
@@ -102,16 +109,17 @@ let botAvatarChanger = null;
|
|
|
102
109
|
export function setBotAvatarChanger(fn) {
|
|
103
110
|
botAvatarChanger = fn;
|
|
104
111
|
}
|
|
105
|
-
import { composeRowFromActive, composeRowFromClosed, feishuChatLink, setBotName as setRowsBotName, getBotName, } from './dashboard-rows.js';
|
|
106
|
-
import { getBotBrand, getBot, loadBotConfigs, readBotSkillPolicy, getBotTuiSlashAllow } from '../bot-registry.js';
|
|
112
|
+
import { composeRowFromActive, composeRowFromClosed, composeRowFromPersistedActive, feishuChatLink, setBotName as setRowsBotName, getBotName, } from './dashboard-rows.js';
|
|
113
|
+
import { getBotBrand, getBot, getBotOpenId, loadBotConfigs, readBotSkillPolicy, getBotTuiSlashAllow } from '../bot-registry.js';
|
|
107
114
|
import { normalizeKanbanColumn, normalizeKanbanPosition, normalizeSessionTitle } from './session-board.js';
|
|
108
115
|
import { validateSlashInjection } from './slash-inject.js';
|
|
109
116
|
import { validateRoleLibraryPath } from './role-library.js';
|
|
110
117
|
import { repinSessionWorkingDir } from './session-cwd.js';
|
|
111
118
|
import { authorizeSessionScopedIpc } from './daemon-ipc-session-auth.js';
|
|
112
|
-
import { updateSessionTitle } from './session-title.js';
|
|
119
|
+
import { normalizeSessionTitleSource, updateSessionTitle } from './session-title.js';
|
|
113
120
|
import { requestAgentSessionRename } from './session-rename.js';
|
|
114
|
-
import {
|
|
121
|
+
import { ChatRenameCooldown, ChatRenameSerialQueue, normalizeLarkChatName } from './chat-rename.js';
|
|
122
|
+
import { sessionAnchorId, larkTransportEnabled } from './types.js';
|
|
115
123
|
import { attachSkillPolicy, detachSkillPolicy } from './skills/im-command.js';
|
|
116
124
|
import { readSkillRegistry } from '../services/skill-registry-store.js';
|
|
117
125
|
import { commitDeviceIsolationActivation, DEVICE_ISOLATION_COMMIT_PATH, DEVICE_ISOLATION_PREPARE_PATH, DEVICE_ISOLATION_RELEASE_PATH, logDeviceIsolationActivationError, prepareDeviceIsolationActivation, releaseDeviceIsolationActivation, } from './device-isolation-daemon.js';
|
|
@@ -318,8 +326,32 @@ function tokenRouteAuthorized(req, bind) {
|
|
|
318
326
|
return ipcHmacAuthorized(req, bind);
|
|
319
327
|
}
|
|
320
328
|
function routeHasPublicAccess(method, pathname) {
|
|
321
|
-
// Liveness contains no data and performs no mutation.
|
|
322
|
-
|
|
329
|
+
// Liveness contains no data and performs no mutation. /healthz is the
|
|
330
|
+
// core-only public alias of /__health (riff's sandbox launcher polls it).
|
|
331
|
+
return method === 'GET' && (pathname === '/__health' || pathname === '/healthz');
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Core-only ONLY: the exact riff-facing routes that bypass the trusted-host HMAC
|
|
335
|
+
* when the daemon runs headless in riff's sandbox. Everything else STILL requires
|
|
336
|
+
* the HMAC (codex P1: authRequired:false opened all 96 IPC routes — a co-resident
|
|
337
|
+
* model turn could read/perturb sessions, scheduler, mutations). This is a tight
|
|
338
|
+
* allowlist of drive-my-own-turn + poll-my-own-output surfaces:
|
|
339
|
+
* POST /api/trigger (start a turn)
|
|
340
|
+
* GET /api/sessions/:id/trigger-result (poll final)
|
|
341
|
+
* GET /api/sessions/:id/insight (poll conversation/progress)
|
|
342
|
+
* `/api/asks/answer` is deliberately EXCLUDED — it is askId-keyed with no
|
|
343
|
+
* session/turn binding, so exposing it would let any co-resident turn hijack
|
|
344
|
+
* another pending ask (codex). riff's async main-link needs no awaiting_input;
|
|
345
|
+
* a future clarify path must be a sessionId+interaction-bound endpoint.
|
|
346
|
+
*/
|
|
347
|
+
function routeIsCoreOnlyPublic(method, pathname) {
|
|
348
|
+
if (method === 'POST' && pathname === '/api/trigger')
|
|
349
|
+
return true;
|
|
350
|
+
if (method === 'GET') {
|
|
351
|
+
return /^\/api\/sessions\/[^/]+\/trigger-result$/.test(pathname)
|
|
352
|
+
|| /^\/api\/sessions\/[^/]+\/insight$/.test(pathname);
|
|
353
|
+
}
|
|
354
|
+
return false;
|
|
323
355
|
}
|
|
324
356
|
function routeHasNarrowUntrustedAuth(method, pathname) {
|
|
325
357
|
// The receiver action endpoint performs its own rotating worker-capability
|
|
@@ -340,7 +372,7 @@ function routeHasNarrowUntrustedAuth(method, pathname) {
|
|
|
340
372
|
// 该会话的 rotating per-turn
|
|
341
373
|
// capability 并绑定到 URL 里的 sessionId(同 /api/asks 姿势)——capability 只
|
|
342
374
|
// 证明「我是这个会话当前这一轮的 CLI」,选不了别的会话。
|
|
343
|
-
if (method === 'POST' && /^\/api\/sessions\/[^/]+\/(?:slash|cd|close)$/.test(pathname))
|
|
375
|
+
if (method === 'POST' && /^\/api\/sessions\/[^/]+\/(?:slash|cd|close|chat-rename)$/.test(pathname))
|
|
344
376
|
return true;
|
|
345
377
|
if (method === 'POST' && pathname === '/api/hooks/emit')
|
|
346
378
|
return true;
|
|
@@ -378,12 +410,50 @@ function trustedHostAuthorized(req, pathname, port, secret) {
|
|
|
378
410
|
ipcRoute('GET', '/__health', (_req, res) => {
|
|
379
411
|
jsonRes(res, 200, { ok: true });
|
|
380
412
|
});
|
|
381
|
-
|
|
413
|
+
// Core-only readiness barrier (codex P1-3): the daemon binds its HTTP port BEFORE
|
|
414
|
+
// restoreActiveSessions / v3 cold-attach / scheduler finish, so a launcher that
|
|
415
|
+
// triggers the instant the port answers would race durable restore (transient
|
|
416
|
+
// not_found / re-fire). /healthz returns 503 until the daemon marks itself ready
|
|
417
|
+
// (setCoreOnlyReady, called AFTER restore in daemon.ts). Non-core-only daemons
|
|
418
|
+
// never set this gate, so /healthz stays an unconditional 200 there.
|
|
419
|
+
let coreOnlyReadinessGate = false; // true only in core-only, until ready
|
|
420
|
+
let coreOnlyReady = false;
|
|
421
|
+
export function armCoreOnlyReadinessGate() { coreOnlyReadinessGate = true; }
|
|
422
|
+
export function setCoreOnlyReady() { coreOnlyReady = true; }
|
|
423
|
+
/** @internal test-only: reset the core-only readiness gate between cases. */
|
|
424
|
+
export function __testOnly_resetCoreOnlyReadiness() { coreOnlyReadinessGate = false; coreOnlyReady = false; }
|
|
425
|
+
/** True when the readiness gate is armed (core-only) but restore hasn't finished.
|
|
426
|
+
* The server-level gate returns 503 for the public control routes in this state,
|
|
427
|
+
* and /healthz reports 'starting' — a barrier so riff can't trigger into a racing
|
|
428
|
+
* durable restore even if it skips the healthz probe (codex P1). */
|
|
429
|
+
function coreOnlyNotReady() { return coreOnlyReadinessGate && !coreOnlyReady; }
|
|
430
|
+
// Public alias for core-only: riff's sandbox launcher polls GET /healthz to know
|
|
431
|
+
// the service is FULLY up (bound AND restore-complete). 200 {ok:true} once ready;
|
|
432
|
+
// 503 {ok:false,status:'starting'} while the readiness gate is armed but not ready.
|
|
433
|
+
ipcRoute('GET', '/healthz', (_req, res) => {
|
|
434
|
+
if (coreOnlyNotReady()) {
|
|
435
|
+
return jsonRes(res, 503, { ok: false, status: 'starting' });
|
|
436
|
+
}
|
|
437
|
+
jsonRes(res, 200, { ok: true });
|
|
438
|
+
});
|
|
439
|
+
export { composeRowFromActive, composeRowFromClosed, composeRowFromPersistedActive };
|
|
382
440
|
// Re-export setBotName for backwards-compatible imports (daemon.ts). Both
|
|
383
441
|
// callers (this module's cachedBotName + dashboard-rows' cachedBotName) need
|
|
384
442
|
// to be primed; here we forward to the rows module which is the canonical
|
|
385
443
|
// holder.
|
|
386
444
|
export function setBotName(name) { setRowsBotName(name); }
|
|
445
|
+
function composeDashboardSessionRows() {
|
|
446
|
+
const active = listActiveSessions().map(composeRowFromActive);
|
|
447
|
+
const activeIds = new Set(active.map(row => row.sessionId));
|
|
448
|
+
const persisted = sessionStore.listSessions();
|
|
449
|
+
const unregisteredActive = persisted
|
|
450
|
+
.filter(session => session.status === 'active' && !activeIds.has(session.sessionId))
|
|
451
|
+
.map(composeRowFromPersistedActive);
|
|
452
|
+
const closed = persisted
|
|
453
|
+
.filter(session => session.status === 'closed' && !activeIds.has(session.sessionId))
|
|
454
|
+
.map(composeRowFromClosed);
|
|
455
|
+
return [...active, ...unregisteredActive, ...closed];
|
|
456
|
+
}
|
|
387
457
|
// The daemon's own larkAppId, primed at startup. Required for the groups
|
|
388
458
|
// endpoints below which proxy calls into groups-store on this bot's behalf.
|
|
389
459
|
let cachedLarkAppId = '';
|
|
@@ -450,23 +520,35 @@ ipcRoute('POST', '/api/asks/answer', async (req, res) => {
|
|
|
450
520
|
return jsonRes(res, 200, { ok: true, outcome });
|
|
451
521
|
});
|
|
452
522
|
ipcRoute('GET', '/api/sessions', (_req, res) => {
|
|
453
|
-
//
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
.filter(s => s.status === 'closed' && !activeIds.has(s.sessionId))
|
|
458
|
-
.map(composeRowFromClosed);
|
|
459
|
-
jsonRes(res, 200, { sessions: [...active, ...closed] });
|
|
523
|
+
// Runtime active first, then persisted active rows that restore deliberately
|
|
524
|
+
// left detached, then closed history. Persisted-active must never be projected
|
|
525
|
+
// through composeRowFromClosed: teardown uncertainty is not a close.
|
|
526
|
+
jsonRes(res, 200, { sessions: composeDashboardSessionRows() });
|
|
460
527
|
});
|
|
461
528
|
ipcRoute('GET', '/api/sessions/:sessionId', (_req, res, params) => {
|
|
462
529
|
const ds = findActiveBySessionId(params.sessionId);
|
|
463
530
|
if (ds)
|
|
464
531
|
return jsonRes(res, 200, { session: composeRowFromActive(ds) });
|
|
465
|
-
const
|
|
466
|
-
if (
|
|
467
|
-
return jsonRes(res, 200, {
|
|
532
|
+
const persisted = sessionStore.listSessions().find(s => s.sessionId === params.sessionId);
|
|
533
|
+
if (persisted) {
|
|
534
|
+
return jsonRes(res, 200, {
|
|
535
|
+
session: persisted.status === 'active'
|
|
536
|
+
? composeRowFromPersistedActive(persisted)
|
|
537
|
+
: composeRowFromClosed(persisted),
|
|
538
|
+
});
|
|
539
|
+
}
|
|
468
540
|
jsonRes(res, 404, { error: 'not_found' });
|
|
469
541
|
});
|
|
542
|
+
/** Low-frequency card-display read used by `botmux send`. Keeping the
|
|
543
|
+
* transcript reader and per-bot visibility decision in the resident daemon
|
|
544
|
+
* preserves its incremental cache and live config instead of making every
|
|
545
|
+
* short-lived CLI process rescan the Session or guess from sandboxed files. */
|
|
546
|
+
ipcRoute('GET', '/api/sessions/:sessionId/usage', (_req, res, params) => {
|
|
547
|
+
const ds = findActiveBySessionId(params.sessionId);
|
|
548
|
+
if (!ds)
|
|
549
|
+
return jsonRes(res, 404, { error: 'not_found' });
|
|
550
|
+
jsonRes(res, 200, { usage: getDaemonReplyCardUsageSnapshot(ds) });
|
|
551
|
+
});
|
|
470
552
|
/** Canonical daemon-side close used by the dashboard and `botmux delete`.
|
|
471
553
|
* Host callers authenticate with HMAC; a read-isolated CLI may close only its
|
|
472
554
|
* exact live session with the rotating per-turn capability. */
|
|
@@ -489,6 +571,11 @@ ipcRoute('POST', '/api/sessions/:sessionId/close', async (req, res, params) => {
|
|
|
489
571
|
function postRestartNotice(ds, fresh) {
|
|
490
572
|
if (!ds.larkAppId)
|
|
491
573
|
return;
|
|
574
|
+
// No-transport session (apiOnly bot or HTTP virtual chat) has no Feishu chat
|
|
575
|
+
// to post a restart notice into — skip (the raw sendMessage/replyMessage below
|
|
576
|
+
// bypass sessionReply's gate). Best-effort path; a silent skip is correct.
|
|
577
|
+
if (!larkTransportEnabled({ chatId: ds.chatId, apiOnly: getBot(ds.larkAppId).config.apiOnly }))
|
|
578
|
+
return;
|
|
492
579
|
const loc = localeForBot(ds.larkAppId);
|
|
493
580
|
const cliName = getCliDisplayName(ds.session.cliId ?? 'claude-code');
|
|
494
581
|
const text = fresh
|
|
@@ -511,6 +598,9 @@ ipcRoute('POST', '/api/sessions/:sessionId/restart', (_req, res, params) => {
|
|
|
511
598
|
const ds = findActiveBySessionId(params.sessionId);
|
|
512
599
|
if (!ds)
|
|
513
600
|
return jsonRes(res, 404, { ok: false, error: 'session_not_active' });
|
|
601
|
+
if (isSessionTransferring(ds)) {
|
|
602
|
+
return jsonRes(res, 409, { ok: false, error: 'session_transferring' });
|
|
603
|
+
}
|
|
514
604
|
// Adopt/observed sessions: botmux never owned the CLI — restarting would kill
|
|
515
605
|
// the user's real tmux/zellij pane. Hard-reject (the worker self-guards too).
|
|
516
606
|
if (ds.adoptedFrom || ds.initConfig?.adoptMode) {
|
|
@@ -519,8 +609,10 @@ ipcRoute('POST', '/api/sessions/:sessionId/restart', (_req, res, params) => {
|
|
|
519
609
|
const cliId = ds.session.cliId ?? 'unknown';
|
|
520
610
|
if (ds.worker && !ds.worker.killed) {
|
|
521
611
|
// Live worker → in-place CLI restart (kills the CLI, respawns with --resume).
|
|
612
|
+
// 捎带最新 per-bot env:dashboard 改完 env 后重启才真正生效(与 /restart 同逻辑)。
|
|
522
613
|
try {
|
|
523
|
-
ds.
|
|
614
|
+
ds.workerReady = false;
|
|
615
|
+
ds.worker.send({ type: 'restart', env: latestPerBotEnvForRestart(ds) });
|
|
524
616
|
}
|
|
525
617
|
catch (err) {
|
|
526
618
|
return jsonRes(res, 502, { ok: false, error: String(err) });
|
|
@@ -546,6 +638,9 @@ ipcRoute('POST', '/api/sessions/:sessionId/suspend', (_req, res, params) => {
|
|
|
546
638
|
const ds = findActiveBySessionId(params.sessionId);
|
|
547
639
|
if (!ds)
|
|
548
640
|
return jsonRes(res, 404, { ok: false, error: 'session_not_active' });
|
|
641
|
+
if (isSessionTransferring(ds)) {
|
|
642
|
+
return jsonRes(res, 409, { ok: false, error: 'session_transferring' });
|
|
643
|
+
}
|
|
549
644
|
// Adopt/observed sessions: botmux never owned the CLI — suspending would kill
|
|
550
645
|
// the user's real tmux/zellij pane. Same guard as /restart.
|
|
551
646
|
if (ds.adoptedFrom || ds.initConfig?.adoptMode) {
|
|
@@ -565,11 +660,10 @@ ipcRoute('POST', '/api/sessions/:sessionId/suspend', (_req, res, params) => {
|
|
|
565
660
|
});
|
|
566
661
|
/**
|
|
567
662
|
* Count host-overload降压 candidates for THIS daemon's scope, so the alert card
|
|
568
|
-
* can show "僵尸 N / 闲置 M" before the owner clicks.
|
|
569
|
-
*
|
|
570
|
-
*
|
|
571
|
-
*
|
|
572
|
-
* exact classification the sweep uses so the preview matches what a click does.
|
|
663
|
+
* can show "僵尸 N / 闲置 M" before the owner clicks. Both counts are local to
|
|
664
|
+
* THIS daemon: its session store is bot-scoped, and live workers only exist in
|
|
665
|
+
* their owning process. The card handler sums every daemon's response. Mirrors
|
|
666
|
+
* the exact classification the sweep uses so the preview matches a click.
|
|
573
667
|
*/
|
|
574
668
|
ipcRoute('GET', '/api/host-overload/counts', (_req, res) => {
|
|
575
669
|
const stopped = sessionStore.listSessions().filter(s => s.status === 'active' && isSessionStopped(s)).length;
|
|
@@ -579,6 +673,8 @@ ipcRoute('GET', '/api/host-overload/counts', (_req, res) => {
|
|
|
579
673
|
continue;
|
|
580
674
|
if (ds.adoptedFrom || ds.initConfig?.adoptMode)
|
|
581
675
|
continue;
|
|
676
|
+
if (!isSuspendableBackendType(ds.initConfig?.backendType))
|
|
677
|
+
continue;
|
|
582
678
|
if (ds.lastScreenStatus !== 'idle')
|
|
583
679
|
continue;
|
|
584
680
|
idle++;
|
|
@@ -588,9 +684,9 @@ ipcRoute('GET', '/api/host-overload/counts', (_req, res) => {
|
|
|
588
684
|
/**
|
|
589
685
|
* Bulk host-overload降压 sweep, driven by the overload-alert card buttons.
|
|
590
686
|
* `mode`:
|
|
591
|
-
* - `clean_stopped`: close stopped zombie sessions (dead CLI + no
|
|
592
|
-
*
|
|
593
|
-
*
|
|
687
|
+
* - `clean_stopped`: close stopped zombie sessions (dead CLI + no exact
|
|
688
|
+
* persistent backing) from THIS daemon's bot-scoped session store. The
|
|
689
|
+
* card handler fans this mode out to every online daemon.
|
|
594
690
|
* - `suspend_idle`: suspend THIS daemon's own idle (non-busy, suspendable,
|
|
595
691
|
* non-adopt) live workers. Live workers only exist in their owning daemon's
|
|
596
692
|
* process, so the card handler fans this mode out to every online daemon.
|
|
@@ -611,10 +707,8 @@ ipcRoute('POST', '/api/host-overload/sweep', async (req, res) => {
|
|
|
611
707
|
for (const s of stopped) {
|
|
612
708
|
try {
|
|
613
709
|
const r = await closeSession(s.sessionId);
|
|
614
|
-
// Only count sessions this call actually closed. A
|
|
615
|
-
//
|
|
616
|
-
// alreadyClosed=true — counting it would inflate `affected` by the
|
|
617
|
-
// number of daemons that raced on the same zombie.
|
|
710
|
+
// Only count sessions this call actually closed. A concurrent action in
|
|
711
|
+
// this daemon may already have closed the same record.
|
|
618
712
|
if (r.ok && !r.alreadyClosed)
|
|
619
713
|
affected++;
|
|
620
714
|
}
|
|
@@ -634,6 +728,8 @@ ipcRoute('POST', '/api/host-overload/sweep', async (req, res) => {
|
|
|
634
728
|
continue; // no live worker
|
|
635
729
|
if (ds.adoptedFrom || ds.initConfig?.adoptMode)
|
|
636
730
|
continue; // never suspend adopt
|
|
731
|
+
if (!isSuspendableBackendType(ds.initConfig?.backendType))
|
|
732
|
+
continue;
|
|
637
733
|
if (ds.lastScreenStatus !== 'idle')
|
|
638
734
|
continue; // never cut an in-flight reply
|
|
639
735
|
try {
|
|
@@ -692,14 +788,17 @@ ipcRoute('POST', '/api/sessions/:sessionId/slash', async (req, res, params) => {
|
|
|
692
788
|
if (ds.adoptedFrom || ds.initConfig?.adoptMode) {
|
|
693
789
|
return jsonRes(res, 409, { ok: false, error: 'adopt_inject_unsupported' });
|
|
694
790
|
}
|
|
695
|
-
if (!ds.worker || ds.worker.killed)
|
|
791
|
+
if ((!ds.worker || ds.worker.killed) && !isSessionTransferring(ds)) {
|
|
696
792
|
return jsonRes(res, 409, { ok: false, error: 'no_live_worker' });
|
|
793
|
+
}
|
|
697
794
|
const allow = getBotTuiSlashAllow(ds.larkAppId);
|
|
698
795
|
const v = validateSlashInjection(body?.command ?? '', allow);
|
|
699
796
|
if (!v.ok)
|
|
700
797
|
return jsonRes(res, 403, { ok: false, error: v.error });
|
|
701
798
|
try {
|
|
702
|
-
ds
|
|
799
|
+
if (!sendWorkerSessionInput(ds, { type: 'inject_command', command: v.command })) {
|
|
800
|
+
return jsonRes(res, 409, { ok: false, error: 'no_live_worker' });
|
|
801
|
+
}
|
|
703
802
|
}
|
|
704
803
|
catch {
|
|
705
804
|
// slash 注入无状态(不像 /cd 那样已 repin 记录),send 失败不需要杀进程
|
|
@@ -708,6 +807,76 @@ ipcRoute('POST', '/api/sessions/:sessionId/slash', async (req, res, params) => {
|
|
|
708
807
|
}
|
|
709
808
|
jsonRes(res, 200, { ok: true, sessionId: params.sessionId, queued: v.command });
|
|
710
809
|
});
|
|
810
|
+
const proactiveChatRenameCooldown = new ChatRenameCooldown();
|
|
811
|
+
const chatRenameSerialQueue = new ChatRenameSerialQueue();
|
|
812
|
+
/** Session-scoped external mutation used by the botmux-chat-rename Skill. */
|
|
813
|
+
ipcRoute('POST', '/api/sessions/:sessionId/chat-rename', async (req, res, params) => {
|
|
814
|
+
const body = await readJsonBody(req)
|
|
815
|
+
.catch(() => ({}));
|
|
816
|
+
const ds = findActiveBySessionId(params.sessionId);
|
|
817
|
+
const auth = sessionCliIpcAuth(req, ds, params.sessionId, body);
|
|
818
|
+
if (!auth.ok)
|
|
819
|
+
return jsonRes(res, 403, { ok: false, error: auth.error });
|
|
820
|
+
if (!ds)
|
|
821
|
+
return jsonRes(res, 404, { ok: false, error: 'session_not_active' });
|
|
822
|
+
if (sessionTransportDisabled(ds))
|
|
823
|
+
return jsonRes(res, 200, { ok: false, error: 'no_feishu_transport' });
|
|
824
|
+
if (ds.chatType !== 'group')
|
|
825
|
+
return jsonRes(res, 400, { ok: false, error: 'not_group_chat' });
|
|
826
|
+
const normalized = normalizeLarkChatName(body.name);
|
|
827
|
+
if (!normalized.ok)
|
|
828
|
+
return jsonRes(res, 400, normalized);
|
|
829
|
+
const proactive = body.proactive === true;
|
|
830
|
+
const trigger = proactive ? 'ai_proactive' : 'user_explicit';
|
|
831
|
+
const cooldownKey = `${ds.larkAppId}:${ds.chatId}`;
|
|
832
|
+
await chatRenameSerialQueue.run(cooldownKey, async () => {
|
|
833
|
+
const result = await groupsStore.renameChat(ds.larkAppId, ds.chatId, normalized.name, {
|
|
834
|
+
beforeUpdate: proactive
|
|
835
|
+
? () => {
|
|
836
|
+
const cooldown = proactiveChatRenameCooldown.check(cooldownKey);
|
|
837
|
+
return cooldown.ok
|
|
838
|
+
? cooldown
|
|
839
|
+
: { ...cooldown, error: 'rate_limited' };
|
|
840
|
+
}
|
|
841
|
+
: undefined,
|
|
842
|
+
});
|
|
843
|
+
const botOpenId = getBotOpenId(ds.larkAppId) ?? '-';
|
|
844
|
+
if (!result.ok) {
|
|
845
|
+
const status = result.error === 'bot_not_in_chat' ? 403
|
|
846
|
+
: result.error === 'permission_denied' ? 403
|
|
847
|
+
: result.error === 'rate_limited' ? 429
|
|
848
|
+
: 502;
|
|
849
|
+
logger.warn(`[chat-rename:audit] result=failed session=${ds.session.sessionId} chat=${ds.chatId} `
|
|
850
|
+
+ `app=${ds.larkAppId} botOpenId=${botOpenId} trigger=${trigger} `
|
|
851
|
+
+ `old=${JSON.stringify(result.oldName ?? null)} new=${JSON.stringify(result.newName ?? normalized.name)} `
|
|
852
|
+
+ `error=${result.error} larkCode=${result.larkCode ?? '-'} detail=${result.detail ?? '-'}`);
|
|
853
|
+
return jsonRes(res, status, result);
|
|
854
|
+
}
|
|
855
|
+
if (result.changed) {
|
|
856
|
+
if (proactive)
|
|
857
|
+
proactiveChatRenameCooldown.record(cooldownKey);
|
|
858
|
+
// FR-7: the Lark write already succeeded, so a local cache-refresh
|
|
859
|
+
// failure (ENOSPC/EACCES on the session store) must NOT reverse the
|
|
860
|
+
// outcome into an HTTP 500 — best-effort per session, warn and keep the
|
|
861
|
+
// rename a success. Catch per-session so one bad write can't skip the rest.
|
|
862
|
+
for (const active of getActiveSessionsRegistry()?.values() ?? []) {
|
|
863
|
+
if (active.chatId !== ds.chatId)
|
|
864
|
+
continue;
|
|
865
|
+
active.session.chatDisplayName = result.newName;
|
|
866
|
+
try {
|
|
867
|
+
sessionStore.updateSession(active.session);
|
|
868
|
+
}
|
|
869
|
+
catch (e) {
|
|
870
|
+
logger.warn(`[chat-rename:audit] cache_refresh_failed session=${active.session.sessionId} chat=${ds.chatId} app=${ds.larkAppId} detail=${e instanceof Error ? e.message : String(e)}`);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
logger.info(`[chat-rename:audit] result=success session=${ds.session.sessionId} chat=${ds.chatId} `
|
|
874
|
+
+ `app=${ds.larkAppId} botOpenId=${botOpenId} trigger=${trigger} `
|
|
875
|
+
+ `old=${JSON.stringify(result.oldName)} new=${JSON.stringify(result.newName)} larkCode=0`);
|
|
876
|
+
}
|
|
877
|
+
return jsonRes(res, 200, { ...result, chatId: ds.chatId });
|
|
878
|
+
});
|
|
879
|
+
});
|
|
711
880
|
/** 会话内切换工作目录(角色切换专用):硬校验角色库根 → 更新记录落盘(唯一事实源)
|
|
712
881
|
* → 活 worker 走「带 --resume 的进程重启、respawn 在新 cwd」,无活 worker 杀残留
|
|
713
882
|
* pane 让下条消息冷启动。
|
|
@@ -734,24 +903,46 @@ ipcRoute('POST', '/api/sessions/:sessionId/cd', async (req, res, params) => {
|
|
|
734
903
|
return jsonRes(res, 403, { ok: false, error: auth.error });
|
|
735
904
|
if (!ds)
|
|
736
905
|
return jsonRes(res, 404, { ok: false, error: 'session_not_active' });
|
|
906
|
+
if (isSessionTransferring(ds)) {
|
|
907
|
+
return jsonRes(res, 409, { ok: false, error: 'session_transferring' });
|
|
908
|
+
}
|
|
737
909
|
// Adopt/observed 会话是收编的用户自有 pane——注入或冷重启都会打断用户自己的
|
|
738
910
|
// 终端会话。与 /suspend、/restart、/slash 同款排除。
|
|
739
911
|
if (ds.adoptedFrom || ds.initConfig?.adoptMode) {
|
|
740
912
|
return jsonRes(res, 409, { ok: false, error: 'adopt_cd_unsupported' });
|
|
741
913
|
}
|
|
742
|
-
|
|
914
|
+
// ownAppId 收窄到本 bot 自己的角色库子树:不收窄就能切进别的 bot 的角色目录,
|
|
915
|
+
// 下面 repinSessionWorkingDir 把 ds.workingDir 钉过去之后,那个 bot 的沙盒会话
|
|
916
|
+
// 就拿到了对方整棵角色库的 readWrite(打穿 fs-policy 的跨 bot 隔离)。
|
|
917
|
+
const v = validateRoleLibraryPath(body?.dir ?? '', undefined, ds.larkAppId);
|
|
743
918
|
if (!v.ok) {
|
|
744
|
-
|
|
919
|
+
const forbidden = v.error === 'outside_role_library' || v.error === 'outside_own_role_library';
|
|
920
|
+
// own_role_library_missing:本 bot 的 `<角色库根>/<appId>` 不是真目录(存量用
|
|
921
|
+
// 人类 slug 命名这一层,未按 deploy-runbook §8 迁移)。FAIL-CLOSED——不回落全局
|
|
922
|
+
// 根(回落是 fail-open,会让存量部署继续能跨 bot 切并经 workingDir 拿 rw)。回
|
|
923
|
+
// 409 + 迁移指引,让运营看得见查得到,而不是静默放行或静默拒绝。
|
|
924
|
+
if (v.error === 'own_role_library_missing') {
|
|
925
|
+
logger.warn(`[role] 角色库每-bot 目录名不是 appId(期望 ~/botmux-roles/${ds.larkAppId})——`
|
|
926
|
+
+ 'role switch 已 fail-closed 拒绝,避免跨 bot 越权。按 docs/roles/deploy-runbook.md '
|
|
927
|
+
+ '§8「迁移:每-bot 目录名改为 appId」重命名该目录即恢复。');
|
|
928
|
+
return jsonRes(res, 409, { ok: false, error: v.error });
|
|
929
|
+
}
|
|
930
|
+
return jsonRes(res, forbidden ? 403 : 400, { ok: false, error: v.error });
|
|
745
931
|
}
|
|
746
932
|
repinSessionWorkingDir(ds, v.resolvedPath);
|
|
933
|
+
// ds.initConfig 与 repin 同步,且**无条件**(不只在 live-worker 分支里):下次
|
|
934
|
+
// forkWorker 用 initConfig 重建 init 消息,只在有活 worker 时更新的话,no-worker /
|
|
935
|
+
// worker.killed 两条分支会留下「记录新、initConfig 旧」的分裂状态,冷启动把会话
|
|
936
|
+
// 带回旧 cwd(= 旧角色,连记忆桶都是旧的)。codex review 抓出的既有 bug,与本 PR
|
|
937
|
+
// 的收窄同一处理,顺手修掉。
|
|
938
|
+
if (ds.initConfig)
|
|
939
|
+
ds.initConfig.workingDir = v.resolvedPath;
|
|
747
940
|
if (ds.worker && !ds.worker.killed) {
|
|
748
941
|
// updateWorkingDir 随 restart 带给 worker:respawn 必须收敛到新目录,而不是
|
|
749
|
-
// 陈旧的 lastInitConfig.workingDir
|
|
750
|
-
// 与 worker 侧一致(下次 forkWorker 用它重建 init 消息)。
|
|
751
|
-
if (ds.initConfig)
|
|
752
|
-
ds.initConfig.workingDir = v.resolvedPath;
|
|
942
|
+
// 陈旧的 lastInitConfig.workingDir(daemon 侧 initConfig 已在上面同步)。
|
|
753
943
|
try {
|
|
754
|
-
ds.
|
|
944
|
+
ds.workerReady = false;
|
|
945
|
+
ds.worker.send({ type: 'restart', updateWorkingDir: v.resolvedPath, env: latestPerBotEnvForRestart(ds) });
|
|
755
946
|
}
|
|
756
947
|
catch {
|
|
757
948
|
// send() 抛异常:worker 进程实际上已经不可达(管道已断),但 above 的
|
|
@@ -778,6 +969,28 @@ ipcRoute('POST', '/api/sessions/:sessionId/cd', async (req, res, params) => {
|
|
|
778
969
|
function findSessionRecord(sessionId) {
|
|
779
970
|
return findActiveBySessionId(sessionId)?.session ?? sessionStore.getSession(sessionId);
|
|
780
971
|
}
|
|
972
|
+
/** True when a session-bound IPC route must NOT touch Feishu: the owning bot is
|
|
973
|
+
* core-only (apiOnly) OR the session is an HTTP virtual chat. Central guard for
|
|
974
|
+
* every session-write route (chat-rename / write-link-card / resume-notice /
|
|
975
|
+
* locate / restart-notice …) — the daemon owns the authoritative bot config,
|
|
976
|
+
* so gating here catches the normal-bot-in-virtual-session case that
|
|
977
|
+
* getBotClient (which only throws for apiOnly) cannot. Accepts a live
|
|
978
|
+
* DaemonSession or a stored Session record. Never throws. */
|
|
979
|
+
function sessionTransportDisabled(s) {
|
|
980
|
+
const appId = s.larkAppId;
|
|
981
|
+
let apiOnly = false;
|
|
982
|
+
if (appId) {
|
|
983
|
+
try {
|
|
984
|
+
apiOnly = getBot(appId).config.apiOnly === true;
|
|
985
|
+
}
|
|
986
|
+
catch { /* unknown bot → not apiOnly */ }
|
|
987
|
+
}
|
|
988
|
+
return !larkTransportEnabled({ chatId: s.chatId ?? '', apiOnly });
|
|
989
|
+
}
|
|
990
|
+
/** Mutating IPC routes may only touch this daemon's own bot-partitioned store. */
|
|
991
|
+
function findOwnedSessionRecord(sessionId) {
|
|
992
|
+
return findActiveBySessionId(sessionId)?.session ?? sessionStore.getOwnedSession(sessionId);
|
|
993
|
+
}
|
|
781
994
|
/** Four-state async lookup with durable fallback (design A).
|
|
782
995
|
*
|
|
783
996
|
* In-memory `asyncTriggerResults` lives only on the active DaemonSession and is
|
|
@@ -826,7 +1039,7 @@ function buildAsyncTriggerLookupResponse(sessionId, triggerId) {
|
|
|
826
1039
|
sessionId,
|
|
827
1040
|
liveActive: !!ds,
|
|
828
1041
|
chatId: ds?.chatId ?? stored?.chatId,
|
|
829
|
-
memResult: memResult ? { status: memResult.status, content: memResult.content, completedAt: memResult.completedAt } : undefined,
|
|
1042
|
+
memResult: memResult ? { status: memResult.status, content: memResult.content, completedAt: memResult.completedAt, usage: memResult.usage } : undefined,
|
|
830
1043
|
memTriggerId: memResult ? memTriggerId : undefined,
|
|
831
1044
|
persisted,
|
|
832
1045
|
storedStatus: stored ? (stored.status === 'closed' ? 'closed' : 'open') : undefined,
|
|
@@ -848,7 +1061,7 @@ ipcRoute('POST', '/api/sessions/:sessionId/board', async (req, res, params) => {
|
|
|
848
1061
|
const position = normalizeKanbanPosition(body.position);
|
|
849
1062
|
if (!column && position === null)
|
|
850
1063
|
return jsonRes(res, 400, { ok: false, error: 'bad_request' });
|
|
851
|
-
const session =
|
|
1064
|
+
const session = findOwnedSessionRecord(params.sessionId);
|
|
852
1065
|
if (!session)
|
|
853
1066
|
return jsonRes(res, 404, { ok: false, error: 'session_not_found' });
|
|
854
1067
|
// 待办池(queued)会话被拖到「进行中」= 激活:把暂存内容当首轮发给 CLI 开跑。
|
|
@@ -968,6 +1181,12 @@ ipcRoute('GET', '/api/sessions/:sessionId/history', async (req, res, params) =>
|
|
|
968
1181
|
const appId = session.larkAppId || cachedLarkAppId;
|
|
969
1182
|
if (!appId)
|
|
970
1183
|
return jsonRes(res, 422, { ok: false, error: 'no_lark_app' });
|
|
1184
|
+
// No-transport session (apiOnly bot or HTTP virtual chat) has no Feishu chat
|
|
1185
|
+
// history — listChatMessages/listThreadMessages would dial Feishu with a
|
|
1186
|
+
// synthetic chatId. Return empty history instead of making the network call.
|
|
1187
|
+
if (!larkTransportEnabled({ chatId: session.chatId, apiOnly: getBot(appId).config.apiOnly })) {
|
|
1188
|
+
return jsonRes(res, 200, { ok: true, messages: [], hint: 'no_feishu_transport' });
|
|
1189
|
+
}
|
|
971
1190
|
const url = new URL(req.url ?? '/', 'http://localhost');
|
|
972
1191
|
const limit = Math.min(Math.max(parseInt(url.searchParams.get('limit') ?? '80', 10) || 80, 1), 200);
|
|
973
1192
|
try {
|
|
@@ -1130,12 +1349,7 @@ ipcRoute('GET', '/api/sessions/:sessionId/insight/turn/:turnIndex', (req, res, p
|
|
|
1130
1349
|
ipcRoute('GET', '/api/insights/summary', async (req, res) => {
|
|
1131
1350
|
const url = new URL(req.url ?? '/', 'http://localhost');
|
|
1132
1351
|
const limit = Math.min(Math.max(parseInt(url.searchParams.get('limit') ?? '200', 10) || 200, 1), 500);
|
|
1133
|
-
const
|
|
1134
|
-
const activeIds = new Set(active.map(r => r.sessionId));
|
|
1135
|
-
const closed = sessionStore.listSessions()
|
|
1136
|
-
.filter(s => s.status === 'closed' && !activeIds.has(s.sessionId))
|
|
1137
|
-
.map(composeRowFromClosed);
|
|
1138
|
-
const rows = [...active, ...closed];
|
|
1352
|
+
const rows = composeDashboardSessionRows();
|
|
1139
1353
|
const overview = await buildSafeInsightOverview(rows.map(row => {
|
|
1140
1354
|
const session = findSessionRecord(row.sessionId);
|
|
1141
1355
|
return {
|
|
@@ -1176,20 +1390,30 @@ ipcRoute('POST', '/api/sessions/:sessionId/rename', async (req, res, params) =>
|
|
|
1176
1390
|
catch {
|
|
1177
1391
|
return jsonRes(res, 400, { ok: false, error: 'bad_json' });
|
|
1178
1392
|
}
|
|
1393
|
+
const active = findActiveBySessionId(params.sessionId);
|
|
1394
|
+
const auth = sessionCliIpcAuth(req, active, params.sessionId, body);
|
|
1395
|
+
if (!auth.ok)
|
|
1396
|
+
return jsonRes(res, 403, { ok: false, error: auth.error });
|
|
1179
1397
|
const title = normalizeSessionTitle(body.title);
|
|
1180
1398
|
if (!title)
|
|
1181
1399
|
return jsonRes(res, 400, { ok: false, error: 'bad_title' });
|
|
1182
|
-
const
|
|
1183
|
-
const session = active?.session ?? sessionStore.getSession(params.sessionId);
|
|
1400
|
+
const session = active?.session ?? sessionStore.getOwnedSession(params.sessionId);
|
|
1184
1401
|
if (!session)
|
|
1185
1402
|
return jsonRes(res, 404, { ok: false, error: 'session_not_found' });
|
|
1186
|
-
const
|
|
1403
|
+
const source = normalizeSessionTitleSource(body.source, 'dashboard');
|
|
1404
|
+
const updated = updateSessionTitle(session, title, source);
|
|
1187
1405
|
if (!updated.ok)
|
|
1188
1406
|
return jsonRes(res, 400, { ok: false, error: updated.error });
|
|
1189
1407
|
const agentSync = active
|
|
1190
1408
|
? requestAgentSessionRename(active, updated.title)
|
|
1191
1409
|
: { status: 'not_running' };
|
|
1192
|
-
jsonRes(res, 200, {
|
|
1410
|
+
jsonRes(res, 200, {
|
|
1411
|
+
ok: true,
|
|
1412
|
+
title: updated.title,
|
|
1413
|
+
titleUpdatedAt: updated.updatedAt,
|
|
1414
|
+
titleSource: updated.source,
|
|
1415
|
+
agentSync: agentSync.status,
|
|
1416
|
+
});
|
|
1193
1417
|
});
|
|
1194
1418
|
// 会话锁定:保护被锁定会话不被 dashboard「清理空闲」批量关闭。锁定是会话元数据,
|
|
1195
1419
|
// 不影响用户显式点击关闭/批量关闭,避免把会话变成不可管理状态。
|
|
@@ -1203,7 +1427,7 @@ ipcRoute('POST', '/api/sessions/:sessionId/lock', async (req, res, params) => {
|
|
|
1203
1427
|
}
|
|
1204
1428
|
if (typeof body.locked !== 'boolean')
|
|
1205
1429
|
return jsonRes(res, 400, { ok: false, error: 'bad_locked' });
|
|
1206
|
-
const session =
|
|
1430
|
+
const session = findOwnedSessionRecord(params.sessionId);
|
|
1207
1431
|
if (!session)
|
|
1208
1432
|
return jsonRes(res, 404, { ok: false, error: 'session_not_found' });
|
|
1209
1433
|
if (body.locked)
|
|
@@ -1238,6 +1462,9 @@ ipcRoute('GET', '/api/sessions/:sessionId/write-link', (req, res, params) => {
|
|
|
1238
1462
|
const ds = findActiveBySessionId(params.sessionId);
|
|
1239
1463
|
if (!ds)
|
|
1240
1464
|
return jsonRes(res, 404, { ok: false, error: 'session_not_active' });
|
|
1465
|
+
if (!sessionSupportsWebTerminal(ds)) {
|
|
1466
|
+
return jsonRes(res, 409, { ok: false, error: 'terminal_unsupported' });
|
|
1467
|
+
}
|
|
1241
1468
|
// Riff backend: the sandbox URL is the writable link — no local worker needed.
|
|
1242
1469
|
if (ds.riffAccessUrl) {
|
|
1243
1470
|
jsonRes(res, 200, { ok: true, url: ds.riffAccessUrl });
|
|
@@ -1286,9 +1513,11 @@ ipcRoute('POST', '/api/sessions/:sessionId/write-link-card', async (req, res, pa
|
|
|
1286
1513
|
const ds = findActiveBySessionId(params.sessionId);
|
|
1287
1514
|
if (!ds)
|
|
1288
1515
|
return jsonRes(res, 404, { ok: false, error: 'session_not_active' });
|
|
1516
|
+
if (sessionTransportDisabled(ds))
|
|
1517
|
+
return jsonRes(res, 200, { ok: false, error: 'no_feishu_transport' });
|
|
1289
1518
|
const r = await deliverWriteLinkCardToOwners(ds);
|
|
1290
1519
|
const status = r.ok ? 200
|
|
1291
|
-
: r.error === 'terminal_unavailable' ? 409
|
|
1520
|
+
: r.error === 'terminal_unavailable' || r.error === 'terminal_unsupported' ? 409
|
|
1292
1521
|
: r.error === 'no_owner' ? 422
|
|
1293
1522
|
: 502;
|
|
1294
1523
|
jsonRes(res, status, r);
|
|
@@ -1341,7 +1570,7 @@ ipcRoute('POST', '/api/sessions/:sessionId/resume', async (req, res, params) =>
|
|
|
1341
1570
|
const cliId = ds.session.cliId;
|
|
1342
1571
|
const cliName = getCliDisplayName(cliId ?? 'claude-code');
|
|
1343
1572
|
const notice = JSON.stringify({ text: `🔄 会话已通过命令行恢复,发条消息继续与 ${cliName} 对话。` });
|
|
1344
|
-
if (ds.larkAppId) {
|
|
1573
|
+
if (ds.larkAppId && !sessionTransportDisabled(ds)) {
|
|
1345
1574
|
if (ds.scope === 'chat' && ds.chatId) {
|
|
1346
1575
|
getChatMode(ds.larkAppId, ds.chatId, { forceRefresh: true })
|
|
1347
1576
|
.then((mode) => mode === 'topic' && ds.session.rootMessageId
|
|
@@ -1520,6 +1749,11 @@ ipcRoute('POST', '/api/sessions/:sessionId/locate', async (_req, res, params) =>
|
|
|
1520
1749
|
if (!ctx.ownerOpenId) {
|
|
1521
1750
|
return jsonRes(res, 422, { ok: false, error: 'no_owner' });
|
|
1522
1751
|
}
|
|
1752
|
+
// No-transport session (apiOnly bot or HTTP virtual chat) has no Feishu thread
|
|
1753
|
+
// to @-locate the owner in — the replyMessage below would dial Feishu.
|
|
1754
|
+
if (sessionTransportDisabled({ chatId: ds?.chatId ?? closed?.chatId, larkAppId: ctx.larkAppId })) {
|
|
1755
|
+
return jsonRes(res, 200, { ok: false, error: 'no_feishu_transport' });
|
|
1756
|
+
}
|
|
1523
1757
|
try {
|
|
1524
1758
|
const messageId = await replyMessage(ctx.larkAppId, ctx.rootMessageId, `<at user_id="${ctx.ownerOpenId}"></at>`, 'text', true);
|
|
1525
1759
|
jsonRes(res, 200, { ok: true, messageId });
|
|
@@ -1913,12 +2147,13 @@ ipcRoute('GET', '/api/groups', async (_req, res) => {
|
|
|
1913
2147
|
const enriched = chats.map(c => {
|
|
1914
2148
|
const oncall = oncallStore.getOncallStatus(cachedLarkAppId, c.chatId);
|
|
1915
2149
|
const hasRole = resolveRoleFile(cachedLarkAppId, c.chatId) !== null;
|
|
2150
|
+
const hasMessageListener = getMessageListenerConfig(cachedLarkAppId, c.chatId)?.enabled === true;
|
|
1916
2151
|
// /introduce 记录的外部 botmux 机器人(按名字)——dashboard 团队看板用
|
|
1917
2152
|
// 它识别「介绍过同团队机器人的协作群」。
|
|
1918
2153
|
const observedBotNames = observedBotsStore
|
|
1919
2154
|
.listObservedBots(config.session.dataDir, cachedLarkAppId, c.chatId)
|
|
1920
2155
|
.map(b => b.name);
|
|
1921
|
-
return { ...c, oncallChat: oncall ?? null, firstSeenAt: seenMap.get(c.chatId) ?? null, hasRole, observedBotNames };
|
|
2156
|
+
return { ...c, oncallChat: oncall ?? null, firstSeenAt: seenMap.get(c.chatId) ?? null, hasRole, hasMessageListener, observedBotNames };
|
|
1922
2157
|
});
|
|
1923
2158
|
jsonRes(res, 200, { chats: enriched });
|
|
1924
2159
|
}
|
|
@@ -2155,6 +2390,290 @@ ipcRoute('DELETE', '/api/roles/:chatId', async (_req, res, p) => {
|
|
|
2155
2390
|
deleteRoleInjectMode(cachedLarkAppId, p.chatId);
|
|
2156
2391
|
jsonRes(res, 200, { ok: true, existed });
|
|
2157
2392
|
});
|
|
2393
|
+
ipcRoute('GET', '/api/message-listeners/:chatId', async (_req, res, p) => {
|
|
2394
|
+
if (!cachedLarkAppId)
|
|
2395
|
+
return jsonRes(res, 503, { error: 'larkAppId_not_set' });
|
|
2396
|
+
if (!isValidRoleChatId(p.chatId))
|
|
2397
|
+
return jsonRes(res, 400, { ok: false, error: 'invalid_chat_id' });
|
|
2398
|
+
jsonRes(res, 200, {
|
|
2399
|
+
chatId: p.chatId,
|
|
2400
|
+
listener: getMessageListenerConfig(cachedLarkAppId, p.chatId),
|
|
2401
|
+
maxPromptBytes: MAX_MESSAGE_LISTENER_PROMPT_BYTES,
|
|
2402
|
+
});
|
|
2403
|
+
});
|
|
2404
|
+
ipcRoute('PUT', '/api/message-listeners/:chatId', async (req, res, p) => {
|
|
2405
|
+
if (!cachedLarkAppId)
|
|
2406
|
+
return jsonRes(res, 503, { error: 'larkAppId_not_set' });
|
|
2407
|
+
if (!isValidRoleChatId(p.chatId))
|
|
2408
|
+
return jsonRes(res, 400, { ok: false, error: 'invalid_chat_id' });
|
|
2409
|
+
let body;
|
|
2410
|
+
try {
|
|
2411
|
+
body = await readJsonBody(req);
|
|
2412
|
+
}
|
|
2413
|
+
catch {
|
|
2414
|
+
return jsonRes(res, 400, { ok: false, error: 'bad_json' });
|
|
2415
|
+
}
|
|
2416
|
+
const update = sanitizeMessageListenerUpdate(body);
|
|
2417
|
+
if (!update)
|
|
2418
|
+
return jsonRes(res, 400, { ok: false, error: 'invalid_listener' });
|
|
2419
|
+
const validation = validateMessageListenerUpdate(update);
|
|
2420
|
+
if (!validation.ok)
|
|
2421
|
+
return jsonRes(res, 400, { ok: false, error: validation.reason });
|
|
2422
|
+
if (update.prompt && Buffer.byteLength(update.prompt, 'utf-8') > MAX_MESSAGE_LISTENER_PROMPT_BYTES) {
|
|
2423
|
+
return jsonRes(res, 400, { ok: false, error: 'prompt_too_large' });
|
|
2424
|
+
}
|
|
2425
|
+
const result = await updateMessageListenerConfig(cachedLarkAppId, p.chatId, update);
|
|
2426
|
+
if (!result.ok)
|
|
2427
|
+
return jsonRes(res, ['prompt_required', 'sender_required'].includes(result.reason) ? 400 : 500, { ok: false, error: result.reason });
|
|
2428
|
+
jsonRes(res, 200, { ok: true, listener: result.listener });
|
|
2429
|
+
});
|
|
2430
|
+
function dashboardHistoryMessageSender(message) {
|
|
2431
|
+
const sender = message?.sender ?? {};
|
|
2432
|
+
const senderId = sender.id ?? sender.open_id ?? sender.user_id ?? sender.app_id
|
|
2433
|
+
?? message?.sender_id?.open_id ?? message?.sender_id?.user_id ?? message?.sender_id?.app_id;
|
|
2434
|
+
const senderName = sender.sender_name ?? sender.name ?? sender.user_name ?? message?.sender_name;
|
|
2435
|
+
const senderIdType = sender.id_type ?? sender.sender_id_type;
|
|
2436
|
+
const senderTypeRaw = sender.sender_type ?? message?.sender_type ?? (senderIdType === 'app_id' ? 'app' : undefined);
|
|
2437
|
+
return {
|
|
2438
|
+
senderOpenId: typeof senderId === 'string' ? senderId : undefined,
|
|
2439
|
+
senderName: typeof senderName === 'string' && senderName.trim() ? senderName.trim() : undefined,
|
|
2440
|
+
senderTypeRaw: typeof senderTypeRaw === 'string' ? senderTypeRaw : undefined,
|
|
2441
|
+
senderIdType: typeof senderIdType === 'string' ? senderIdType : undefined,
|
|
2442
|
+
};
|
|
2443
|
+
}
|
|
2444
|
+
function dashboardMessageCreateTimeMs(message) {
|
|
2445
|
+
const value = Number(message?.create_time ?? message?.createTime);
|
|
2446
|
+
return Number.isFinite(value) ? value : undefined;
|
|
2447
|
+
}
|
|
2448
|
+
async function readMessageListenerPreviewRequest(req) {
|
|
2449
|
+
let body;
|
|
2450
|
+
try {
|
|
2451
|
+
body = await readJsonBody(req);
|
|
2452
|
+
}
|
|
2453
|
+
catch {
|
|
2454
|
+
return { ok: false, status: 400, error: 'bad_json' };
|
|
2455
|
+
}
|
|
2456
|
+
const raw = body && typeof body === 'object' && !Array.isArray(body) ? body : {};
|
|
2457
|
+
const listener = sanitizeMessageListenerUpdate(raw.listener ?? raw);
|
|
2458
|
+
if (!listener)
|
|
2459
|
+
return { ok: false, status: 400, error: 'invalid_listener' };
|
|
2460
|
+
const validation = validateMessageListenerUpdate(listener);
|
|
2461
|
+
if (!validation.ok)
|
|
2462
|
+
return { ok: false, status: 400, error: validation.reason };
|
|
2463
|
+
if (listener.prompt && Buffer.byteLength(listener.prompt, 'utf-8') > MAX_MESSAGE_LISTENER_PROMPT_BYTES) {
|
|
2464
|
+
return { ok: false, status: 400, error: 'prompt_too_large' };
|
|
2465
|
+
}
|
|
2466
|
+
return { ok: true, listener, limit: normalizeMessageListenerPreviewLimit(raw.limit) };
|
|
2467
|
+
}
|
|
2468
|
+
async function collectMessageListenerPreviewMatches(larkAppId, chatId, listener, limit) {
|
|
2469
|
+
const bot = getBot(larkAppId);
|
|
2470
|
+
const previewListener = {
|
|
2471
|
+
enabled: true,
|
|
2472
|
+
...(listener.name ? { name: listener.name } : {}),
|
|
2473
|
+
...(listener.replyCardTitle ? { replyCardTitle: listener.replyCardTitle } : {}),
|
|
2474
|
+
...(listener.workingDir ? { workingDir: listener.workingDir } : {}),
|
|
2475
|
+
prompt: listener.prompt,
|
|
2476
|
+
...(listener.senderPolicy && Object.keys(listener.senderPolicy).length > 0 ? { senderPolicy: listener.senderPolicy } : {}),
|
|
2477
|
+
...(listener.messagePolicy ? { messagePolicy: { ...listener.messagePolicy, scope: 'top_level' } } : { messagePolicy: { scope: 'top_level' } }),
|
|
2478
|
+
replyPolicy: { mode: 'thread', sessionMode: 'per_message' },
|
|
2479
|
+
};
|
|
2480
|
+
const previewBot = {
|
|
2481
|
+
...bot,
|
|
2482
|
+
config: {
|
|
2483
|
+
...bot.config,
|
|
2484
|
+
messageListeners: {
|
|
2485
|
+
...(bot.config.messageListeners ?? {}),
|
|
2486
|
+
[chatId]: previewListener,
|
|
2487
|
+
},
|
|
2488
|
+
},
|
|
2489
|
+
};
|
|
2490
|
+
const cutoff = Date.now() - MESSAGE_LISTENER_PREVIEW_WINDOW_MS;
|
|
2491
|
+
const messages = await listChatMessagesUntil(larkAppId, chatId, {
|
|
2492
|
+
pageSize: 50,
|
|
2493
|
+
stopAfter: (message, seenCount) => {
|
|
2494
|
+
const createdAt = dashboardMessageCreateTimeMs(message);
|
|
2495
|
+
return seenCount >= Math.max(100, limit * 5) ||
|
|
2496
|
+
(Number.isFinite(createdAt) && createdAt < cutoff);
|
|
2497
|
+
},
|
|
2498
|
+
});
|
|
2499
|
+
const candidateBotAppIds = collectListenerBotAppIds(messages, dashboardHistoryMessageSender);
|
|
2500
|
+
const appIdToOpenId = await buildListenerBotAppIdToOpenId(larkAppId, chatId, candidateBotAppIds);
|
|
2501
|
+
return previewMessageListenerMatches({
|
|
2502
|
+
bot: previewBot,
|
|
2503
|
+
chatId,
|
|
2504
|
+
messages,
|
|
2505
|
+
limit,
|
|
2506
|
+
senderForMessage: dashboardHistoryMessageSender,
|
|
2507
|
+
appIdToOpenId,
|
|
2508
|
+
// Mirror realtime/poll routing: a message that explicitly @mentions this bot
|
|
2509
|
+
// hands off to normal @-routing, NOT the listener — so preview/run-preview
|
|
2510
|
+
// must apply the same gate (else preview over-counts and run-preview would
|
|
2511
|
+
// spawn a session for a message live routing never sends to the listener).
|
|
2512
|
+
explicitlyMentionedThisBot: (message) => messageMentionsBot(message, larkAppId, bot.botOpenId),
|
|
2513
|
+
});
|
|
2514
|
+
}
|
|
2515
|
+
function publicMessageListenerMatch(match) {
|
|
2516
|
+
return {
|
|
2517
|
+
messageId: match.messageId,
|
|
2518
|
+
createTime: match.createTime,
|
|
2519
|
+
messageText: match.messageText,
|
|
2520
|
+
messageTitle: match.messageTitle,
|
|
2521
|
+
msgType: match.msgType,
|
|
2522
|
+
senderOpenId: match.senderOpenId,
|
|
2523
|
+
senderName: match.senderName,
|
|
2524
|
+
senderType: match.senderType,
|
|
2525
|
+
};
|
|
2526
|
+
}
|
|
2527
|
+
ipcRoute('POST', '/api/message-listeners/:chatId/preview', async (req, res, p) => {
|
|
2528
|
+
if (!cachedLarkAppId)
|
|
2529
|
+
return jsonRes(res, 503, { ok: false, error: 'larkAppId_not_set' });
|
|
2530
|
+
if (!isValidRoleChatId(p.chatId))
|
|
2531
|
+
return jsonRes(res, 400, { ok: false, error: 'invalid_chat_id' });
|
|
2532
|
+
const parsed = await readMessageListenerPreviewRequest(req);
|
|
2533
|
+
if (!parsed.ok)
|
|
2534
|
+
return jsonRes(res, parsed.status, { ok: false, error: parsed.error });
|
|
2535
|
+
try {
|
|
2536
|
+
const matches = await collectMessageListenerPreviewMatches(cachedLarkAppId, p.chatId, parsed.listener, parsed.limit);
|
|
2537
|
+
jsonRes(res, 200, {
|
|
2538
|
+
ok: true,
|
|
2539
|
+
requestedLimit: parsed.limit,
|
|
2540
|
+
matches: matches.map(publicMessageListenerMatch),
|
|
2541
|
+
});
|
|
2542
|
+
}
|
|
2543
|
+
catch (err) {
|
|
2544
|
+
jsonRes(res, 502, { ok: false, error: err instanceof Error ? err.message : String(err) });
|
|
2545
|
+
}
|
|
2546
|
+
});
|
|
2547
|
+
ipcRoute('POST', '/api/message-listeners/:chatId/run-preview', async (req, res, p) => {
|
|
2548
|
+
if (!cachedLarkAppId)
|
|
2549
|
+
return jsonRes(res, 503, { ok: false, error: 'larkAppId_not_set' });
|
|
2550
|
+
if (!isValidRoleChatId(p.chatId))
|
|
2551
|
+
return jsonRes(res, 400, { ok: false, error: 'invalid_chat_id' });
|
|
2552
|
+
const activeSessions = getActiveSessionsRegistry();
|
|
2553
|
+
if (!activeSessions)
|
|
2554
|
+
return jsonRes(res, 503, { ok: false, error: 'active session registry unavailable' });
|
|
2555
|
+
const parsed = await readMessageListenerPreviewRequest(req);
|
|
2556
|
+
if (!parsed.ok)
|
|
2557
|
+
return jsonRes(res, parsed.status, { ok: false, error: parsed.error });
|
|
2558
|
+
try {
|
|
2559
|
+
const matches = await collectMessageListenerPreviewMatches(cachedLarkAppId, p.chatId, parsed.listener, parsed.limit);
|
|
2560
|
+
const run = createMessageListenerRunPreview(cachedLarkAppId, p.chatId, matches.map(match => match.messageId));
|
|
2561
|
+
const results = [];
|
|
2562
|
+
for (const match of matches) {
|
|
2563
|
+
const triggerId = createMessageListenerRunPreviewTurnId();
|
|
2564
|
+
try {
|
|
2565
|
+
const result = await triggerSessionTurn({
|
|
2566
|
+
source: {
|
|
2567
|
+
type: 'ui',
|
|
2568
|
+
connectorId: 'message-listener-preview',
|
|
2569
|
+
requestId: `listener-preview:${match.messageId}`,
|
|
2570
|
+
receivedAt: new Date().toISOString(),
|
|
2571
|
+
},
|
|
2572
|
+
target: {
|
|
2573
|
+
kind: 'turn',
|
|
2574
|
+
botId: cachedLarkAppId,
|
|
2575
|
+
chatId: p.chatId,
|
|
2576
|
+
rootMessageId: match.messageId,
|
|
2577
|
+
},
|
|
2578
|
+
envelope: {
|
|
2579
|
+
format: 'message_listener',
|
|
2580
|
+
sourceName: match.name || 'Message Listener Preview',
|
|
2581
|
+
trusted: false,
|
|
2582
|
+
payload: publicMessageListenerMatch(match),
|
|
2583
|
+
rawText: match.messageText,
|
|
2584
|
+
},
|
|
2585
|
+
instruction: renderMessageListenerInstruction(match),
|
|
2586
|
+
presentation: { topicMessage: null },
|
|
2587
|
+
}, { larkAppId: cachedLarkAppId, activeSessions }, { stableTurnId: triggerId });
|
|
2588
|
+
const tracked = result.ok
|
|
2589
|
+
? markMessageListenerRunPreviewTriggered(run.runId, match.messageId, {
|
|
2590
|
+
action: result.action,
|
|
2591
|
+
sessionId: result.target?.sessionId,
|
|
2592
|
+
triggerId: result.triggerId ?? triggerId,
|
|
2593
|
+
})
|
|
2594
|
+
: markMessageListenerRunPreviewFailed(run.runId, {
|
|
2595
|
+
messageId: match.messageId,
|
|
2596
|
+
sessionId: result.target?.sessionId,
|
|
2597
|
+
error: result.error,
|
|
2598
|
+
});
|
|
2599
|
+
results.push(tracked ?? {
|
|
2600
|
+
runId: run.runId,
|
|
2601
|
+
messageId: match.messageId,
|
|
2602
|
+
ok: result.ok,
|
|
2603
|
+
state: result.ok ? 'triggered' : 'failed',
|
|
2604
|
+
action: result.action,
|
|
2605
|
+
sessionId: result.target?.sessionId,
|
|
2606
|
+
triggerId: result.triggerId ?? triggerId,
|
|
2607
|
+
error: result.error,
|
|
2608
|
+
});
|
|
2609
|
+
}
|
|
2610
|
+
catch (err) {
|
|
2611
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
2612
|
+
const tracked = markMessageListenerRunPreviewFailed(run.runId, {
|
|
2613
|
+
messageId: match.messageId,
|
|
2614
|
+
error,
|
|
2615
|
+
});
|
|
2616
|
+
results.push(tracked ?? {
|
|
2617
|
+
runId: run.runId,
|
|
2618
|
+
messageId: match.messageId,
|
|
2619
|
+
ok: false,
|
|
2620
|
+
state: 'failed',
|
|
2621
|
+
error,
|
|
2622
|
+
});
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
jsonRes(res, 200, {
|
|
2626
|
+
ok: results.every(result => result.ok),
|
|
2627
|
+
runId: run.runId,
|
|
2628
|
+
requestedLimit: parsed.limit,
|
|
2629
|
+
matches: matches.map(publicMessageListenerMatch),
|
|
2630
|
+
results,
|
|
2631
|
+
});
|
|
2632
|
+
}
|
|
2633
|
+
catch (err) {
|
|
2634
|
+
jsonRes(res, 502, { ok: false, error: err instanceof Error ? err.message : String(err) });
|
|
2635
|
+
}
|
|
2636
|
+
});
|
|
2637
|
+
ipcRoute('GET', '/api/message-listeners/:chatId/run-preview/:runId', async (_req, res, p) => {
|
|
2638
|
+
if (!cachedLarkAppId)
|
|
2639
|
+
return jsonRes(res, 503, { ok: false, error: 'larkAppId_not_set' });
|
|
2640
|
+
if (!isValidRoleChatId(p.chatId))
|
|
2641
|
+
return jsonRes(res, 400, { ok: false, error: 'invalid_chat_id' });
|
|
2642
|
+
const run = getMessageListenerRunPreview(p.runId);
|
|
2643
|
+
if (!run || run.larkAppId !== cachedLarkAppId || run.chatId !== p.chatId) {
|
|
2644
|
+
return jsonRes(res, 404, { ok: false, error: 'not_found' });
|
|
2645
|
+
}
|
|
2646
|
+
jsonRes(res, 200, {
|
|
2647
|
+
ok: true,
|
|
2648
|
+
runId: run.runId,
|
|
2649
|
+
createdAt: run.createdAt,
|
|
2650
|
+
updatedAt: run.updatedAt,
|
|
2651
|
+
results: run.results,
|
|
2652
|
+
});
|
|
2653
|
+
});
|
|
2654
|
+
ipcRoute('DELETE', '/api/message-listeners/:chatId', async (_req, res, p) => {
|
|
2655
|
+
if (!cachedLarkAppId)
|
|
2656
|
+
return jsonRes(res, 503, { error: 'larkAppId_not_set' });
|
|
2657
|
+
if (!isValidRoleChatId(p.chatId))
|
|
2658
|
+
return jsonRes(res, 400, { ok: false, error: 'invalid_chat_id' });
|
|
2659
|
+
const result = await updateMessageListenerConfig(cachedLarkAppId, p.chatId, { enabled: false, prompt: '' });
|
|
2660
|
+
if (!result.ok)
|
|
2661
|
+
return jsonRes(res, 500, { ok: false, error: result.reason });
|
|
2662
|
+
jsonRes(res, 200, { ok: true });
|
|
2663
|
+
});
|
|
2664
|
+
ipcRoute('GET', '/api/groups/:chatId/members-display', async (_req, res, p) => {
|
|
2665
|
+
if (!cachedLarkAppId)
|
|
2666
|
+
return jsonRes(res, 503, { error: 'larkAppId_not_set' });
|
|
2667
|
+
if (!isValidRoleChatId(p.chatId))
|
|
2668
|
+
return jsonRes(res, 400, { ok: false, error: 'invalid_chat_id' });
|
|
2669
|
+
try {
|
|
2670
|
+
const members = await listChatMemberDisplays(cachedLarkAppId, p.chatId);
|
|
2671
|
+
jsonRes(res, 200, { members });
|
|
2672
|
+
}
|
|
2673
|
+
catch (err) {
|
|
2674
|
+
jsonRes(res, 502, { ok: false, error: err instanceof Error ? err.message : String(err) });
|
|
2675
|
+
}
|
|
2676
|
+
});
|
|
2158
2677
|
// ─── Role profile management (dashboard) ──────────────────────────────────
|
|
2159
2678
|
// Profiles are authoring/storage helpers only; applying one writes this bot's
|
|
2160
2679
|
// entry into the selected chat role and does not alter runtime role layering.
|
|
@@ -2416,6 +2935,11 @@ ipcRoute('GET', '/api/bot-default-oncall', async (_req, res) => {
|
|
|
2416
2935
|
// disables the toggle wherever the worker would fail-close on it.
|
|
2417
2936
|
readIsolationSupported: readIsolationEnforceable(cachedLarkAppId),
|
|
2418
2937
|
backendType: backendTypeStore.getBotBackendType(cachedLarkAppId) ?? null,
|
|
2938
|
+
usageDisplay: cardPrefs.usageDisplay,
|
|
2939
|
+
// Whether this bot's CLI can produce native usage at all. When false the
|
|
2940
|
+
// dashboard hides the usage-display control (offering it would be a knob
|
|
2941
|
+
// that is always empty — the CLI has no resolvable transcript).
|
|
2942
|
+
usageSupported: cliSupportsNativeUsage(cliId),
|
|
2419
2943
|
disableStreamingCard: cardPrefs.disableStreamingCard,
|
|
2420
2944
|
silentTurnReactions: cardPrefs.silentTurnReactions,
|
|
2421
2945
|
codexAppCleanInput: cardPrefs.codexAppCleanInput,
|
|
@@ -2466,6 +2990,8 @@ ipcRoute('PUT', '/api/bot-card-prefs', async (req, res) => {
|
|
|
2466
2990
|
return jsonRes(res, 400, { ok: false, error: 'bad_json' });
|
|
2467
2991
|
}
|
|
2468
2992
|
const patch = {};
|
|
2993
|
+
if (body.usageDisplay === 'streaming' || body.usageDisplay === 'footer' || body.usageDisplay === 'off')
|
|
2994
|
+
patch.usageDisplay = body.usageDisplay;
|
|
2469
2995
|
if (typeof body.disableStreamingCard === 'boolean')
|
|
2470
2996
|
patch.disableStreamingCard = body.disableStreamingCard;
|
|
2471
2997
|
if (typeof body.botToBotSameDir === 'boolean')
|
|
@@ -2522,6 +3048,9 @@ ipcRoute('PUT', '/api/bot-substitute-mode', async (req, res) => {
|
|
|
2522
3048
|
const chats = Array.isArray(rec.chats)
|
|
2523
3049
|
? [...new Set(rec.chats.map(String).map(s => s.trim()).filter(Boolean))]
|
|
2524
3050
|
: [];
|
|
3051
|
+
const excludedChats = Array.isArray(rec.excludedChats)
|
|
3052
|
+
? [...new Set(rec.excludedChats.map(String).map(s => s.trim()).filter(Boolean))]
|
|
3053
|
+
: [];
|
|
2525
3054
|
const r = await substituteModeStore.updateBotSubstituteMode(cachedLarkAppId, {
|
|
2526
3055
|
enabled: rec.enabled === true,
|
|
2527
3056
|
targets,
|
|
@@ -2529,6 +3058,7 @@ ipcRoute('PUT', '/api/bot-substitute-mode', async (req, res) => {
|
|
|
2529
3058
|
replyMode: rec.replyMode === 'quote' ? 'quote' : 'thread',
|
|
2530
3059
|
disableControlCard: rec.disableControlCard === true,
|
|
2531
3060
|
...(chats.length ? { chats } : {}),
|
|
3061
|
+
...(excludedChats.length ? { excludedChats } : {}),
|
|
2532
3062
|
// 话题群开关:显式 false 才关(旧客户端不带字段 → normalize 缺省开)。
|
|
2533
3063
|
topicGroups: rec.topicGroups,
|
|
2534
3064
|
topicActiveSessionTrigger: rec.topicActiveSessionTrigger,
|
|
@@ -2592,7 +3122,7 @@ ipcRoute('PUT', '/api/bot-summary-trigger', async (req, res) => {
|
|
|
2592
3122
|
// Per-bot 授权偏好。Body 任意子集:
|
|
2593
3123
|
// • restrictGrantCommands: boolean — 限制被授权人只能纯对话
|
|
2594
3124
|
// • autoGrantRequestCards: boolean — 未授权 @ 被挡住时是否发 grant 申请卡
|
|
2595
|
-
// • messageQuotaDefaultLimit: number|null —
|
|
3125
|
+
// • messageQuotaDefaultLimit: number|null — 卡片默认额度覆盖(null = 产品默认 3 条)
|
|
2596
3126
|
ipcRoute('PUT', '/api/bot-grant-prefs', async (req, res) => {
|
|
2597
3127
|
if (!cachedLarkAppId)
|
|
2598
3128
|
return jsonRes(res, 503, { error: 'larkAppId_not_set' });
|
|
@@ -3287,7 +3817,7 @@ ipcRoute('PUT', '/api/bot-read-isolation', async (req, res) => {
|
|
|
3287
3817
|
const suspendedSessions = await suspendActiveSessionsForBot(cachedLarkAppId);
|
|
3288
3818
|
jsonRes(res, 200, { ok: true, readIsolation: r.readIsolation, suspendedSessions });
|
|
3289
3819
|
});
|
|
3290
|
-
// Per-bot session backend override (pty | tmux | herdr | zellij), or clear it
|
|
3820
|
+
// Per-bot session backend override (pty | tmux | herdr | zellij | zmx), or clear it
|
|
3291
3821
|
// ('' / 'auto' / null → follow the daemon default). next-session 生效:running
|
|
3292
3822
|
// sessions keep their spawn-time backend (Session.backendType stamp), only new
|
|
3293
3823
|
// spawns read the new value — so switching here can't strand live sessions.
|
|
@@ -3608,6 +4138,15 @@ ipcRoute('GET', '/api/events', (_req, res) => {
|
|
|
3608
4138
|
activeIds.add(ds.session.sessionId);
|
|
3609
4139
|
res.write(`event: session.spawned\ndata: ${JSON.stringify({ session: composeRowFromActive(ds) })}\n\n`);
|
|
3610
4140
|
}
|
|
4141
|
+
// Persisted active rows may be intentionally absent from the runtime Map
|
|
4142
|
+
// after an inconclusive exact-backend teardown. Replay them as dormant
|
|
4143
|
+
// upserts so SSE reconnects retain the same truthful state as GET
|
|
4144
|
+
// /api/sessions and never synthesize a closed row.
|
|
4145
|
+
for (const s of sessionStore.listSessions()) {
|
|
4146
|
+
if (s.status !== 'active' || activeIds.has(s.sessionId))
|
|
4147
|
+
continue;
|
|
4148
|
+
res.write(`event: session.spawned\ndata: ${JSON.stringify({ session: composeRowFromPersistedActive(s) })}\n\n`);
|
|
4149
|
+
}
|
|
3611
4150
|
// Also replay sessions CLOSED during this run as `session.spawned` carrying a
|
|
3612
4151
|
// closed row. The active-only replay above can't cover a restore-time zombie:
|
|
3613
4152
|
// restoreActiveSessions registers it, announces it, then immediately probes it
|
|
@@ -3645,7 +4184,16 @@ export function startIpcServer(opts) {
|
|
|
3645
4184
|
try {
|
|
3646
4185
|
const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
|
|
3647
4186
|
const method = req.method ?? 'GET';
|
|
3648
|
-
const
|
|
4187
|
+
const coreOnlyPublic = opts.coreOnlyPublicRoutes === true && routeIsCoreOnlyPublic(method, url.pathname);
|
|
4188
|
+
const publicRoute = routeHasPublicAccess(method, url.pathname) || coreOnlyPublic;
|
|
4189
|
+
// Readiness barrier (codex P1): the core-only public control routes
|
|
4190
|
+
// (trigger / trigger-result / insight) must NOT enter their handlers until
|
|
4191
|
+
// restore completes — a trigger during 'starting' races durable restore.
|
|
4192
|
+
// Gate them at the server level so it doesn't depend on the caller probing
|
|
4193
|
+
// /healthz first. /healthz itself reports 503 via its own handler.
|
|
4194
|
+
if (coreOnlyPublic && coreOnlyNotReady()) {
|
|
4195
|
+
return jsonRes(res, 503, { ok: false, status: 'starting', error: 'core-only service is still restoring; retry after /healthz returns 200' });
|
|
4196
|
+
}
|
|
3649
4197
|
const capabilityRoute = routeHasNarrowUntrustedAuth(method, url.pathname);
|
|
3650
4198
|
if (opts.authRequired && !publicRoute) {
|
|
3651
4199
|
const secret = ipcAuthSecret();
|
|
@@ -3687,6 +4235,7 @@ export function startIpcServer(opts) {
|
|
|
3687
4235
|
server,
|
|
3688
4236
|
port: opts.port,
|
|
3689
4237
|
host: opts.host,
|
|
4238
|
+
maxProbe: opts.maxProbe,
|
|
3690
4239
|
log: (m) => logger.warn(`[dashboard-ipc] ${m}`),
|
|
3691
4240
|
}).then((port) => {
|
|
3692
4241
|
boundPort = port;
|