botmux 3.10.0 → 3.11.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/herdr-backend.d.ts +4 -3
- package/dist/adapters/backend/herdr-backend.d.ts.map +1 -1
- package/dist/adapters/backend/herdr-backend.js +11 -8
- package/dist/adapters/backend/herdr-backend.js.map +1 -1
- package/dist/adapters/backend/pty-backend.d.ts +1 -1
- package/dist/adapters/backend/pty-backend.d.ts.map +1 -1
- package/dist/adapters/backend/pty-backend.js +4 -1
- package/dist/adapters/backend/pty-backend.js.map +1 -1
- package/dist/adapters/backend/riff-backend.d.ts +29 -5
- package/dist/adapters/backend/riff-backend.d.ts.map +1 -1
- package/dist/adapters/backend/riff-backend.js +302 -31
- package/dist/adapters/backend/riff-backend.js.map +1 -1
- package/dist/adapters/backend/sandbox.d.ts +6 -0
- package/dist/adapters/backend/sandbox.d.ts.map +1 -1
- package/dist/adapters/backend/sandbox.js +7 -0
- package/dist/adapters/backend/sandbox.js.map +1 -1
- package/dist/adapters/backend/tmux-backend.d.ts +1 -1
- package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
- package/dist/adapters/backend/tmux-backend.js +4 -1
- package/dist/adapters/backend/tmux-backend.js.map +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.d.ts +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.d.ts.map +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.js +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
- package/dist/adapters/backend/types.d.ts +37 -3
- package/dist/adapters/backend/types.d.ts.map +1 -1
- package/dist/adapters/backend/types.js.map +1 -1
- package/dist/adapters/backend/zellij-backend.d.ts +3 -3
- package/dist/adapters/backend/zellij-backend.d.ts.map +1 -1
- package/dist/adapters/backend/zellij-backend.js +9 -3
- package/dist/adapters/backend/zellij-backend.js.map +1 -1
- package/dist/adapters/backend/zellij-observe-backend.d.ts +3 -3
- package/dist/adapters/backend/zellij-observe-backend.d.ts.map +1 -1
- package/dist/adapters/backend/zellij-observe-backend.js +12 -7
- package/dist/adapters/backend/zellij-observe-backend.js.map +1 -1
- package/dist/adapters/cli/codex-app.d.ts.map +1 -1
- package/dist/adapters/cli/codex-app.js +11 -2
- package/dist/adapters/cli/codex-app.js.map +1 -1
- package/dist/adapters/cli/read-isolation.d.ts +35 -2
- package/dist/adapters/cli/read-isolation.d.ts.map +1 -1
- package/dist/adapters/cli/read-isolation.js +88 -7
- package/dist/adapters/cli/read-isolation.js.map +1 -1
- package/dist/adapters/cli/runner-input.d.ts +15 -14
- package/dist/adapters/cli/runner-input.d.ts.map +1 -1
- package/dist/adapters/cli/runner-input.js +64 -26
- package/dist/adapters/cli/runner-input.js.map +1 -1
- package/dist/adapters/cli/types.d.ts +20 -5
- package/dist/adapters/cli/types.d.ts.map +1 -1
- package/dist/bot-registry.d.ts +24 -0
- package/dist/bot-registry.d.ts.map +1 -1
- package/dist/bot-registry.js +110 -5
- package/dist/bot-registry.js.map +1 -1
- package/dist/cli/fleet-shutdown.d.ts +47 -0
- package/dist/cli/fleet-shutdown.d.ts.map +1 -0
- package/dist/cli/fleet-shutdown.js +387 -0
- package/dist/cli/fleet-shutdown.js.map +1 -0
- package/dist/cli/pm2-descriptor-guard.d.ts +19 -0
- package/dist/cli/pm2-descriptor-guard.d.ts.map +1 -0
- package/dist/cli/pm2-descriptor-guard.js +122 -0
- package/dist/cli/pm2-descriptor-guard.js.map +1 -0
- package/dist/cli/pm2-exact-start.d.ts +16 -0
- package/dist/cli/pm2-exact-start.d.ts.map +1 -0
- package/dist/cli/pm2-exact-start.js +63 -0
- package/dist/cli/pm2-exact-start.js.map +1 -0
- package/dist/cli/pm2-god-admission.d.ts +8 -0
- package/dist/cli/pm2-god-admission.d.ts.map +1 -0
- package/dist/cli/pm2-god-admission.js +25 -0
- package/dist/cli/pm2-god-admission.js.map +1 -0
- package/dist/cli/pm2-jlist.d.ts +13 -0
- package/dist/cli/pm2-jlist.d.ts.map +1 -0
- package/dist/cli/pm2-jlist.js +108 -0
- package/dist/cli/pm2-jlist.js.map +1 -0
- package/dist/cli/pm2-preflight.d.ts +10 -0
- package/dist/cli/pm2-preflight.d.ts.map +1 -0
- package/dist/cli/pm2-preflight.js +23 -0
- package/dist/cli/pm2-preflight.js.map +1 -0
- package/dist/cli/pm2-shutdown-capability.d.ts +27 -0
- package/dist/cli/pm2-shutdown-capability.d.ts.map +1 -0
- package/dist/cli/pm2-shutdown-capability.js +161 -0
- package/dist/cli/pm2-shutdown-capability.js.map +1 -0
- package/dist/cli/pm2-start-transaction.d.ts +55 -0
- package/dist/cli/pm2-start-transaction.d.ts.map +1 -0
- package/dist/cli/pm2-start-transaction.js +220 -0
- package/dist/cli/pm2-start-transaction.js.map +1 -0
- package/dist/cli/send-dispatch.d.ts +12 -0
- package/dist/cli/send-dispatch.d.ts.map +1 -1
- package/dist/cli/send-dispatch.js +31 -4
- package/dist/cli/send-dispatch.js.map +1 -1
- package/dist/cli/session-list-liveness.d.ts +1 -0
- package/dist/cli/session-list-liveness.d.ts.map +1 -1
- package/dist/cli/session-list-liveness.js +6 -0
- package/dist/cli/session-list-liveness.js.map +1 -1
- package/dist/cli/session-picker-layout.d.ts +26 -0
- package/dist/cli/session-picker-layout.d.ts.map +1 -0
- package/dist/cli/session-picker-layout.js +123 -0
- package/dist/cli/session-picker-layout.js.map +1 -0
- package/dist/cli/session-picker-viewport.d.ts +49 -0
- package/dist/cli/session-picker-viewport.d.ts.map +1 -0
- package/dist/cli/session-picker-viewport.js +46 -0
- package/dist/cli/session-picker-viewport.js.map +1 -0
- package/dist/cli/supervisor-shutdown-client.d.ts +31 -0
- package/dist/cli/supervisor-shutdown-client.d.ts.map +1 -0
- package/dist/cli/supervisor-shutdown-client.js +146 -0
- package/dist/cli/supervisor-shutdown-client.js.map +1 -0
- package/dist/cli/terminal-width.d.ts +48 -0
- package/dist/cli/terminal-width.d.ts.map +1 -0
- package/dist/cli/terminal-width.js +119 -0
- package/dist/cli/terminal-width.js.map +1 -0
- package/dist/cli/vc-agent.d.ts.map +1 -1
- package/dist/cli/vc-agent.js +1 -1
- package/dist/cli/vc-agent.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +2269 -716
- package/dist/cli.js.map +1 -1
- package/dist/codex-app-runner.js +1630 -125
- package/dist/codex-app-runner.js.map +1 -1
- package/dist/core/ask-api.d.ts +8 -1
- package/dist/core/ask-api.d.ts.map +1 -1
- package/dist/core/ask-api.js +18 -0
- package/dist/core/ask-api.js.map +1 -1
- package/dist/core/ask-broker.d.ts +20 -0
- package/dist/core/ask-broker.d.ts.map +1 -1
- package/dist/core/ask-broker.js +467 -47
- package/dist/core/ask-broker.js.map +1 -1
- package/dist/core/ask-persist-store.d.ts +126 -0
- package/dist/core/ask-persist-store.d.ts.map +1 -0
- package/dist/core/ask-persist-store.js +188 -0
- package/dist/core/ask-persist-store.js.map +1 -0
- package/dist/core/ask-types.d.ts +80 -0
- package/dist/core/ask-types.d.ts.map +1 -1
- package/dist/core/ask-types.js +62 -0
- package/dist/core/ask-types.js.map +1 -1
- package/dist/core/cli-session-id-publisher.d.ts +20 -0
- package/dist/core/cli-session-id-publisher.d.ts.map +1 -0
- package/dist/core/cli-session-id-publisher.js +21 -0
- package/dist/core/cli-session-id-publisher.js.map +1 -0
- package/dist/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +427 -255
- package/dist/core/command-handler.js.map +1 -1
- package/dist/core/cost-calculator.d.ts +13 -0
- package/dist/core/cost-calculator.d.ts.map +1 -1
- package/dist/core/cost-calculator.js +385 -26
- package/dist/core/cost-calculator.js.map +1 -1
- package/dist/core/dashboard-ipc-server.d.ts +9 -0
- package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
- package/dist/core/dashboard-ipc-server.js +842 -288
- package/dist/core/dashboard-ipc-server.js.map +1 -1
- package/dist/core/explicit-session-backing-cleanup.d.ts +49 -0
- package/dist/core/explicit-session-backing-cleanup.d.ts.map +1 -0
- package/dist/core/explicit-session-backing-cleanup.js +76 -0
- package/dist/core/explicit-session-backing-cleanup.js.map +1 -0
- package/dist/core/idle-worker-sweeper.d.ts +20 -0
- package/dist/core/idle-worker-sweeper.d.ts.map +1 -1
- package/dist/core/idle-worker-sweeper.js +18 -0
- package/dist/core/idle-worker-sweeper.js.map +1 -1
- package/dist/core/inflight-input-tracker.d.ts +10 -0
- package/dist/core/inflight-input-tracker.d.ts.map +1 -1
- package/dist/core/inflight-input-tracker.js +7 -0
- package/dist/core/inflight-input-tracker.js.map +1 -1
- package/dist/core/managed-origin-attestation.d.ts +51 -0
- package/dist/core/managed-origin-attestation.d.ts.map +1 -0
- package/dist/core/managed-origin-attestation.js +208 -0
- package/dist/core/managed-origin-attestation.js.map +1 -0
- package/dist/core/managed-origin-capability.d.ts +62 -3
- package/dist/core/managed-origin-capability.d.ts.map +1 -1
- package/dist/core/managed-origin-capability.js +419 -9
- package/dist/core/managed-origin-capability.js.map +1 -1
- package/dist/core/pending-repo-journal.d.ts +6 -0
- package/dist/core/pending-repo-journal.d.ts.map +1 -0
- package/dist/core/pending-repo-journal.js +85 -0
- package/dist/core/pending-repo-journal.js.map +1 -0
- package/dist/core/persistent-backend.d.ts +6 -1
- package/dist/core/persistent-backend.d.ts.map +1 -1
- package/dist/core/persistent-backend.js +12 -6
- package/dist/core/persistent-backend.js.map +1 -1
- package/dist/core/process-start-identity.d.ts +3 -0
- package/dist/core/process-start-identity.d.ts.map +1 -0
- package/dist/core/process-start-identity.js +62 -0
- package/dist/core/process-start-identity.js.map +1 -0
- package/dist/core/raw-command-writer.d.ts +34 -0
- package/dist/core/raw-command-writer.d.ts.map +1 -0
- package/dist/core/raw-command-writer.js +48 -0
- package/dist/core/raw-command-writer.js.map +1 -0
- package/dist/core/reply-target.d.ts +85 -22
- package/dist/core/reply-target.d.ts.map +1 -1
- package/dist/core/reply-target.js +267 -32
- package/dist/core/reply-target.js.map +1 -1
- package/dist/core/resource-monitor/runtime.d.ts.map +1 -1
- package/dist/core/resource-monitor/runtime.js +4 -1
- package/dist/core/resource-monitor/runtime.js.map +1 -1
- package/dist/core/restart-report.d.ts +6 -1
- package/dist/core/restart-report.d.ts.map +1 -1
- package/dist/core/restart-report.js +22 -4
- package/dist/core/restart-report.js.map +1 -1
- package/dist/core/riff-shutdown-detach.d.ts +164 -0
- package/dist/core/riff-shutdown-detach.d.ts.map +1 -0
- package/dist/core/riff-shutdown-detach.js +787 -0
- package/dist/core/riff-shutdown-detach.js.map +1 -0
- package/dist/core/riff-worker-shutdown-readiness.d.ts +17 -0
- package/dist/core/riff-worker-shutdown-readiness.d.ts.map +1 -0
- package/dist/core/riff-worker-shutdown-readiness.js +21 -0
- package/dist/core/riff-worker-shutdown-readiness.js.map +1 -0
- package/dist/core/session-manager.d.ts +6 -6
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +1227 -594
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-marker.d.ts +1 -1
- package/dist/core/session-marker.d.ts.map +1 -1
- package/dist/core/session-marker.js +2 -2
- package/dist/core/session-marker.js.map +1 -1
- package/dist/core/session-mutation-guard.d.ts +17 -0
- package/dist/core/session-mutation-guard.d.ts.map +1 -0
- package/dist/core/session-mutation-guard.js +33 -0
- package/dist/core/session-mutation-guard.js.map +1 -0
- package/dist/core/shutdown-budgets.d.ts +27 -0
- package/dist/core/shutdown-budgets.d.ts.map +1 -0
- package/dist/core/shutdown-budgets.js +47 -0
- package/dist/core/shutdown-budgets.js.map +1 -0
- package/dist/core/supervisor-shutdown-ipc.d.ts +10 -0
- package/dist/core/supervisor-shutdown-ipc.d.ts.map +1 -0
- package/dist/core/supervisor-shutdown-ipc.js +10 -0
- package/dist/core/supervisor-shutdown-ipc.js.map +1 -0
- package/dist/core/supervisor-shutdown-protocol.d.ts +16 -0
- package/dist/core/supervisor-shutdown-protocol.d.ts.map +1 -0
- package/dist/core/supervisor-shutdown-protocol.js +16 -0
- package/dist/core/supervisor-shutdown-protocol.js.map +1 -0
- package/dist/core/trigger-final-suppression.d.ts +4 -0
- package/dist/core/trigger-final-suppression.d.ts.map +1 -1
- package/dist/core/trigger-final-suppression.js +6 -11
- package/dist/core/trigger-final-suppression.js.map +1 -1
- package/dist/core/trigger-session.d.ts +70 -0
- package/dist/core/trigger-session.d.ts.map +1 -1
- package/dist/core/trigger-session.js +923 -145
- package/dist/core/trigger-session.js.map +1 -1
- package/dist/core/types.d.ts +94 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +9 -0
- package/dist/core/types.js.map +1 -1
- package/dist/core/worker-pool.d.ts +194 -13
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +2597 -547
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts +76 -2
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +2255 -773
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/bot-payload.d.ts +20 -0
- package/dist/dashboard/bot-payload.d.ts.map +1 -1
- package/dist/dashboard/bot-payload.js +24 -0
- package/dist/dashboard/bot-payload.js.map +1 -1
- package/dist/dashboard/feed-groups.d.ts +17 -0
- package/dist/dashboard/feed-groups.d.ts.map +1 -0
- package/dist/dashboard/feed-groups.js +75 -0
- package/dist/dashboard/feed-groups.js.map +1 -0
- package/dist/dashboard/session-card-model.d.ts +1 -1
- package/dist/dashboard/session-card-model.d.ts.map +1 -1
- package/dist/dashboard/session-card-model.js +8 -5
- package/dist/dashboard/session-card-model.js.map +1 -1
- package/dist/dashboard/web/bot-defaults-page.d.ts +4 -0
- package/dist/dashboard/web/bot-defaults-page.d.ts.map +1 -1
- package/dist/dashboard/web/bot-defaults-page.js +139 -46
- package/dist/dashboard/web/bot-defaults-page.js.map +1 -1
- package/dist/dashboard/web/bot-defaults.d.ts +4 -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/connectors-page.d.ts.map +1 -1
- package/dist/dashboard/web/connectors-page.js +40 -3
- package/dist/dashboard/web/connectors-page.js.map +1 -1
- package/dist/dashboard/web/groups-page.d.ts.map +1 -1
- package/dist/dashboard/web/groups-page.js +95 -2
- 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 +86 -40
- package/dist/dashboard/web/i18n.js.map +1 -1
- package/dist/dashboard/web/kanban-model.js +1 -1
- package/dist/dashboard/web/kanban-model.js.map +1 -1
- package/dist/dashboard/web/overview-page.d.ts.map +1 -1
- package/dist/dashboard/web/overview-page.js +3 -2
- package/dist/dashboard/web/overview-page.js.map +1 -1
- package/dist/dashboard/web/overview.d.ts +3 -0
- package/dist/dashboard/web/overview.d.ts.map +1 -1
- package/dist/dashboard/web/overview.js +2 -0
- package/dist/dashboard/web/overview.js.map +1 -1
- package/dist/dashboard/web/sessions-kanban.d.ts.map +1 -1
- package/dist/dashboard/web/sessions-kanban.js +2 -0
- package/dist/dashboard/web/sessions-kanban.js.map +1 -1
- package/dist/dashboard/web/sessions-page.d.ts.map +1 -1
- package/dist/dashboard/web/sessions-page.js +103 -2
- package/dist/dashboard/web/sessions-page.js.map +1 -1
- package/dist/dashboard/web/sessions.d.ts.map +1 -1
- package/dist/dashboard/web/sessions.js +3 -2
- package/dist/dashboard/web/sessions.js.map +1 -1
- package/dist/dashboard/web/ui.d.ts +8 -0
- package/dist/dashboard/web/ui.d.ts.map +1 -1
- package/dist/dashboard/web/ui.js +15 -0
- package/dist/dashboard/web/ui.js.map +1 -1
- package/dist/dashboard-web/app.js +1 -1
- package/dist/dashboard-web/chunks/bot-defaults-page-GED4FEMS.js +10 -0
- package/dist/dashboard-web/chunks/chunk-6J5LS5AU.js +51 -0
- package/dist/dashboard-web/chunks/{chunk-ZU3UUKAR.js → chunk-HK7NIBJG.js} +1 -1
- package/dist/dashboard-web/chunks/{chunk-3UE7S77S.js → chunk-KRHWZEXU.js} +1 -1
- package/dist/dashboard-web/chunks/chunk-VBXJOJFH.js +1 -0
- package/dist/dashboard-web/chunks/connectors-page-EELM66G3.js +1 -0
- package/dist/dashboard-web/chunks/groups-page-WKUV6E42.js +8 -0
- package/dist/dashboard-web/chunks/{insights-page-T2ZX2RWL.js → insights-page-54UBPPOC.js} +1 -1
- package/dist/dashboard-web/chunks/{monitor-room-YNVBAGR6.js → monitor-room-PORHVKRH.js} +1 -1
- package/dist/dashboard-web/chunks/{monitoring-page-LHM55YWN.js → monitoring-page-XGA7AXNL.js} +1 -1
- package/dist/dashboard-web/chunks/{office-page-ATXVP7PW.js → office-page-AO3AFG45.js} +1 -1
- package/dist/dashboard-web/chunks/overview-page-F5BUTYR6.js +1 -0
- package/dist/dashboard-web/chunks/{roles-page-56VWM6CD.js → roles-page-M5GIASJU.js} +1 -1
- package/dist/dashboard-web/chunks/{schedules-page-S5K6U4EO.js → schedules-page-O232WQRR.js} +1 -1
- package/dist/dashboard-web/chunks/sessions-page-YFRPSTIL.js +6 -0
- package/dist/dashboard-web/chunks/{settings-page-QUZDQARR.js → settings-page-OJAZLZQT.js} +1 -1
- package/dist/dashboard-web/chunks/{skills-page-IFY7AEND.js → skills-page-EFZQPWV4.js} +1 -1
- package/dist/dashboard-web/chunks/{team-federation-page-C7FJQPFZ.js → team-federation-page-ZJVAP7HW.js} +1 -1
- package/dist/dashboard-web/chunks/{v3-page-2LMF3WWR.js → v3-page-GYWTRGO2.js} +1 -1
- package/dist/dashboard-web/chunks/{whiteboards-page-3XC6QRMY.js → whiteboards-page-MSDAG6P7.js} +1 -1
- package/dist/dashboard-web/style.css +255 -0
- package/dist/dashboard.js +172 -7
- package/dist/dashboard.js.map +1 -1
- package/dist/desktop/renderer/app.js +21 -0
- package/dist/desktop/renderer/app.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +35 -9
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +35 -9
- package/dist/i18n/zh.js.map +1 -1
- package/dist/im/lark/ask-card.d.ts +23 -0
- package/dist/im/lark/ask-card.d.ts.map +1 -1
- package/dist/im/lark/ask-card.js +94 -4
- package/dist/im/lark/ask-card.js.map +1 -1
- package/dist/im/lark/card-builder.d.ts +6 -1
- package/dist/im/lark/card-builder.d.ts.map +1 -1
- package/dist/im/lark/card-builder.js +112 -15
- package/dist/im/lark/card-builder.js.map +1 -1
- package/dist/im/lark/card-handler.d.ts +1 -1
- package/dist/im/lark/card-handler.d.ts.map +1 -1
- package/dist/im/lark/card-handler.js +496 -310
- package/dist/im/lark/card-handler.js.map +1 -1
- package/dist/im/lark/client.d.ts +7 -0
- package/dist/im/lark/client.d.ts.map +1 -1
- package/dist/im/lark/client.js +39 -26
- package/dist/im/lark/client.js.map +1 -1
- package/dist/im/lark/doc-comment.d.ts +7 -4
- package/dist/im/lark/doc-comment.d.ts.map +1 -1
- package/dist/im/lark/doc-comment.js +19 -6
- package/dist/im/lark/doc-comment.js.map +1 -1
- package/dist/im/lark/event-dispatcher.d.ts +16 -0
- package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
- package/dist/im/lark/event-dispatcher.js +88 -23
- package/dist/im/lark/event-dispatcher.js.map +1 -1
- package/dist/im/lark/forward-followup-content.d.ts.map +1 -1
- package/dist/im/lark/forward-followup-content.js +9 -1
- package/dist/im/lark/forward-followup-content.js.map +1 -1
- package/dist/im/lark/grant-command.d.ts.map +1 -1
- package/dist/im/lark/grant-command.js +7 -4
- package/dist/im/lark/grant-command.js.map +1 -1
- package/dist/im/lark/md-card.d.ts +11 -0
- package/dist/im/lark/md-card.d.ts.map +1 -1
- package/dist/im/lark/md-card.js +46 -0
- package/dist/im/lark/md-card.js.map +1 -1
- package/dist/im/lark/message-parser.d.ts +23 -2
- package/dist/im/lark/message-parser.d.ts.map +1 -1
- package/dist/im/lark/message-parser.js +58 -3
- package/dist/im/lark/message-parser.js.map +1 -1
- package/dist/im/lark/overview-card.d.ts.map +1 -1
- package/dist/im/lark/overview-card.js +2 -1
- package/dist/im/lark/overview-card.js.map +1 -1
- package/dist/services/async-trigger-state.d.ts +6 -1
- package/dist/services/async-trigger-state.d.ts.map +1 -1
- package/dist/services/async-trigger-state.js +21 -0
- package/dist/services/async-trigger-state.js.map +1 -1
- package/dist/services/async-trigger-store.d.ts +23 -1
- package/dist/services/async-trigger-store.d.ts.map +1 -1
- package/dist/services/async-trigger-store.js +96 -13
- package/dist/services/async-trigger-store.js.map +1 -1
- package/dist/services/bridge-fallback-gate.d.ts +40 -0
- package/dist/services/bridge-fallback-gate.d.ts.map +1 -1
- package/dist/services/bridge-fallback-gate.js +124 -24
- package/dist/services/bridge-fallback-gate.js.map +1 -1
- package/dist/services/codex-app-runner-protocol.d.ts +8 -0
- package/dist/services/codex-app-runner-protocol.d.ts.map +1 -1
- package/dist/services/codex-app-runner-protocol.js +9 -2
- package/dist/services/codex-app-runner-protocol.js.map +1 -1
- package/dist/services/codex-bridge-queue.d.ts +1 -0
- package/dist/services/codex-bridge-queue.d.ts.map +1 -1
- package/dist/services/codex-bridge-queue.js +1 -0
- package/dist/services/codex-bridge-queue.js.map +1 -1
- package/dist/services/codex-service-tier.d.ts +2 -0
- package/dist/services/codex-service-tier.d.ts.map +1 -1
- package/dist/services/codex-service-tier.js +1 -0
- package/dist/services/codex-service-tier.js.map +1 -1
- package/dist/services/codex-transcript.d.ts +27 -0
- package/dist/services/codex-transcript.d.ts.map +1 -1
- package/dist/services/codex-transcript.js +340 -2
- package/dist/services/codex-transcript.js.map +1 -1
- package/dist/services/grant-policy.d.ts +3 -0
- package/dist/services/grant-policy.d.ts.map +1 -1
- package/dist/services/grant-policy.js +5 -0
- package/dist/services/grant-policy.js.map +1 -1
- package/dist/services/grant-prefs-store.d.ts +6 -3
- package/dist/services/grant-prefs-store.d.ts.map +1 -1
- package/dist/services/grant-prefs-store.js +42 -12
- package/dist/services/grant-prefs-store.js.map +1 -1
- package/dist/services/hook-runner.d.ts +20 -1
- package/dist/services/hook-runner.d.ts.map +1 -1
- package/dist/services/hook-runner.js +17 -13
- package/dist/services/hook-runner.js.map +1 -1
- package/dist/services/idempotency-store.d.ts +134 -0
- package/dist/services/idempotency-store.d.ts.map +1 -0
- package/dist/services/idempotency-store.js +299 -0
- package/dist/services/idempotency-store.js.map +1 -0
- package/dist/services/jsonl-cursor.d.ts +7 -3
- package/dist/services/jsonl-cursor.d.ts.map +1 -1
- package/dist/services/jsonl-cursor.js +55 -31
- package/dist/services/jsonl-cursor.js.map +1 -1
- package/dist/services/open-platform-rename.d.ts.map +1 -1
- package/dist/services/open-platform-rename.js +1 -108
- package/dist/services/open-platform-rename.js.map +1 -1
- package/dist/services/restart-intent-store.d.ts +22 -2
- package/dist/services/restart-intent-store.d.ts.map +1 -1
- package/dist/services/restart-intent-store.js +160 -18
- package/dist/services/restart-intent-store.js.map +1 -1
- package/dist/services/sandbox-store.d.ts +22 -0
- package/dist/services/sandbox-store.d.ts.map +1 -1
- package/dist/services/sandbox-store.js +37 -8
- package/dist/services/sandbox-store.js.map +1 -1
- package/dist/services/send-policy.d.ts +41 -33
- package/dist/services/send-policy.d.ts.map +1 -1
- package/dist/services/send-policy.js +39 -40
- package/dist/services/send-policy.js.map +1 -1
- package/dist/services/session-store.d.ts +66 -0
- package/dist/services/session-store.d.ts.map +1 -1
- package/dist/services/session-store.js +403 -67
- package/dist/services/session-store.js.map +1 -1
- package/dist/services/submit-notification.d.ts +3 -0
- package/dist/services/submit-notification.d.ts.map +1 -0
- package/dist/services/submit-notification.js +15 -0
- package/dist/services/submit-notification.js.map +1 -0
- package/dist/services/traex-transcript.d.ts +25 -2
- package/dist/services/traex-transcript.d.ts.map +1 -1
- package/dist/services/traex-transcript.js +133 -1
- package/dist/services/traex-transcript.js.map +1 -1
- package/dist/services/trigger-types.d.ts +13 -1
- package/dist/services/trigger-types.d.ts.map +1 -1
- package/dist/services/trigger-types.js +39 -0
- package/dist/services/trigger-types.js.map +1 -1
- package/dist/services/voice/index.d.ts +3 -2
- package/dist/services/voice/index.d.ts.map +1 -1
- package/dist/services/voice/index.js +5 -5
- package/dist/services/voice/index.js.map +1 -1
- package/dist/services/voice/openai.d.ts +2 -1
- package/dist/services/voice/openai.d.ts.map +1 -1
- package/dist/services/voice/openai.js +2 -1
- package/dist/services/voice/openai.js.map +1 -1
- package/dist/services/voice/sami.d.ts +5 -2
- package/dist/services/voice/sami.d.ts.map +1 -1
- package/dist/services/voice/sami.js +19 -4
- package/dist/services/voice/sami.js.map +1 -1
- package/dist/setup/bots-store.d.ts.map +1 -1
- package/dist/setup/bots-store.js +10 -4
- package/dist/setup/bots-store.js.map +1 -1
- package/dist/setup/lark-scopes.json +2 -0
- package/dist/setup/open-platform-automation.d.ts +9 -1
- package/dist/setup/open-platform-automation.d.ts.map +1 -1
- package/dist/setup/open-platform-automation.js +44 -15
- package/dist/setup/open-platform-automation.js.map +1 -1
- package/dist/setup/open-platform-visibility.d.ts +42 -0
- package/dist/setup/open-platform-visibility.d.ts.map +1 -0
- package/dist/setup/open-platform-visibility.js +133 -0
- package/dist/setup/open-platform-visibility.js.map +1 -0
- package/dist/setup/verify-permissions.d.ts.map +1 -1
- package/dist/setup/verify-permissions.js +6 -0
- package/dist/setup/verify-permissions.js.map +1 -1
- package/dist/skills/definitions.js +2 -2
- package/dist/types.d.ts +290 -13
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/bot-routing.d.ts +1 -0
- package/dist/utils/bot-routing.d.ts.map +1 -1
- package/dist/utils/bot-routing.js +1 -1
- package/dist/utils/bot-routing.js.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 +9 -0
- package/dist/utils/child-env.js.map +1 -1
- package/dist/utils/codex-app-control.d.ts +438 -0
- package/dist/utils/codex-app-control.d.ts.map +1 -0
- package/dist/utils/codex-app-control.js +2336 -0
- package/dist/utils/codex-app-control.js.map +1 -0
- package/dist/utils/codex-app-dispatch-ledger.d.ts +78 -0
- package/dist/utils/codex-app-dispatch-ledger.d.ts.map +1 -0
- package/dist/utils/codex-app-dispatch-ledger.js +149 -0
- package/dist/utils/codex-app-dispatch-ledger.js.map +1 -0
- package/dist/utils/codex-app-turn-dispatch.d.ts +77 -0
- package/dist/utils/codex-app-turn-dispatch.d.ts.map +1 -0
- package/dist/utils/codex-app-turn-dispatch.js +141 -0
- package/dist/utils/codex-app-turn-dispatch.js.map +1 -0
- package/dist/utils/codex-app-turn-liveness.d.ts +125 -0
- package/dist/utils/codex-app-turn-liveness.d.ts.map +1 -0
- package/dist/utils/codex-app-turn-liveness.js +301 -0
- package/dist/utils/codex-app-turn-liveness.js.map +1 -0
- package/dist/utils/idle-detector.d.ts +12 -0
- package/dist/utils/idle-detector.d.ts.map +1 -1
- package/dist/utils/idle-detector.js +19 -0
- package/dist/utils/idle-detector.js.map +1 -1
- package/dist/utils/input-gate.d.ts +87 -0
- package/dist/utils/input-gate.d.ts.map +1 -1
- package/dist/utils/input-gate.js +86 -0
- package/dist/utils/input-gate.js.map +1 -1
- package/dist/utils/lark-upload.d.ts.map +1 -1
- package/dist/utils/lark-upload.js +52 -1
- package/dist/utils/lark-upload.js.map +1 -1
- package/dist/utils/pending-input-queue.d.ts +15 -0
- package/dist/utils/pending-input-queue.d.ts.map +1 -1
- package/dist/utils/pending-input-queue.js +5 -1
- package/dist/utils/pending-input-queue.js.map +1 -1
- package/dist/utils/user-token.d.ts.map +1 -1
- package/dist/utils/user-token.js +53 -2
- package/dist/utils/user-token.js.map +1 -1
- package/dist/worker-ipc-preload.d.ts +12 -0
- package/dist/worker-ipc-preload.d.ts.map +1 -0
- package/dist/worker-ipc-preload.js +44 -0
- package/dist/worker-ipc-preload.js.map +1 -0
- package/dist/worker.js +2604 -325
- package/dist/worker.js.map +1 -1
- package/dist/workflows/v3/session-relay-client.d.ts +1 -0
- package/dist/workflows/v3/session-relay-client.d.ts.map +1 -1
- package/dist/workflows/v3/session-relay-client.js +12 -3
- package/dist/workflows/v3/session-relay-client.js.map +1 -1
- package/package.json +1 -1
- package/dist/dashboard-web/chunks/bot-defaults-page-LTVOUDIS.js +0 -10
- package/dist/dashboard-web/chunks/chunk-DCS35UMD.js +0 -1
- package/dist/dashboard-web/chunks/chunk-MFQPCVHY.js +0 -51
- package/dist/dashboard-web/chunks/connectors-page-5KWDIMKC.js +0 -1
- package/dist/dashboard-web/chunks/groups-page-FHOKYXMW.js +0 -8
- package/dist/dashboard-web/chunks/overview-page-QYI3KBDD.js +0 -1
- package/dist/dashboard-web/chunks/sessions-page-WBP4N7D2.js +0 -6
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/core/dashboard-ipc-server.ts
|
|
2
2
|
import { createServer } from 'node:http';
|
|
3
3
|
import { execFileSync } from 'node:child_process';
|
|
4
|
-
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { readFileSync, unlinkSync } from 'node:fs';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { logger } from '../utils/logger.js';
|
|
7
7
|
import { cliAuthBind, verifyHmac } from '../dashboard/auth.js';
|
|
@@ -9,6 +9,7 @@ import { WORKFLOW_DAEMON_IPC_ROUTE_PREFIX } from '../workflows/v3/daemon-ipc-aut
|
|
|
9
9
|
import { V3_SESSION_RUN_MUTATION_ROUTE_PREFIX } from '../workflows/v3/session-relay.js';
|
|
10
10
|
import { listenWithProbe } from '../utils/listen-with-probe.js';
|
|
11
11
|
import { dashboardSecretPath } from './dashboard-secret.js';
|
|
12
|
+
import { MANAGED_ORIGIN_ATTEST_ROUTE, MANAGED_ORIGIN_PROOF_DOMAIN, MANAGED_ORIGIN_PROOF_TTL_MS, writeManagedOriginAttestationProof, } from './managed-origin-attestation.js';
|
|
12
13
|
import * as sessionStore from '../services/session-store.js';
|
|
13
14
|
import { cliSupportsNativeUsage } from '../services/transcript-resolver.js';
|
|
14
15
|
import * as asyncTriggerStore from '../services/async-trigger-store.js';
|
|
@@ -22,6 +23,7 @@ import * as sandboxStore from '../services/sandbox-store.js';
|
|
|
22
23
|
import * as backendTypeStore from '../services/backend-type-store.js';
|
|
23
24
|
import { isValidRiffBaseUrl, isValidRiffSandboxCluster } from '../adapters/backend/riff-backend.js';
|
|
24
25
|
import { ensureBackendAvailable } from '../services/backend-availability.js';
|
|
26
|
+
import * as persistentBackend from './persistent-backend.js';
|
|
25
27
|
import * as cardPrefsStore from '../services/card-prefs-store.js';
|
|
26
28
|
import * as substituteModeStore from '../services/substitute-mode-store.js';
|
|
27
29
|
import { createCliAdapterSync } from '../adapters/cli/registry.js';
|
|
@@ -77,7 +79,7 @@ import { isSessionStopped } from './session-liveness.js';
|
|
|
77
79
|
import { isSuspendableBackendType } from './persistent-backend.js';
|
|
78
80
|
import { getChatMode, replyMessage, sendMessage, resolveUnionIdFromOpenId, listThreadMessages, listChatMessages, listChatMessagesUntil, listChatBotMembers, getUserProfile, getUserProfileStrict, resolveAllowedUsersWithMap } from '../im/lark/client.js';
|
|
79
81
|
import { parseApiMessage, cardContentHasUpgradeFallback, resolveMergedCardContent, messageMentionsBot } from '../im/lark/message-parser.js';
|
|
80
|
-
import { resumeSession, spawnDashboardSession, activateQueuedSession, closeCliMismatchedSessionsForBot
|
|
82
|
+
import { resumeSession, spawnDashboardSession, activateQueuedSession, closeCliMismatchedSessionsForBot } from './session-manager.js';
|
|
81
83
|
import { parseSpawnRequest } from './session-create.js';
|
|
82
84
|
import { cleanupMaterializedDashboardImages, materializeDashboardImages } from './dashboard-images.js';
|
|
83
85
|
import { getCliDisplayName } from '../im/lark/card-builder.js';
|
|
@@ -93,12 +95,16 @@ import { validateTriggerRequest } from '../services/trigger-types.js';
|
|
|
93
95
|
import { resolveCliSelection, selectionKeyForBot } from '../setup/cli-selection.js';
|
|
94
96
|
import { checkCliAvailability } from '../setup/cli-availability.js';
|
|
95
97
|
import { enrichHistorySenders } from '../dashboard/history-senders.js';
|
|
98
|
+
import { validateCodexAppManagedSendOrigin, } from '../utils/codex-app-dispatch-ledger.js';
|
|
99
|
+
import { withBotTurnAdmission, withBotTurnMutation } from './bot-turn-mutation-gate.js';
|
|
100
|
+
import { protectedSessionMutationReasons, } from './session-mutation-guard.js';
|
|
96
101
|
import { listPendingAsks, submitAskFromDesktop } from './ask-broker.js';
|
|
97
102
|
import { getMessageListenerConfig, sanitizeMessageListenerUpdate, updateMessageListenerConfig, validateMessageListenerUpdate } from '../services/message-listener-store.js';
|
|
98
103
|
import { MAX_MESSAGE_LISTENER_PROMPT_BYTES, normalizeMessageListenerPreviewLimit, previewMessageListenerMatches, buildListenerBotAppIdToOpenId, collectListenerBotAppIds, renderMessageListenerInstruction, } from '../services/message-listener.js';
|
|
99
104
|
import { createMessageListenerRunPreview, createMessageListenerRunPreviewTurnId, getMessageListenerRunPreview, markMessageListenerRunPreviewFailed, markMessageListenerRunPreviewTriggered, } from '../services/message-listener-run-preview-store.js';
|
|
100
105
|
import { listChatMemberDisplays } from '../services/groups-store.js';
|
|
101
106
|
const MESSAGE_LISTENER_PREVIEW_WINDOW_MS = 24 * 60 * 60 * 1000;
|
|
107
|
+
import { SUPERVISOR_SHUTDOWN_ROUTE, isExactSupervisorShutdownRequest, } from './supervisor-shutdown-ipc.js';
|
|
102
108
|
let exactChatGrantHandler = applyExactChatGrantRequest;
|
|
103
109
|
/** Test seam: replace the exact-grant service without touching live Feishu/config state. */
|
|
104
110
|
export function setExactChatGrantHandler(handler) {
|
|
@@ -112,6 +118,10 @@ let botAvatarChanger = null;
|
|
|
112
118
|
export function setBotAvatarChanger(fn) {
|
|
113
119
|
botAvatarChanger = fn;
|
|
114
120
|
}
|
|
121
|
+
let supervisorShutdownRegistration = null;
|
|
122
|
+
export function setSupervisorShutdownHandler(registration) {
|
|
123
|
+
supervisorShutdownRegistration = registration;
|
|
124
|
+
}
|
|
115
125
|
import { composeRowFromActive, composeRowFromClosed, composeRowFromPersistedActive, feishuChatLink, setBotName as setRowsBotName, getBotName, } from './dashboard-rows.js';
|
|
116
126
|
import { getBotBrand, getBot, getBotOpenId, loadBotConfigs, readBotSkillPolicy, getBotTuiSlashAllow } from '../bot-registry.js';
|
|
117
127
|
import { normalizeKanbanColumn, normalizeKanbanPosition, normalizeSessionTitle } from './session-board.js';
|
|
@@ -123,6 +133,7 @@ import { normalizeSessionTitleSource, updateSessionTitle } from './session-title
|
|
|
123
133
|
import { requestAgentSessionRename } from './session-rename.js';
|
|
124
134
|
import { ChatRenameCooldown, ChatRenameSerialQueue, normalizeLarkChatName } from './chat-rename.js';
|
|
125
135
|
import { sessionAnchorId, larkTransportEnabled } from './types.js';
|
|
136
|
+
import { isRiffBackendSession } from './persistent-backend.js';
|
|
126
137
|
import { attachSkillPolicy, detachSkillPolicy } from './skills/im-command.js';
|
|
127
138
|
import { readSkillRegistry } from '../services/skill-registry-store.js';
|
|
128
139
|
import { commitDeviceIsolationActivation, DEVICE_ISOLATION_COMMIT_PATH, DEVICE_ISOLATION_PREPARE_PATH, DEVICE_ISOLATION_RELEASE_PATH, logDeviceIsolationActivationError, prepareDeviceIsolationActivation, releaseDeviceIsolationActivation, } from './device-isolation-daemon.js';
|
|
@@ -149,6 +160,73 @@ export function jsonRes(res, status, body) {
|
|
|
149
160
|
res.writeHead(status, { 'content-type': 'application/json' });
|
|
150
161
|
res.end(JSON.stringify(body));
|
|
151
162
|
}
|
|
163
|
+
function rejectProtectedSessionMutation(res, values) {
|
|
164
|
+
const bySessionId = new Map();
|
|
165
|
+
for (const value of values) {
|
|
166
|
+
const session = 'session' in value ? value.session : value;
|
|
167
|
+
const reasons = protectedSessionMutationReasons(value);
|
|
168
|
+
if (reasons.length === 0)
|
|
169
|
+
continue;
|
|
170
|
+
const existing = bySessionId.get(session.sessionId);
|
|
171
|
+
if (existing) {
|
|
172
|
+
existing.reasons = [...new Set([...existing.reasons, ...reasons])];
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
bySessionId.set(session.sessionId, {
|
|
176
|
+
sessionId: session.sessionId,
|
|
177
|
+
...(session.cliId ? { cliId: session.cliId } : {}),
|
|
178
|
+
reasons,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
const blockingSessions = [...bySessionId.values()];
|
|
182
|
+
if (blockingSessions.length === 0)
|
|
183
|
+
return false;
|
|
184
|
+
const codexDispatchOnly = blockingSessions.every(blocker => blocker.reasons.every(reason => reason === 'codex_app_dispatch'));
|
|
185
|
+
jsonRes(res, 409, {
|
|
186
|
+
ok: false,
|
|
187
|
+
error: codexDispatchOnly
|
|
188
|
+
? 'codex_app_dispatch_pending'
|
|
189
|
+
: 'session_mutation_pending',
|
|
190
|
+
blockingSessions,
|
|
191
|
+
});
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
ipcRoute('POST', SUPERVISOR_SHUTDOWN_ROUTE, async (req, res) => {
|
|
195
|
+
// The production server-wide HMAC gate records trusted requests here. Keep
|
|
196
|
+
// an explicit route-local check: shutdown is never a bare loopback API.
|
|
197
|
+
if (!isTrustedHostIpcRequest(req)) {
|
|
198
|
+
return jsonRes(res, 403, { ok: false, error: 'supervisor_shutdown_unauthorized' });
|
|
199
|
+
}
|
|
200
|
+
const registration = supervisorShutdownRegistration;
|
|
201
|
+
if (!registration) {
|
|
202
|
+
return jsonRes(res, 503, { ok: false, error: 'supervisor_shutdown_not_ready' });
|
|
203
|
+
}
|
|
204
|
+
let body;
|
|
205
|
+
try {
|
|
206
|
+
body = await readJsonBody(req);
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
return jsonRes(res, 400, { ok: false, error: 'invalid_json' });
|
|
210
|
+
}
|
|
211
|
+
if (!isExactSupervisorShutdownRequest(registration, body)) {
|
|
212
|
+
return jsonRes(res, 409, { ok: false, error: 'supervisor_shutdown_generation_mismatch' });
|
|
213
|
+
}
|
|
214
|
+
// ACK means this exact in-memory generation accepted the request; the CLI
|
|
215
|
+
// still proves OS/PM2 quiescence. Start after flushing the ACK so a long Riff
|
|
216
|
+
// drain cannot turn a valid request into an ambiguous transport timeout.
|
|
217
|
+
jsonRes(res, 202, {
|
|
218
|
+
ok: true,
|
|
219
|
+
accepted: true,
|
|
220
|
+
larkAppId: registration.larkAppId,
|
|
221
|
+
bootInstanceId: registration.bootInstanceId,
|
|
222
|
+
processStartIdentity: registration.processStartIdentity,
|
|
223
|
+
});
|
|
224
|
+
setImmediate(() => {
|
|
225
|
+
void registration.shutdown().catch(error => {
|
|
226
|
+
logger.error(`supervisor shutdown failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
});
|
|
152
230
|
export class JsonBodyTooLargeError extends Error {
|
|
153
231
|
maxBytes;
|
|
154
232
|
constructor(maxBytes) {
|
|
@@ -285,6 +363,81 @@ export async function readJsonBody(req, maxBytes) {
|
|
|
285
363
|
return {};
|
|
286
364
|
return JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
287
365
|
}
|
|
366
|
+
class IpcBodyTooLargeError extends Error {
|
|
367
|
+
}
|
|
368
|
+
class IpcBodyTimeoutError extends Error {
|
|
369
|
+
}
|
|
370
|
+
/** Strict reader for the one unauthenticated capability challenge route. The
|
|
371
|
+
* generic IPC reader intentionally has no cap for trusted-host endpoints; a
|
|
372
|
+
* loopback-confined process must not be able to buffer arbitrary chunked input
|
|
373
|
+
* before its capability is checked. */
|
|
374
|
+
async function readBoundedJsonBody(req, maxBytes, timeoutMs) {
|
|
375
|
+
const contentLength = req.headers['content-length'];
|
|
376
|
+
if (typeof contentLength === 'string') {
|
|
377
|
+
if (!/^\d+$/.test(contentLength) || Number(contentLength) > maxBytes) {
|
|
378
|
+
throw new IpcBodyTooLargeError('request body too large');
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return await new Promise((resolve, reject) => {
|
|
382
|
+
const chunks = [];
|
|
383
|
+
let total = 0;
|
|
384
|
+
let settled = false;
|
|
385
|
+
const cleanup = () => {
|
|
386
|
+
clearTimeout(timer);
|
|
387
|
+
req.off('data', onData);
|
|
388
|
+
req.off('end', onEnd);
|
|
389
|
+
req.off('error', onError);
|
|
390
|
+
req.off('aborted', onAborted);
|
|
391
|
+
};
|
|
392
|
+
const fail = (err) => {
|
|
393
|
+
if (settled)
|
|
394
|
+
return;
|
|
395
|
+
settled = true;
|
|
396
|
+
req.pause();
|
|
397
|
+
cleanup();
|
|
398
|
+
reject(err);
|
|
399
|
+
};
|
|
400
|
+
const onData = (raw) => {
|
|
401
|
+
const chunk = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
|
|
402
|
+
total += chunk.length;
|
|
403
|
+
if (total > maxBytes) {
|
|
404
|
+
fail(new IpcBodyTooLargeError('request body too large'));
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
chunks.push(chunk);
|
|
408
|
+
};
|
|
409
|
+
const onEnd = () => {
|
|
410
|
+
if (settled)
|
|
411
|
+
return;
|
|
412
|
+
settled = true;
|
|
413
|
+
cleanup();
|
|
414
|
+
try {
|
|
415
|
+
resolve(chunks.length === 0
|
|
416
|
+
? {}
|
|
417
|
+
: JSON.parse(Buffer.concat(chunks, total).toString('utf8')));
|
|
418
|
+
}
|
|
419
|
+
catch (err) {
|
|
420
|
+
reject(err);
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
const onError = (err) => fail(err);
|
|
424
|
+
const onAborted = () => fail(new Error('request aborted'));
|
|
425
|
+
const timer = setTimeout(() => fail(new IpcBodyTimeoutError('request body timed out')), timeoutMs);
|
|
426
|
+
timer.unref?.();
|
|
427
|
+
req.on('data', onData);
|
|
428
|
+
req.once('end', onEnd);
|
|
429
|
+
req.once('error', onError);
|
|
430
|
+
req.once('aborted', onAborted);
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
function closeUntrustedRequestAfterResponse(req, res) {
|
|
434
|
+
res.setHeader('connection', 'close');
|
|
435
|
+
res.once('finish', () => req.destroy());
|
|
436
|
+
// Drain whatever is already buffered until the response has been flushed;
|
|
437
|
+
// the finish hook then closes a partial/slow body instead of reusing it as a
|
|
438
|
+
// keep-alive request.
|
|
439
|
+
req.resume();
|
|
440
|
+
}
|
|
288
441
|
// ─── Trusted-host auth (loopback + route-bound HMAC) ────────────────────────
|
|
289
442
|
//
|
|
290
443
|
// Production start enables a server-wide gate: loopback is connectivity, not
|
|
@@ -381,6 +534,11 @@ function routeHasNarrowUntrustedAuth(method, pathname) {
|
|
|
381
534
|
return true;
|
|
382
535
|
if (method === 'POST' && pathname === '/api/attention')
|
|
383
536
|
return true;
|
|
537
|
+
// macOS read-isolated `botmux send` presents a rotating worker capability;
|
|
538
|
+
// the handler writes the authoritative tuple into a host-owned read-only
|
|
539
|
+
// proof sidecar, so loopback response spoofing cannot confer authority.
|
|
540
|
+
if (method === 'POST' && pathname === MANAGED_ORIGIN_ATTEST_ROUTE)
|
|
541
|
+
return true;
|
|
384
542
|
// Workflow v3 mutations carry their own domain-separated full-envelope
|
|
385
543
|
// protocol (request signature over method/path/exact body with nonce
|
|
386
544
|
// anti-replay + boot audience, signed response), keyed on the same host
|
|
@@ -439,6 +597,152 @@ ipcRoute('GET', '/healthz', (_req, res) => {
|
|
|
439
597
|
}
|
|
440
598
|
jsonRes(res, 200, { ok: true });
|
|
441
599
|
});
|
|
600
|
+
const MANAGED_ORIGIN_ATTEST_BODY_MAX_BYTES = 2 * 1024;
|
|
601
|
+
const MANAGED_ORIGIN_ATTEST_BODY_TIMEOUT_MS = 1_000;
|
|
602
|
+
const MANAGED_ORIGIN_ATTEST_MAX_PREAUTH_IN_FLIGHT = 128;
|
|
603
|
+
const MANAGED_ORIGIN_ATTEST_MAX_OUTSTANDING_PER_SESSION = 64;
|
|
604
|
+
const managedOriginOutstandingProofs = new Map();
|
|
605
|
+
let managedOriginPreauthInFlight = 0;
|
|
606
|
+
async function handleManagedOriginAttestation(req, res) {
|
|
607
|
+
let body;
|
|
608
|
+
try {
|
|
609
|
+
body = await readBoundedJsonBody(req, MANAGED_ORIGIN_ATTEST_BODY_MAX_BYTES, MANAGED_ORIGIN_ATTEST_BODY_TIMEOUT_MS);
|
|
610
|
+
}
|
|
611
|
+
catch (err) {
|
|
612
|
+
if (err instanceof IpcBodyTooLargeError || err instanceof IpcBodyTimeoutError) {
|
|
613
|
+
closeUntrustedRequestAfterResponse(req, res);
|
|
614
|
+
}
|
|
615
|
+
return jsonRes(res, err instanceof IpcBodyTooLargeError
|
|
616
|
+
? 413
|
|
617
|
+
: err instanceof IpcBodyTimeoutError
|
|
618
|
+
? 408
|
|
619
|
+
: 400, {
|
|
620
|
+
ok: false,
|
|
621
|
+
error: err instanceof IpcBodyTooLargeError
|
|
622
|
+
? 'body_too_large'
|
|
623
|
+
: err instanceof IpcBodyTimeoutError
|
|
624
|
+
? 'body_timeout'
|
|
625
|
+
: 'bad_json',
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) {
|
|
629
|
+
return jsonRes(res, 400, { ok: false, error: 'bad_attestation_request' });
|
|
630
|
+
}
|
|
631
|
+
const sessionId = typeof body.sessionId === 'string' && body.sessionId.length <= 256
|
|
632
|
+
? body.sessionId
|
|
633
|
+
: '';
|
|
634
|
+
const capability = typeof body.originCapability === 'string'
|
|
635
|
+
&& /^[a-f0-9]{32,128}$/i.test(body.originCapability)
|
|
636
|
+
? body.originCapability
|
|
637
|
+
: '';
|
|
638
|
+
const channelId = typeof (body.originChannelId ?? body.channelId) === 'string'
|
|
639
|
+
&& /^[a-f0-9]{64}$/.test((body.originChannelId ?? body.channelId))
|
|
640
|
+
? (body.originChannelId ?? body.channelId)
|
|
641
|
+
: '';
|
|
642
|
+
const nonce = typeof body.nonce === 'string' && /^[a-f0-9]{64}$/.test(body.nonce)
|
|
643
|
+
? body.nonce
|
|
644
|
+
: '';
|
|
645
|
+
if (!sessionId || !channelId || !capability || !nonce) {
|
|
646
|
+
return jsonRes(res, 400, { ok: false, error: 'bad_attestation_request' });
|
|
647
|
+
}
|
|
648
|
+
const ds = findActiveBySessionId(sessionId);
|
|
649
|
+
const worker = ds?.worker;
|
|
650
|
+
let workerPidLive = false;
|
|
651
|
+
if (worker && Number.isSafeInteger(worker.pid) && (worker.pid ?? 0) > 0) {
|
|
652
|
+
try {
|
|
653
|
+
process.kill(worker.pid, 0);
|
|
654
|
+
workerPidLive = true;
|
|
655
|
+
}
|
|
656
|
+
catch { /* ESRCH/EPERM/invalid pid all fail closed */ }
|
|
657
|
+
}
|
|
658
|
+
const workerLive = !!worker
|
|
659
|
+
&& worker.connected === true
|
|
660
|
+
&& !worker.killed
|
|
661
|
+
&& worker.exitCode === null
|
|
662
|
+
&& worker.signalCode === null
|
|
663
|
+
&& workerPidLive;
|
|
664
|
+
const liveTurnId = ds?.managedTurnOrigin?.turnId;
|
|
665
|
+
const verified = authorizeSessionScopedIpc({
|
|
666
|
+
trustedHost: false,
|
|
667
|
+
sessionExists: !!ds,
|
|
668
|
+
receiverSession: !!ds?.session.vcMeetingReceiver,
|
|
669
|
+
allowReceiver: true,
|
|
670
|
+
sessionId,
|
|
671
|
+
liveOrigin: ds?.managedTurnOrigin,
|
|
672
|
+
claimedCapability: capability,
|
|
673
|
+
});
|
|
674
|
+
if (!verified.ok || !ds?.managedTurnOrigin || !liveTurnId || !workerLive) {
|
|
675
|
+
return jsonRes(res, 403, { ok: false, error: 'origin_unproven' });
|
|
676
|
+
}
|
|
677
|
+
const origin = ds.managedTurnOrigin;
|
|
678
|
+
if (!origin.originChannelId || !/^[a-f0-9]{64}$/.test(origin.originChannelId)) {
|
|
679
|
+
return jsonRes(res, 403, { ok: false, error: 'origin_channel_unproven' });
|
|
680
|
+
}
|
|
681
|
+
if (channelId !== origin.originChannelId) {
|
|
682
|
+
return jsonRes(res, 403, { ok: false, error: 'origin_channel_unproven' });
|
|
683
|
+
}
|
|
684
|
+
const codexDecision = validateCodexAppManagedSendOrigin(ds.session.codexAppDispatchLedger, origin, ds.initConfig?.cliId === 'codex-app' || ds.session.cliId === 'codex-app');
|
|
685
|
+
if (!codexDecision.ok) {
|
|
686
|
+
return jsonRes(res, 409, { ok: false, error: 'origin_not_sendable' });
|
|
687
|
+
}
|
|
688
|
+
const outstanding = managedOriginOutstandingProofs.get(sessionId) ?? 0;
|
|
689
|
+
if (outstanding >= MANAGED_ORIGIN_ATTEST_MAX_OUTSTANDING_PER_SESSION) {
|
|
690
|
+
return jsonRes(res, 429, { ok: false, error: 'too_many_attestations' });
|
|
691
|
+
}
|
|
692
|
+
let proofPath;
|
|
693
|
+
try {
|
|
694
|
+
proofPath = writeManagedOriginAttestationProof({
|
|
695
|
+
dataDir: config.session.dataDir,
|
|
696
|
+
proof: {
|
|
697
|
+
domain: MANAGED_ORIGIN_PROOF_DOMAIN,
|
|
698
|
+
version: 1,
|
|
699
|
+
nonce,
|
|
700
|
+
channelId: origin.originChannelId,
|
|
701
|
+
sessionId,
|
|
702
|
+
turnId: liveTurnId,
|
|
703
|
+
...(origin.dispatchAttempt !== undefined
|
|
704
|
+
? { dispatchAttempt: origin.dispatchAttempt }
|
|
705
|
+
: {}),
|
|
706
|
+
requiresCodexAppLedger: codexDecision.requiresLedger,
|
|
707
|
+
issuedAtMs: Date.now(),
|
|
708
|
+
},
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
catch (err) {
|
|
712
|
+
logger.warn(`[managed-origin] could not write attestation proof: ${err}`);
|
|
713
|
+
return jsonRes(res, 409, { ok: false, error: 'proof_unavailable' });
|
|
714
|
+
}
|
|
715
|
+
managedOriginOutstandingProofs.set(sessionId, outstanding + 1);
|
|
716
|
+
const cleanupTimer = setTimeout(() => {
|
|
717
|
+
try {
|
|
718
|
+
unlinkSync(proofPath);
|
|
719
|
+
}
|
|
720
|
+
catch { /* expired/already gone */ }
|
|
721
|
+
const remaining = (managedOriginOutstandingProofs.get(sessionId) ?? 1) - 1;
|
|
722
|
+
if (remaining > 0)
|
|
723
|
+
managedOriginOutstandingProofs.set(sessionId, remaining);
|
|
724
|
+
else
|
|
725
|
+
managedOriginOutstandingProofs.delete(sessionId);
|
|
726
|
+
}, MANAGED_ORIGIN_PROOF_TTL_MS + 1_000);
|
|
727
|
+
cleanupTimer.unref?.();
|
|
728
|
+
return jsonRes(res, 200, { ok: true });
|
|
729
|
+
}
|
|
730
|
+
ipcRoute('POST', MANAGED_ORIGIN_ATTEST_ROUTE, async (req, res) => {
|
|
731
|
+
// This counter is acquired before parsing or capability lookup. Per-session
|
|
732
|
+
// proof quotas cannot protect the unauthenticated slow-body phase because a
|
|
733
|
+
// session id is not trustworthy until the complete request has been read.
|
|
734
|
+
if (managedOriginPreauthInFlight >= MANAGED_ORIGIN_ATTEST_MAX_PREAUTH_IN_FLIGHT) {
|
|
735
|
+
closeUntrustedRequestAfterResponse(req, res);
|
|
736
|
+
return jsonRes(res, 429, { ok: false, error: 'too_many_attestation_requests' });
|
|
737
|
+
}
|
|
738
|
+
managedOriginPreauthInFlight += 1;
|
|
739
|
+
try {
|
|
740
|
+
await handleManagedOriginAttestation(req, res);
|
|
741
|
+
}
|
|
742
|
+
finally {
|
|
743
|
+
managedOriginPreauthInFlight -= 1;
|
|
744
|
+
}
|
|
745
|
+
});
|
|
442
746
|
export { composeRowFromActive, composeRowFromClosed, composeRowFromPersistedActive };
|
|
443
747
|
// Re-export setBotName for backwards-compatible imports (daemon.ts). Both
|
|
444
748
|
// callers (this module's cachedBotName + dashboard-rows' cachedBotName) need
|
|
@@ -562,8 +866,48 @@ ipcRoute('POST', '/api/sessions/:sessionId/close', async (req, res, params) => {
|
|
|
562
866
|
const auth = sessionCliIpcAuth(req, ds, params.sessionId, body);
|
|
563
867
|
if (!auth.ok)
|
|
564
868
|
return jsonRes(res, 403, { ok: false, error: auth.error });
|
|
565
|
-
const
|
|
566
|
-
|
|
869
|
+
const initial = findSessionRecord(params.sessionId);
|
|
870
|
+
// Resolve the owning bot for the FIFO-drain gate from the live DaemonSession
|
|
871
|
+
// first (it carries larkAppId even before a persisted record exists), then the
|
|
872
|
+
// stored row, then this daemon's own identity. A trusted-host close of an
|
|
873
|
+
// already-missing session has no owner to key the gate on, so close directly:
|
|
874
|
+
// closeSession is idempotent and reports alreadyClosed.
|
|
875
|
+
const larkAppId = ds?.larkAppId || initial?.larkAppId || cachedLarkAppId;
|
|
876
|
+
if (!larkAppId) {
|
|
877
|
+
const r = await closeSession(params.sessionId);
|
|
878
|
+
return jsonRes(res, r.ok ? 200 : 502, r);
|
|
879
|
+
}
|
|
880
|
+
return withBotTurnMutation(larkAppId, async () => {
|
|
881
|
+
// Re-resolve only after every earlier admitted turn has drained. Explicit
|
|
882
|
+
// close is the abandon boundary and may clear accepted FIFO, but it must
|
|
883
|
+
// never clear a stale pre-drain snapshot while a turn is still preparing.
|
|
884
|
+
const current = findSessionRecord(params.sessionId);
|
|
885
|
+
if (current && current.status === 'closed') {
|
|
886
|
+
return jsonRes(res, 200, { ok: true, alreadyClosed: true });
|
|
887
|
+
}
|
|
888
|
+
const r = await closeSession(params.sessionId);
|
|
889
|
+
jsonRes(res, r.ok ? 200 : 502, r);
|
|
890
|
+
});
|
|
891
|
+
});
|
|
892
|
+
// `botmux list` zombie pruning is maintenance, not explicit abandon. Serialize
|
|
893
|
+
// against inbound admission and refuse if any backend-neutral durable owner
|
|
894
|
+
// became unsettled after the CLI took its liveness snapshot.
|
|
895
|
+
ipcRoute('POST', '/api/sessions/:sessionId/prune', async (_req, res, params) => {
|
|
896
|
+
const initial = findSessionRecord(params.sessionId);
|
|
897
|
+
if (!initial)
|
|
898
|
+
return jsonRes(res, 404, { ok: false, error: 'session_not_found' });
|
|
899
|
+
const larkAppId = initial.larkAppId || cachedLarkAppId;
|
|
900
|
+
if (!larkAppId)
|
|
901
|
+
return jsonRes(res, 503, { ok: false, error: 'bot_not_found' });
|
|
902
|
+
return withBotTurnMutation(larkAppId, async () => {
|
|
903
|
+
const current = findSessionRecord(params.sessionId);
|
|
904
|
+
if (!current)
|
|
905
|
+
return jsonRes(res, 404, { ok: false, error: 'session_not_found' });
|
|
906
|
+
if (rejectProtectedSessionMutation(res, [current]))
|
|
907
|
+
return;
|
|
908
|
+
const r = await closeSession(params.sessionId);
|
|
909
|
+
jsonRes(res, r.ok ? 200 : 502, r);
|
|
910
|
+
});
|
|
567
911
|
});
|
|
568
912
|
/** Post a scope-aware "restarting" notice into the session's Lark thread/chat,
|
|
569
913
|
* mirroring the /resume route — so a Feishu-side observer sees why the CLI just
|
|
@@ -599,69 +943,93 @@ function postRestartNotice(ds, fresh) {
|
|
|
599
943
|
.catch(err => logger.debug(`[restart] failed to post thread-scope restart notice: ${err}`));
|
|
600
944
|
}
|
|
601
945
|
}
|
|
602
|
-
ipcRoute('POST', '/api/sessions/:sessionId/restart', (_req, res, params) => {
|
|
603
|
-
const
|
|
604
|
-
if (!
|
|
946
|
+
ipcRoute('POST', '/api/sessions/:sessionId/restart', async (_req, res, params) => {
|
|
947
|
+
const initial = findActiveBySessionId(params.sessionId);
|
|
948
|
+
if (!initial)
|
|
605
949
|
return jsonRes(res, 404, { ok: false, error: 'session_not_active' });
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
return jsonRes(res, 409, { ok: false, error: 'adopt_restart_unsupported' });
|
|
613
|
-
}
|
|
614
|
-
const cliId = ds.session.cliId ?? 'unknown';
|
|
615
|
-
if (ds.worker && !ds.worker.killed) {
|
|
616
|
-
// Live worker → in-place CLI restart (kills the CLI, respawns with --resume).
|
|
617
|
-
// 捎带最新 per-bot env:dashboard 改完 env 后重启才真正生效(与 /restart 同逻辑)。
|
|
618
|
-
try {
|
|
619
|
-
ds.workerReady = false;
|
|
620
|
-
ds.worker.send({ type: 'restart', env: latestPerBotEnvForRestart(ds) });
|
|
950
|
+
return withBotTurnMutation(initial.larkAppId, () => {
|
|
951
|
+
const ds = findActiveBySessionId(params.sessionId);
|
|
952
|
+
if (!ds)
|
|
953
|
+
return jsonRes(res, 404, { ok: false, error: 'session_not_active' });
|
|
954
|
+
if (isSessionTransferring(ds)) {
|
|
955
|
+
return jsonRes(res, 409, { ok: false, error: 'session_transferring' });
|
|
621
956
|
}
|
|
622
|
-
|
|
623
|
-
|
|
957
|
+
// Adopt/observed sessions: botmux never owned the CLI — restarting would kill
|
|
958
|
+
// the user's real tmux/zellij pane. Hard-reject (the worker self-guards too).
|
|
959
|
+
if (ds.adoptedFrom || ds.initConfig?.adoptMode) {
|
|
960
|
+
return jsonRes(res, 409, { ok: false, error: 'adopt_restart_unsupported' });
|
|
961
|
+
}
|
|
962
|
+
// Riff owns a remote task lineage. Its worker deliberately refuses restart
|
|
963
|
+
// because destroy + respawn would sever or replace that lineage. Reject at
|
|
964
|
+
// the daemon boundary so the dashboard never reports HTTP 200 for a no-op.
|
|
965
|
+
if (isRiffBackendSession(ds)) {
|
|
966
|
+
return jsonRes(res, 409, {
|
|
967
|
+
ok: false,
|
|
968
|
+
error: 'riff_restart_unsupported',
|
|
969
|
+
message: t('cmd.restart.riff_unsupported', undefined, localeForBot(ds.larkAppId)),
|
|
970
|
+
});
|
|
624
971
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
972
|
+
if (rejectProtectedSessionMutation(res, [ds]))
|
|
973
|
+
return;
|
|
974
|
+
const cliId = ds.session.cliId ?? 'unknown';
|
|
975
|
+
if (ds.worker && !ds.worker.killed) {
|
|
976
|
+
// Live worker → in-place CLI restart (kills the CLI, respawns with --resume).
|
|
977
|
+
// 捎带最新 per-bot env:dashboard 改完 env 后重启才真正生效(与 /restart 同逻辑)。
|
|
978
|
+
try {
|
|
979
|
+
ds.workerReady = false;
|
|
980
|
+
ds.worker.send({ type: 'restart', reason: 'operator', env: latestPerBotEnvForRestart(ds) });
|
|
981
|
+
}
|
|
982
|
+
catch (err) {
|
|
983
|
+
return jsonRes(res, 502, { ok: false, error: String(err) });
|
|
984
|
+
}
|
|
985
|
+
postRestartNotice(ds, false);
|
|
986
|
+
return jsonRes(res, 200, { ok: true, sessionId: params.sessionId, cliId, revived: false });
|
|
987
|
+
}
|
|
988
|
+
// Worker is gone but the session is still active — idle-suspended (over the
|
|
989
|
+
// per-bot cap), lazy-restored after a daemon restart, or crash-loop-stopped.
|
|
990
|
+
// Revive it the same way the Feishu card restart does (forkWorker), so the
|
|
991
|
+
// dashboard isn't a dead-end: a 409 here would leave NO working control to
|
|
992
|
+
// bring the CLI back (the resume button only shows for closed sessions).
|
|
993
|
+
forkWorker(ds, '', ds.hasHistory);
|
|
994
|
+
postRestartNotice(ds, true);
|
|
995
|
+
jsonRes(res, 200, { ok: true, sessionId: params.sessionId, cliId, revived: true });
|
|
996
|
+
});
|
|
636
997
|
});
|
|
637
998
|
/** Manually suspend one active session: kill the worker + CLI/pane, session
|
|
638
999
|
* stays active and cold-resumes from its transcript on the next message —
|
|
639
1000
|
* the same semantics the idle-worker sweeper applies over the live cap.
|
|
640
1001
|
* Primary use: `botmux suspend --isolated` after a credential rotation, so
|
|
641
1002
|
* isolated bots' next cold spawn re-provisions the freshest creds. */
|
|
642
|
-
ipcRoute('POST', '/api/sessions/:sessionId/suspend', (_req, res, params) => {
|
|
643
|
-
const
|
|
644
|
-
if (!
|
|
1003
|
+
ipcRoute('POST', '/api/sessions/:sessionId/suspend', async (_req, res, params) => {
|
|
1004
|
+
const initial = findActiveBySessionId(params.sessionId);
|
|
1005
|
+
if (!initial)
|
|
645
1006
|
return jsonRes(res, 404, { ok: false, error: 'session_not_active' });
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
1007
|
+
return withBotTurnMutation(initial.larkAppId, () => {
|
|
1008
|
+
const ds = findActiveBySessionId(params.sessionId);
|
|
1009
|
+
if (!ds)
|
|
1010
|
+
return jsonRes(res, 404, { ok: false, error: 'session_not_active' });
|
|
1011
|
+
if (isSessionTransferring(ds)) {
|
|
1012
|
+
return jsonRes(res, 409, { ok: false, error: 'session_transferring' });
|
|
1013
|
+
}
|
|
1014
|
+
// Adopt/observed sessions: botmux never owned the CLI — suspending would kill
|
|
1015
|
+
// the user's real tmux/zellij pane. Same guard as /restart.
|
|
1016
|
+
if (ds.adoptedFrom || ds.initConfig?.adoptMode) {
|
|
1017
|
+
return jsonRes(res, 409, { ok: false, error: 'adopt_suspend_unsupported' });
|
|
1018
|
+
}
|
|
1019
|
+
if (rejectProtectedSessionMutation(res, [ds]))
|
|
1020
|
+
return;
|
|
1021
|
+
if (!ds.worker || ds.worker.killed) {
|
|
1022
|
+
// Worker already gone (idle-suspended / crash-stopped) — the goal state is
|
|
1023
|
+
// already reached, so report idempotent success without a live kill.
|
|
1024
|
+
return jsonRes(res, 200, { ok: true, sessionId: params.sessionId, suspended: false, reason: 'no_live_worker' });
|
|
1025
|
+
}
|
|
1026
|
+
if (!suspendWorker(ds, 'manual_suspend')) {
|
|
1027
|
+
// Live worker but a non-suspendable (pty) backend: killing it would drop the
|
|
1028
|
+
// in-memory conversation with no persistent pane to resume from lazily.
|
|
1029
|
+
return jsonRes(res, 409, { ok: false, error: 'backend_not_suspendable' });
|
|
1030
|
+
}
|
|
1031
|
+
jsonRes(res, 200, { ok: true, sessionId: params.sessionId, suspended: true });
|
|
1032
|
+
});
|
|
665
1033
|
});
|
|
666
1034
|
/**
|
|
667
1035
|
* Count host-overload降压 candidates for THIS daemon's scope, so the alert card
|
|
@@ -916,6 +1284,16 @@ ipcRoute('POST', '/api/sessions/:sessionId/cd', async (req, res, params) => {
|
|
|
916
1284
|
if (ds.adoptedFrom || ds.initConfig?.adoptMode) {
|
|
917
1285
|
return jsonRes(res, 409, { ok: false, error: 'adopt_cd_unsupported' });
|
|
918
1286
|
}
|
|
1287
|
+
// Riff restart/kill is intentionally refused to preserve remote task
|
|
1288
|
+
// lineage. Reject before validation/repin so the persisted cwd cannot drift
|
|
1289
|
+
// from the still-running sandbox.
|
|
1290
|
+
if (isRiffBackendSession(ds)) {
|
|
1291
|
+
return jsonRes(res, 409, {
|
|
1292
|
+
ok: false,
|
|
1293
|
+
error: 'riff_cd_unsupported',
|
|
1294
|
+
message: t('cmd.cd.riff_unsupported', undefined, localeForBot(ds.larkAppId)),
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
919
1297
|
// ownAppId 收窄到本 bot 自己的角色库子树:不收窄就能切进别的 bot 的角色目录,
|
|
920
1298
|
// 下面 repinSessionWorkingDir 把 ds.workingDir 钉过去之后,那个 bot 的沙盒会话
|
|
921
1299
|
// 就拿到了对方整棵角色库的 readWrite(打穿 fs-policy 的跨 bot 隔离)。
|
|
@@ -1085,45 +1463,103 @@ ipcRoute('POST', '/api/sessions/:sessionId/board', async (req, res, params) => {
|
|
|
1085
1463
|
const session = findOwnedSessionRecord(params.sessionId);
|
|
1086
1464
|
if (!session)
|
|
1087
1465
|
return jsonRes(res, 404, { ok: false, error: 'session_not_found' });
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1466
|
+
const larkAppId = session.larkAppId || cachedLarkAppId;
|
|
1467
|
+
if (!larkAppId)
|
|
1468
|
+
return jsonRes(res, 503, { ok: false, error: 'bot_not_found' });
|
|
1469
|
+
return withBotTurnAdmission(larkAppId, async () => {
|
|
1470
|
+
const currentSession = findSessionRecord(params.sessionId);
|
|
1471
|
+
if (!currentSession)
|
|
1472
|
+
return jsonRes(res, 404, { ok: false, error: 'session_not_found' });
|
|
1473
|
+
// 待办池(queued)会话被拖到「进行中」= 激活:把暂存内容当首轮发给 CLI 开跑。
|
|
1474
|
+
// activateQueuedSession 内部会清 queued + 把列设成 in_progress + forkWorker。
|
|
1475
|
+
const activeDs = findActiveBySessionId(params.sessionId);
|
|
1476
|
+
let activationTransferred = false;
|
|
1477
|
+
if (column === 'in_progress' && activeDs?.session.queued) {
|
|
1478
|
+
const activated = await activateQueuedSession(activeDs);
|
|
1479
|
+
if (!activated.ok)
|
|
1480
|
+
return jsonRes(res, 500, activated);
|
|
1481
|
+
activationTransferred = true;
|
|
1482
|
+
}
|
|
1483
|
+
else if (column) {
|
|
1484
|
+
currentSession.kanbanColumn = column;
|
|
1485
|
+
}
|
|
1486
|
+
if (position !== null)
|
|
1487
|
+
currentSession.kanbanPosition = position;
|
|
1488
|
+
try {
|
|
1489
|
+
sessionStore.updateSession(currentSession);
|
|
1490
|
+
}
|
|
1491
|
+
catch (err) {
|
|
1492
|
+
if (!activationTransferred)
|
|
1493
|
+
throw err;
|
|
1494
|
+
logger.error(`[dashboard] board metadata persistence failed after queued activation ownership transferred: `
|
|
1495
|
+
+ `${err instanceof Error ? err.message : String(err)}`);
|
|
1496
|
+
}
|
|
1497
|
+
dashboardEventBus.publish({
|
|
1498
|
+
type: 'session.update',
|
|
1499
|
+
body: {
|
|
1500
|
+
sessionId: params.sessionId,
|
|
1501
|
+
// queued 一并下发:激活后 session.queued 已为 false,前端浅合并若不带这个字段
|
|
1502
|
+
// 会残留 queued=true(卡片仍显示「开始」、再点 409)。!!session.queued 始终反映现态。
|
|
1503
|
+
patch: { kanbanColumn: currentSession.kanbanColumn, kanbanPosition: currentSession.kanbanPosition, queued: !!currentSession.queued },
|
|
1504
|
+
},
|
|
1505
|
+
});
|
|
1506
|
+
jsonRes(res, 200, { ok: true });
|
|
1507
|
+
});
|
|
1508
|
+
});
|
|
1509
|
+
// Narrow CLI whiteboard binding mutation. Keeping this daemon-side avoids a
|
|
1510
|
+
// short-lived `botmux whiteboard` process rewriting a stale whole Session row
|
|
1511
|
+
// over a concurrent Codex App FIFO transition.
|
|
1512
|
+
ipcRoute('POST', '/api/sessions/:sessionId/whiteboard', async (req, res, params) => {
|
|
1513
|
+
let body;
|
|
1514
|
+
try {
|
|
1515
|
+
body = await readJsonBody(req);
|
|
1093
1516
|
}
|
|
1094
|
-
|
|
1095
|
-
|
|
1517
|
+
catch {
|
|
1518
|
+
return jsonRes(res, 400, { ok: false, error: 'bad_json' });
|
|
1096
1519
|
}
|
|
1097
|
-
if (
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
}
|
|
1520
|
+
if (typeof body.whiteboardId !== 'string'
|
|
1521
|
+
|| body.whiteboardId.length === 0
|
|
1522
|
+
|| body.whiteboardId.length > 256) {
|
|
1523
|
+
return jsonRes(res, 400, { ok: false, error: 'bad_whiteboard_id' });
|
|
1524
|
+
}
|
|
1525
|
+
const session = findSessionRecord(params.sessionId);
|
|
1526
|
+
if (!session)
|
|
1527
|
+
return jsonRes(res, 404, { ok: false, error: 'session_not_found' });
|
|
1528
|
+
const larkAppId = session.larkAppId || cachedLarkAppId;
|
|
1529
|
+
if (!larkAppId)
|
|
1530
|
+
return jsonRes(res, 503, { ok: false, error: 'bot_not_found' });
|
|
1531
|
+
return withBotTurnAdmission(larkAppId, async () => {
|
|
1532
|
+
const current = findSessionRecord(params.sessionId);
|
|
1533
|
+
if (!current)
|
|
1534
|
+
return jsonRes(res, 404, { ok: false, error: 'session_not_found' });
|
|
1535
|
+
current.whiteboardId = body.whiteboardId;
|
|
1536
|
+
sessionStore.updateSession(current);
|
|
1537
|
+
jsonRes(res, 200, { ok: true, whiteboardId: current.whiteboardId });
|
|
1108
1538
|
});
|
|
1109
|
-
jsonRes(res, 200, { ok: true });
|
|
1110
1539
|
});
|
|
1111
1540
|
// 待办池会话「开始」:把 parked 会话激活(发首轮、起 CLI),与拖到「进行中」同义。
|
|
1112
1541
|
ipcRoute('POST', '/api/sessions/:sessionId/start', async (_req, res, params) => {
|
|
1113
|
-
const
|
|
1114
|
-
if (!
|
|
1542
|
+
const initial = findActiveBySessionId(params.sessionId);
|
|
1543
|
+
if (!initial)
|
|
1115
1544
|
return jsonRes(res, 404, { ok: false, error: 'session_not_found' });
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1545
|
+
return withBotTurnAdmission(initial.larkAppId, async () => {
|
|
1546
|
+
const ds = findActiveBySessionId(params.sessionId);
|
|
1547
|
+
if (!ds)
|
|
1548
|
+
return jsonRes(res, 404, { ok: false, error: 'session_not_found' });
|
|
1549
|
+
if (!ds.session.queued)
|
|
1550
|
+
return jsonRes(res, 409, { ok: false, error: 'not_queued' });
|
|
1551
|
+
const r = await activateQueuedSession(ds);
|
|
1552
|
+
if (!r.ok)
|
|
1553
|
+
return jsonRes(res, 500, r);
|
|
1554
|
+
dashboardEventBus.publish({
|
|
1555
|
+
type: 'session.update',
|
|
1556
|
+
body: {
|
|
1557
|
+
sessionId: params.sessionId,
|
|
1558
|
+
patch: { kanbanColumn: ds.session.kanbanColumn, queued: !!ds.session.queued },
|
|
1559
|
+
},
|
|
1560
|
+
});
|
|
1561
|
+
jsonRes(res, 200, { ok: true });
|
|
1125
1562
|
});
|
|
1126
|
-
jsonRes(res, 200, { ok: true });
|
|
1127
1563
|
});
|
|
1128
1564
|
// Dashboard「创建会话」spawn:在新建的群里为本 daemon 的 bot 拉起/暂存一条 chat-scope
|
|
1129
1565
|
// 会话。aggregator 建完群后按模式(一起开工/lead 分配)对每个目标 bot 的 daemon 调一次。
|
|
@@ -1144,32 +1580,34 @@ ipcRoute('POST', '/api/sessions/spawn', async (req, res) => {
|
|
|
1144
1580
|
if (!parsed.ok)
|
|
1145
1581
|
return jsonRes(res, 400, { ok: false, error: parsed.error });
|
|
1146
1582
|
const postBanner = !!body.postBanner;
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1583
|
+
return withBotTurnAdmission(cachedLarkAppId, async () => {
|
|
1584
|
+
let attachments;
|
|
1585
|
+
try {
|
|
1586
|
+
attachments = materializeDashboardImages(cachedLarkAppId, parsed.value.images);
|
|
1587
|
+
}
|
|
1588
|
+
catch (err) {
|
|
1589
|
+
logger.error(`[createSession] failed to persist Dashboard images: ${err?.message ?? err}`);
|
|
1590
|
+
return jsonRes(res, 500, { ok: false, error: 'image_store_failed' });
|
|
1591
|
+
}
|
|
1592
|
+
const r = await spawnDashboardSession(activeSessions, undefined, {
|
|
1593
|
+
larkAppId: cachedLarkAppId,
|
|
1594
|
+
chatId: parsed.value.chatId,
|
|
1595
|
+
content: parsed.value.content,
|
|
1596
|
+
column: parsed.value.column,
|
|
1597
|
+
role: parsed.value.role,
|
|
1598
|
+
coworkers: parsed.value.coworkers,
|
|
1599
|
+
attachments,
|
|
1600
|
+
title: parsed.value.title,
|
|
1601
|
+
postBanner,
|
|
1602
|
+
ownerOpenId: parsed.value.ownerOpenId,
|
|
1603
|
+
ownerUnionId: parsed.value.ownerUnionId,
|
|
1604
|
+
});
|
|
1605
|
+
if (!r.ok) {
|
|
1606
|
+
cleanupMaterializedDashboardImages(cachedLarkAppId, attachments);
|
|
1607
|
+
return jsonRes(res, r.error === 'session_exists' ? 409 : 500, r);
|
|
1608
|
+
}
|
|
1609
|
+
jsonRes(res, 200, r);
|
|
1167
1610
|
});
|
|
1168
|
-
if (!r.ok) {
|
|
1169
|
-
cleanupMaterializedDashboardImages(cachedLarkAppId, attachments);
|
|
1170
|
-
return jsonRes(res, r.error === 'session_exists' ? 409 : 500, r);
|
|
1171
|
-
}
|
|
1172
|
-
jsonRes(res, 200, r);
|
|
1173
1611
|
});
|
|
1174
1612
|
ipcRoute('POST', '/api/chat-reply-mode', async (req, res) => {
|
|
1175
1613
|
if (!cachedLarkAppId)
|
|
@@ -1558,71 +1996,80 @@ function workingDirForSession(sessionId) {
|
|
|
1558
1996
|
*/
|
|
1559
1997
|
ipcRoute('POST', '/api/sessions/:sessionId/resume', async (req, res, params) => {
|
|
1560
1998
|
const sessionId = params.sessionId;
|
|
1561
|
-
const
|
|
1562
|
-
if (!
|
|
1563
|
-
return jsonRes(res,
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1999
|
+
const sourceSession = findSessionRecord(sessionId);
|
|
2000
|
+
if (!sourceSession)
|
|
2001
|
+
return jsonRes(res, 404, { ok: false, error: 'not_found' });
|
|
2002
|
+
// Legacy persisted sessions may carry an empty larkAppId and are hydrated
|
|
2003
|
+
// with this daemon's identity by resumeSession. Use the same fallback for
|
|
2004
|
+
// admission instead of rejecting an otherwise valid recovery record.
|
|
2005
|
+
const larkAppId = sourceSession.larkAppId || cachedLarkAppId || '__legacy_unbound__';
|
|
2006
|
+
return withBotTurnAdmission(larkAppId, async () => {
|
|
2007
|
+
const reg = getActiveSessionsRegistry();
|
|
2008
|
+
if (!reg)
|
|
2009
|
+
return jsonRes(res, 503, { ok: false, error: 'registry_unavailable' });
|
|
2010
|
+
const result = await resumeSession(sessionId, reg);
|
|
2011
|
+
if (!result.ok) {
|
|
2012
|
+
const status = result.error === 'not_found' ? 404 : 409;
|
|
2013
|
+
return jsonRes(res, status, { ok: false, error: result.error, activeSessionId: result.activeSessionId });
|
|
2014
|
+
}
|
|
2015
|
+
const ds = result.ds;
|
|
2016
|
+
// `?wake=1` is an opt-in operational hook (no UI/CLI caller wires it today —
|
|
2017
|
+
// it's meant for direct `curl` recovery): instead of the default lazy
|
|
2018
|
+
// cold-resume on the next inbound message, fork the worker immediately so the
|
|
2019
|
+
// session is usable right away. Off by default keeps every existing caller's
|
|
2020
|
+
// behaviour unchanged.
|
|
2021
|
+
const wake = new URL(req.url ?? '/', 'http://localhost').searchParams.get('wake') === '1';
|
|
2022
|
+
// Tell the dashboard the row flipped back to active (mirror of session.update
|
|
2023
|
+
// emitted by closeSession). Use `null` for closedAt — `undefined` would be
|
|
2024
|
+
// dropped by JSON.stringify on the SSE wire and the aggregator's spread
|
|
2025
|
+
// (`{...cur, ...patch}`) would leave the stale closedAt in place.
|
|
2026
|
+
dashboardEventBus.publish({
|
|
2027
|
+
type: 'session.update',
|
|
2028
|
+
body: {
|
|
2029
|
+
sessionId,
|
|
2030
|
+
patch: { status: 'active', closedAt: null },
|
|
2031
|
+
},
|
|
2032
|
+
});
|
|
2033
|
+
// Notify the original chat so humans see why the session is alive again.
|
|
2034
|
+
// Routing follows session.scope — thread-scope replies into the thread root
|
|
2035
|
+
// (reply_in_thread=true), chat-scope posts a plain message to the chat (any
|
|
2036
|
+
// reply_in_thread call would silently get rejected or land on a stale root).
|
|
2037
|
+
const cliId = ds.session.cliId;
|
|
2038
|
+
const botCfg = ds.larkAppId ? getBot(ds.larkAppId).config : undefined;
|
|
2039
|
+
const cliName = sessionConfiguredRuntimeDisplayName(ds.session, botCfg?.cliRuntime)
|
|
2040
|
+
?? getCliDisplayName(cliId ?? botCfg?.cliId ?? 'claude-code');
|
|
2041
|
+
const notice = JSON.stringify({ text: `🔄 会话已通过命令行恢复,发条消息继续与 ${cliName} 对话。` });
|
|
2042
|
+
if (ds.larkAppId && !sessionTransportDisabled(ds)) {
|
|
2043
|
+
if (ds.scope === 'chat' && ds.chatId) {
|
|
2044
|
+
getChatMode(ds.larkAppId, ds.chatId, { forceRefresh: true })
|
|
2045
|
+
.then((mode) => mode === 'topic' && ds.session.rootMessageId
|
|
2046
|
+
? replyMessage(ds.larkAppId, ds.session.rootMessageId, notice, 'text', true)
|
|
2047
|
+
: sendMessage(ds.larkAppId, ds.chatId, notice, 'text'))
|
|
2048
|
+
.catch(err => logger.debug(`[resume] failed to post chat-scope resume notice: ${err}`));
|
|
2049
|
+
}
|
|
2050
|
+
else if (ds.session.rootMessageId) {
|
|
2051
|
+
replyMessage(ds.larkAppId, ds.session.rootMessageId, notice, 'text', true)
|
|
2052
|
+
.catch(err => logger.debug(`[resume] failed to post thread-scope resume notice: ${err}`));
|
|
2053
|
+
}
|
|
1603
2054
|
}
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
2055
|
+
// Report the EFFECTIVE action, not the raw request flag: only fork when wake
|
|
2056
|
+
// was asked AND there's no live worker to clobber. (resumeSession always hands
|
|
2057
|
+
// back a worker:null ds today, so this matches `wake` in practice — but
|
|
2058
|
+
// reporting the action keeps the response honest if the guard ever broadens.)
|
|
2059
|
+
const woke = wake && (!ds.worker || ds.worker.killed);
|
|
2060
|
+
if (woke) {
|
|
2061
|
+
forkWorker(ds, '', true);
|
|
1607
2062
|
}
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
ok: true,
|
|
1619
|
-
sessionId,
|
|
1620
|
-
wake: woke,
|
|
1621
|
-
title: ds.session.title,
|
|
1622
|
-
chatId: ds.chatId,
|
|
1623
|
-
rootMessageId: ds.session.rootMessageId,
|
|
1624
|
-
workingDir: ds.session.workingDir,
|
|
1625
|
-
cliId,
|
|
2063
|
+
jsonRes(res, 200, {
|
|
2064
|
+
ok: true,
|
|
2065
|
+
sessionId,
|
|
2066
|
+
wake: woke,
|
|
2067
|
+
title: ds.session.title,
|
|
2068
|
+
chatId: ds.chatId,
|
|
2069
|
+
rootMessageId: ds.session.rootMessageId,
|
|
2070
|
+
workingDir: ds.session.workingDir,
|
|
2071
|
+
cliId,
|
|
2072
|
+
});
|
|
1626
2073
|
});
|
|
1627
2074
|
});
|
|
1628
2075
|
/**
|
|
@@ -2074,15 +2521,22 @@ ipcRoute('POST', '/api/trigger', async (req, res) => {
|
|
|
2074
2521
|
const result = await triggerSessionTurn(valid.request, { larkAppId: cachedLarkAppId, activeSessions });
|
|
2075
2522
|
const status = result.ok
|
|
2076
2523
|
? 200
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2524
|
+
// An idempotent retry that resolves to a durable `failed` async state is a
|
|
2525
|
+
// successful HTTP call reporting a terminal outcome (like a 200 completed/
|
|
2526
|
+
// queued), not a request error — surface it 200 so the caller reads `state`.
|
|
2527
|
+
: result.state === 'failed'
|
|
2528
|
+
? 200
|
|
2529
|
+
: result.errorCode === 'idempotency_conflict'
|
|
2530
|
+
? 409
|
|
2531
|
+
: result.errorCode === 'bot_not_in_chat'
|
|
2532
|
+
? 403
|
|
2533
|
+
: result.errorCode === 'session_not_found'
|
|
2534
|
+
? 404
|
|
2535
|
+
: result.errorCode === 'wait_timeout'
|
|
2536
|
+
? 504
|
|
2537
|
+
: result.errorCode === 'target_required' || result.errorCode === 'bad_request'
|
|
2538
|
+
? 400
|
|
2539
|
+
: 500;
|
|
2086
2540
|
return jsonRes(res, status, result);
|
|
2087
2541
|
}
|
|
2088
2542
|
catch (e) {
|
|
@@ -3021,6 +3475,7 @@ ipcRoute('GET', '/api/bot-default-oncall', async (_req, res) => {
|
|
|
3021
3475
|
restrictGrantCommands: grantPrefs.restrictGrantCommands,
|
|
3022
3476
|
autoGrantRequestCards: grantPrefs.autoGrantRequestCards,
|
|
3023
3477
|
messageQuotaDefaultLimit: grantPrefs.messageQuotaDefaultLimit,
|
|
3478
|
+
grantDefaultDurationMs: grantPrefs.grantDefaultDurationMs,
|
|
3024
3479
|
p2pMode,
|
|
3025
3480
|
skillInjection,
|
|
3026
3481
|
skillInjectionSupport,
|
|
@@ -3190,7 +3645,8 @@ ipcRoute('PUT', '/api/bot-summary-trigger', async (req, res) => {
|
|
|
3190
3645
|
// Per-bot 授权偏好。Body 任意子集:
|
|
3191
3646
|
// • restrictGrantCommands: boolean — 限制被授权人只能纯对话
|
|
3192
3647
|
// • autoGrantRequestCards: boolean — 未授权 @ 被挡住时是否发 grant 申请卡
|
|
3193
|
-
// • messageQuotaDefaultLimit: number|null —
|
|
3648
|
+
// • messageQuotaDefaultLimit: number|null — 卡片/Oncall 额度覆盖(null = 卡片内置 3 条、Oncall 不限)
|
|
3649
|
+
// • grantDefaultDurationMs: number|null — 新授权默认有限时长(null = 产品默认 1 小时)
|
|
3194
3650
|
ipcRoute('PUT', '/api/bot-grant-prefs', async (req, res) => {
|
|
3195
3651
|
if (!cachedLarkAppId)
|
|
3196
3652
|
return jsonRes(res, 503, { error: 'larkAppId_not_set' });
|
|
@@ -3211,11 +3667,16 @@ ipcRoute('PUT', '/api/bot-grant-prefs', async (req, res) => {
|
|
|
3211
3667
|
patch.restrictGrantCommands = body.restrictGrantCommands;
|
|
3212
3668
|
if (typeof body.autoGrantRequestCards === 'boolean')
|
|
3213
3669
|
patch.autoGrantRequestCards = body.autoGrantRequestCards;
|
|
3214
|
-
// null(含 JSON null)=
|
|
3670
|
+
// null(含 JSON null)= 恢复内置额度策略;number = 设定覆盖值(store 内校验 1–1000)。
|
|
3215
3671
|
if (body.messageQuotaDefaultLimit === null)
|
|
3216
3672
|
patch.messageQuotaDefaultLimit = null;
|
|
3217
3673
|
else if (typeof body.messageQuotaDefaultLimit === 'number')
|
|
3218
3674
|
patch.messageQuotaDefaultLimit = body.messageQuotaDefaultLimit;
|
|
3675
|
+
// null = 恢复产品默认 1 小时;number 由 store 按卡片有限选项白名单校验。
|
|
3676
|
+
if (body.grantDefaultDurationMs === null)
|
|
3677
|
+
patch.grantDefaultDurationMs = null;
|
|
3678
|
+
else if (typeof body.grantDefaultDurationMs === 'number')
|
|
3679
|
+
patch.grantDefaultDurationMs = body.grantDefaultDurationMs;
|
|
3219
3680
|
if (Object.keys(patch).length === 0)
|
|
3220
3681
|
return jsonRes(res, 400, { ok: false, error: 'no_valid_fields' });
|
|
3221
3682
|
const r = await grantPrefsStore.updateBotGrantPrefs(cachedLarkAppId, patch);
|
|
@@ -3343,6 +3804,7 @@ ipcRoute('PUT', '/api/bot-avatar', async (req, res) => {
|
|
|
3343
3804
|
ipcRoute('PUT', '/api/bot-agent', async (req, res) => {
|
|
3344
3805
|
if (!cachedLarkAppId)
|
|
3345
3806
|
return jsonRes(res, 503, { error: 'larkAppId_not_set' });
|
|
3807
|
+
const larkAppId = cachedLarkAppId;
|
|
3346
3808
|
let body;
|
|
3347
3809
|
try {
|
|
3348
3810
|
body = await readJsonBody(req);
|
|
@@ -3361,7 +3823,7 @@ ipcRoute('PUT', '/api/bot-agent', async (req, res) => {
|
|
|
3361
3823
|
return jsonRes(res, 400, { ok: false, error: 'invalid_cli', message: err?.message ?? String(err) });
|
|
3362
3824
|
}
|
|
3363
3825
|
const model = typeof body.model === 'string' ? body.model.trim() : '';
|
|
3364
|
-
const currentBotConfig = getBot(
|
|
3826
|
+
const currentBotConfig = getBot(larkAppId).config;
|
|
3365
3827
|
const runtimeFieldPresent = Object.prototype.hasOwnProperty.call(body, 'cliRuntime');
|
|
3366
3828
|
const currentSelectionKey = selectionKeyForBot(currentBotConfig.cliId, currentBotConfig.wrapperCli);
|
|
3367
3829
|
const selectionChanged = key !== currentSelectionKey;
|
|
@@ -3436,94 +3898,109 @@ ipcRoute('PUT', '/api/bot-agent', async (req, res) => {
|
|
|
3436
3898
|
const availabilityWarning = availability.available
|
|
3437
3899
|
? undefined
|
|
3438
3900
|
: `配置已保存,但所选 Agent 当前无法启动:${availability.reason ?? '本地启动依赖不可用'}。请先在 daemon 所在机器安装或修正 PATH / CLI 路径。`;
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3901
|
+
return withBotTurnMutation(larkAppId, async () => {
|
|
3902
|
+
// Agent selection can replace every live worker generation and may also
|
|
3903
|
+
// auto-clear readIsolation. Close admission and drain in-flight acceptance
|
|
3904
|
+
// before inspecting both the registry and restart source of truth. A
|
|
3905
|
+
// settings mutation is not an explicit abandon boundary: an unsettled
|
|
3906
|
+
// Codex App FIFO must survive unchanged for recovery.
|
|
3907
|
+
const activeBotSessions = listActiveSessions().filter(ds => ds.larkAppId === larkAppId);
|
|
3908
|
+
const persistedActiveBotSessions = sessionStore.listSessions().filter(session => session.status === 'active'
|
|
3909
|
+
&& (session.larkAppId === larkAppId || !session.larkAppId));
|
|
3910
|
+
if (rejectProtectedSessionMutation(res, [
|
|
3911
|
+
...activeBotSessions,
|
|
3912
|
+
...persistedActiveBotSessions,
|
|
3913
|
+
]))
|
|
3914
|
+
return;
|
|
3915
|
+
// If the new CLI/wrapper can no longer enforce a currently-on read isolation,
|
|
3916
|
+
// auto-clear the flag here so the next session doesn't fail-close on it. (The
|
|
3917
|
+
// read-isolation toggle validates at enable time; changing the agent afterwards
|
|
3918
|
+
// is the other way a bot could end up configured-but-unenforceable.)
|
|
3919
|
+
let readIsolationCleared = false;
|
|
3920
|
+
const r = await rmwBotEntry(larkAppId, (entry) => {
|
|
3921
|
+
entry.cliId = selected.cliId;
|
|
3922
|
+
if (selected.wrapperCli)
|
|
3923
|
+
entry.wrapperCli = selected.wrapperCli;
|
|
3924
|
+
else
|
|
3925
|
+
delete entry.wrapperCli;
|
|
3926
|
+
if (nextRuntime) {
|
|
3927
|
+
entry.cliRuntime = nextRuntime;
|
|
3928
|
+
// Downgrade shadow: older BotMux versions ignore cliRuntime but retain
|
|
3929
|
+
// cliPathOverride, so a rollback still launches this distribution.
|
|
3930
|
+
entry.cliPathOverride = nextRuntime.executable;
|
|
3931
|
+
}
|
|
3932
|
+
else if (nextLegacyPath) {
|
|
3933
|
+
entry.cliPathOverride = nextLegacyPath;
|
|
3934
|
+
delete entry.cliRuntime;
|
|
3935
|
+
}
|
|
3936
|
+
else {
|
|
3937
|
+
delete entry.cliRuntime;
|
|
3938
|
+
delete entry.cliPathOverride;
|
|
3939
|
+
}
|
|
3940
|
+
if (model)
|
|
3941
|
+
entry.model = model;
|
|
3942
|
+
else
|
|
3943
|
+
delete entry.model;
|
|
3944
|
+
if (entry.readIsolation === true &&
|
|
3945
|
+
!readIsolationEnforceableFor({ cliId: selected.cliId, cliPathOverride: effectivePath, wrapperCli: selected.wrapperCli })) {
|
|
3946
|
+
delete entry.readIsolation;
|
|
3947
|
+
readIsolationCleared = true;
|
|
3948
|
+
}
|
|
3949
|
+
// cliId=riff → backendType 自动设为 riff(否则 spawn 走 pty 后端找不到本地二进制)。
|
|
3950
|
+
if (selected.cliId === 'riff') {
|
|
3951
|
+
entry.backendType = 'riff';
|
|
3952
|
+
}
|
|
3953
|
+
else if (entry.backendType === 'riff') {
|
|
3954
|
+
// 从 riff 切回其它 CLI:清掉这个自动配对的 backend override,回落 daemon
|
|
3955
|
+
// 默认后端——否则新 CLI 会跑在 RiffBackend 上(PTY 分块输入被当成一串 riff
|
|
3956
|
+
// 任务)。手动的 pty/tmux/herdr/zellij override 不受影响(它们不会是 riff)。
|
|
3957
|
+
delete entry.backendType;
|
|
3958
|
+
}
|
|
3959
|
+
return { write: true, result: null };
|
|
3960
|
+
});
|
|
3961
|
+
if (!r.ok)
|
|
3962
|
+
return jsonRes(res, 400, { ok: false, error: r.reason });
|
|
3963
|
+
const bot = getBot(larkAppId);
|
|
3964
|
+
bot.config.cliId = selected.cliId;
|
|
3965
|
+
bot.config.cliRuntime = nextRuntime;
|
|
3966
|
+
bot.config.cliPathOverride = nextRuntime?.executable ?? nextLegacyPath;
|
|
3446
3967
|
if (selected.wrapperCli)
|
|
3447
|
-
|
|
3968
|
+
bot.config.wrapperCli = selected.wrapperCli;
|
|
3448
3969
|
else
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
// cliPathOverride, so a rollback still launches this distribution.
|
|
3454
|
-
entry.cliPathOverride = nextRuntime.executable;
|
|
3455
|
-
}
|
|
3456
|
-
else if (nextLegacyPath) {
|
|
3457
|
-
entry.cliPathOverride = nextLegacyPath;
|
|
3458
|
-
delete entry.cliRuntime;
|
|
3459
|
-
}
|
|
3460
|
-
else {
|
|
3461
|
-
delete entry.cliRuntime;
|
|
3462
|
-
delete entry.cliPathOverride;
|
|
3463
|
-
}
|
|
3464
|
-
if (model)
|
|
3465
|
-
entry.model = model;
|
|
3466
|
-
else
|
|
3467
|
-
delete entry.model;
|
|
3468
|
-
if (entry.readIsolation === true &&
|
|
3469
|
-
!readIsolationEnforceableFor({ cliId: selected.cliId, cliPathOverride: effectivePath, wrapperCli: selected.wrapperCli })) {
|
|
3470
|
-
delete entry.readIsolation;
|
|
3471
|
-
readIsolationCleared = true;
|
|
3472
|
-
}
|
|
3473
|
-
// cliId=riff → backendType 自动设为 riff(否则 spawn 走 pty 后端找不到本地二进制)。
|
|
3970
|
+
bot.config.wrapperCli = undefined;
|
|
3971
|
+
bot.config.model = model || undefined;
|
|
3972
|
+
if (readIsolationCleared)
|
|
3973
|
+
bot.config.readIsolation = false;
|
|
3474
3974
|
if (selected.cliId === 'riff') {
|
|
3475
|
-
|
|
3975
|
+
bot.config.backendType = 'riff';
|
|
3476
3976
|
}
|
|
3477
|
-
else if (
|
|
3478
|
-
|
|
3479
|
-
// 默认后端——否则新 CLI 会跑在 RiffBackend 上(PTY 分块输入被当成一串 riff
|
|
3480
|
-
// 任务)。手动的 pty/tmux/herdr/zellij override 不受影响(它们不会是 riff)。
|
|
3481
|
-
delete entry.backendType;
|
|
3977
|
+
else if (bot.config.backendType === 'riff') {
|
|
3978
|
+
bot.config.backendType = undefined;
|
|
3482
3979
|
}
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
const selectionKey = selectionKeyForBot(selected.cliId, selected.wrapperCli);
|
|
3508
|
-
jsonRes(res, 200, {
|
|
3509
|
-
ok: true,
|
|
3510
|
-
cliId: selected.cliId,
|
|
3511
|
-
cliRuntime: nextRuntime ?? null,
|
|
3512
|
-
cliPathOverride: nextRuntime ? null : nextLegacyPath ?? null,
|
|
3513
|
-
wrapperCli: selected.wrapperCli ?? null,
|
|
3514
|
-
model: model || null,
|
|
3515
|
-
selectionKey,
|
|
3516
|
-
closedMismatchedSessions,
|
|
3517
|
-
// Report the (possibly auto-cleared) read-isolation state + whether the new
|
|
3518
|
-
// agent can still enforce it, so the dashboard updates its toggle immediately
|
|
3519
|
-
// instead of showing a stale enabled/supported state until a full refetch.
|
|
3520
|
-
readIsolation: bot.config.readIsolation === true,
|
|
3521
|
-
readIsolationSupported: readIsolationEnforceableFor(bot.config),
|
|
3522
|
-
readIsolationCleared,
|
|
3523
|
-
agentAvailable: availability.available,
|
|
3524
|
-
availabilityWarning,
|
|
3525
|
-
requiredCommand: availability.command,
|
|
3526
|
-
runtimeProbe,
|
|
3980
|
+
// 热切后立刻清掉本 bot 名下失配的存量会话——否则它们冻结的旧 CLI 会被下一条
|
|
3981
|
+
// 消息 lazy resume 复活,要等下次 daemon 重启才被 restore 守卫清理。
|
|
3982
|
+
const closedMismatchedSessions = await closeCliMismatchedSessionsForBot(larkAppId);
|
|
3983
|
+
const selectionKey = selectionKeyForBot(selected.cliId, selected.wrapperCli);
|
|
3984
|
+
jsonRes(res, 200, {
|
|
3985
|
+
ok: true,
|
|
3986
|
+
cliId: selected.cliId,
|
|
3987
|
+
cliRuntime: nextRuntime ?? null,
|
|
3988
|
+
cliPathOverride: nextRuntime ? null : nextLegacyPath ?? null,
|
|
3989
|
+
wrapperCli: selected.wrapperCli ?? null,
|
|
3990
|
+
model: model || null,
|
|
3991
|
+
selectionKey,
|
|
3992
|
+
closedMismatchedSessions,
|
|
3993
|
+
// Report the (possibly auto-cleared) read-isolation state + whether the new
|
|
3994
|
+
// agent can still enforce it, so the dashboard updates its toggle immediately
|
|
3995
|
+
// instead of showing a stale enabled/supported state until a full refetch.
|
|
3996
|
+
readIsolation: bot.config.readIsolation === true,
|
|
3997
|
+
readIsolationSupported: readIsolationEnforceableFor(bot.config),
|
|
3998
|
+
readIsolationCleared,
|
|
3999
|
+
agentAvailable: availability.available,
|
|
4000
|
+
availabilityWarning,
|
|
4001
|
+
requiredCommand: availability.command,
|
|
4002
|
+
runtimeProbe,
|
|
4003
|
+
});
|
|
3527
4004
|
});
|
|
3528
4005
|
});
|
|
3529
4006
|
// Per-bot 私聊单聊模式 p2pMode。Body `{ p2pMode: 'chat' | 'thread' }`:
|
|
@@ -3905,6 +4382,9 @@ ipcRoute('PUT', '/api/bot-sandbox', async (req, res) => {
|
|
|
3905
4382
|
catch {
|
|
3906
4383
|
return jsonRes(res, 400, { ok: false, error: 'bad_json' });
|
|
3907
4384
|
}
|
|
4385
|
+
// File-sandbox policy is frozen onto each Session at creation and reused on
|
|
4386
|
+
// restore; this toggle is intentionally next-session-only and cannot mutate
|
|
4387
|
+
// a live pane's profile.
|
|
3908
4388
|
const r = await sandboxStore.updateBotSandbox(cachedLarkAppId, body.enabled === true);
|
|
3909
4389
|
if (!r.ok)
|
|
3910
4390
|
return jsonRes(res, 400, { ok: false, error: r.reason });
|
|
@@ -3941,6 +4421,7 @@ ipcRoute('PUT', '/api/bot-sandbox-paths', async (req, res) => {
|
|
|
3941
4421
|
ipcRoute('PUT', '/api/bot-read-isolation', async (req, res) => {
|
|
3942
4422
|
if (!cachedLarkAppId)
|
|
3943
4423
|
return jsonRes(res, 503, { error: 'larkAppId_not_set' });
|
|
4424
|
+
const larkAppId = cachedLarkAppId;
|
|
3944
4425
|
let body;
|
|
3945
4426
|
try {
|
|
3946
4427
|
body = await readJsonBody(req);
|
|
@@ -3949,23 +4430,94 @@ ipcRoute('PUT', '/api/bot-read-isolation', async (req, res) => {
|
|
|
3949
4430
|
return jsonRes(res, 400, { ok: false, error: 'bad_json' });
|
|
3950
4431
|
}
|
|
3951
4432
|
const enable = body.enabled === true;
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
4433
|
+
return withBotTurnMutation(larkAppId, async () => {
|
|
4434
|
+
// An idempotent request changes neither the durable policy nor any pane.
|
|
4435
|
+
// Return before pending/active/teardown guards so a dashboard refresh that
|
|
4436
|
+
// repeats the authoritative value cannot be rejected merely because the
|
|
4437
|
+
// bot is doing work.
|
|
4438
|
+
if (sandboxStore.getBotReadIsolation(larkAppId) === enable) {
|
|
4439
|
+
return jsonRes(res, 200, {
|
|
4440
|
+
ok: true,
|
|
4441
|
+
readIsolation: enable,
|
|
4442
|
+
suspendedSessions: 0,
|
|
4443
|
+
changed: false,
|
|
4444
|
+
});
|
|
4445
|
+
}
|
|
4446
|
+
// Close admission first and drain handlers that may already be awaiting
|
|
4447
|
+
// downloads/noteTurnReceived. Ledger preflight alone cannot see those
|
|
4448
|
+
// pre-accept turns; draining prevents a post-sweep send into a killed ds.
|
|
4449
|
+
const activeBotSessions = listActiveSessions().filter(ds => ds.larkAppId === larkAppId);
|
|
4450
|
+
// Registry state alone is insufficient: partial restore, an anchor
|
|
4451
|
+
// collision, or a failed staggered reattach can omit a durable active row
|
|
4452
|
+
// while its persistent pane still survives. Consult the same persisted
|
|
4453
|
+
// session source a restart will hydrate. Legacy unscoped active rows are
|
|
4454
|
+
// conservatively treated as this daemon's until explicitly closed.
|
|
4455
|
+
const persistedBotSessions = sessionStore.listSessions().filter(session => session.larkAppId === larkAppId || !session.larkAppId);
|
|
4456
|
+
const persistedActiveBotSessions = persistedBotSessions.filter(session => session.status === 'active');
|
|
4457
|
+
if (rejectProtectedSessionMutation(res, [
|
|
4458
|
+
...activeBotSessions,
|
|
4459
|
+
...persistedActiveBotSessions,
|
|
4460
|
+
]))
|
|
4461
|
+
return;
|
|
4462
|
+
// Crash-transactional safety boundary: bots.json is the restart source of
|
|
4463
|
+
// truth, while a live tmux/herdr/zellij pane retains its old in-memory
|
|
4464
|
+
// Seatbelt profile. Persisting the new flag before tearing those panes down
|
|
4465
|
+
// creates an unrecoverable crash window because the restart path cannot
|
|
4466
|
+
// prove which exact read/write isolation profile a surviving pane runs.
|
|
4467
|
+
// Require explicit close first; with no active logical session there is no
|
|
4468
|
+
// owned pane a restart can reattach under the newly persisted policy.
|
|
4469
|
+
if (activeBotSessions.length > 0 || persistedActiveBotSessions.length > 0) {
|
|
4470
|
+
return jsonRes(res, 409, {
|
|
4471
|
+
ok: false,
|
|
4472
|
+
error: 'read_isolation_active_sessions',
|
|
4473
|
+
});
|
|
4474
|
+
}
|
|
4475
|
+
// `/close` intentionally returns after sending worker close IPC and marking
|
|
4476
|
+
// the row closed; persistent-pane destruction can lag. A closed row's pid
|
|
4477
|
+
// is deliberately not probed: PID alone has no birth identity and may have
|
|
4478
|
+
// been reused by an unrelated process. closeSession clears it atomically.
|
|
4479
|
+
// For current rows, the stamped persistent backend is the teardown proof.
|
|
4480
|
+
// Pre-stamp closed rows are not synchronously probed across three CLIs here:
|
|
4481
|
+
// that legacy shell fan-out blocks the daemon event loop, while any active
|
|
4482
|
+
// legacy row has already failed the active-session guard above.
|
|
4483
|
+
for (const session of persistedBotSessions) {
|
|
4484
|
+
if (session.adoptedFrom || session.title?.startsWith('Adopt:'))
|
|
4485
|
+
continue;
|
|
4486
|
+
const backendTypes = persistentBackend.isSuspendableBackendType(session.backendType)
|
|
4487
|
+
? [session.backendType]
|
|
4488
|
+
: [];
|
|
4489
|
+
for (const backendType of backendTypes) {
|
|
4490
|
+
const backingName = persistentBackend.persistentSessionName(backendType, session.sessionId);
|
|
4491
|
+
if (persistentBackend.probePersistentSession(backendType, backingName) !== 'missing') {
|
|
4492
|
+
return jsonRes(res, 409, {
|
|
4493
|
+
ok: false,
|
|
4494
|
+
error: 'read_isolation_teardown_unverified',
|
|
4495
|
+
});
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4498
|
+
}
|
|
4499
|
+
// The worker FAIL-CLOSES (refuses to start the session) for a configured
|
|
4500
|
+
// readIsolation that cannot be enforced. Check this after the active-session
|
|
4501
|
+
// safety boundary so even an unsupported enable cannot obscure a surviving
|
|
4502
|
+
// old-policy pane with a less important validation error.
|
|
4503
|
+
if (enable && !readIsolationEnforceable(larkAppId)) {
|
|
4504
|
+
return jsonRes(res, 400, { ok: false, error: 'read_isolation_unenforceable' });
|
|
4505
|
+
}
|
|
4506
|
+
// With the gate closed and no active logical session, persistence is the
|
|
4507
|
+
// only mutation. updateBotReadIsolation writes bots.json atomically and
|
|
4508
|
+
// then publishes the same value to the daemon runtime before resolving.
|
|
4509
|
+
// A crash at any point can only lead to a cold spawn under the old or new
|
|
4510
|
+
// durable policy; there is no owned pane to reattach.
|
|
4511
|
+
const r = await sandboxStore.updateBotReadIsolation(larkAppId, enable);
|
|
4512
|
+
if (!r.ok)
|
|
4513
|
+
return jsonRes(res, 400, { ok: false, error: r.reason });
|
|
4514
|
+
jsonRes(res, 200, {
|
|
4515
|
+
ok: true,
|
|
4516
|
+
readIsolation: r.readIsolation,
|
|
4517
|
+
suspendedSessions: 0,
|
|
4518
|
+
changed: true,
|
|
4519
|
+
});
|
|
4520
|
+
});
|
|
3969
4521
|
});
|
|
3970
4522
|
// Per-bot session backend override (pty | tmux | herdr | zellij | zmx), or clear it
|
|
3971
4523
|
// ('' / 'auto' / null → follow the daemon default). next-session 生效:running
|
|
@@ -4357,6 +4909,8 @@ export function startIpcServer(opts) {
|
|
|
4357
4909
|
return jsonRes(res, 401, { ok: false, error: 'unauthorized', reason: auth.reason });
|
|
4358
4910
|
}
|
|
4359
4911
|
}
|
|
4912
|
+
if (!publicRoute && opts.ready)
|
|
4913
|
+
await opts.ready;
|
|
4360
4914
|
for (const r of routes) {
|
|
4361
4915
|
if (r.method !== req.method)
|
|
4362
4916
|
continue;
|