botmux 3.4.3 → 3.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +10 -7
- package/README.md +11 -6
- package/dist/adapters/backend/herdr-backend.d.ts +43 -6
- package/dist/adapters/backend/herdr-backend.d.ts.map +1 -1
- package/dist/adapters/backend/herdr-backend.js +362 -28
- package/dist/adapters/backend/herdr-backend.js.map +1 -1
- package/dist/adapters/backend/reproduce-command.d.ts +39 -0
- package/dist/adapters/backend/reproduce-command.d.ts.map +1 -0
- package/dist/adapters/backend/reproduce-command.js +63 -0
- package/dist/adapters/backend/reproduce-command.js.map +1 -0
- package/dist/adapters/backend/sandbox.d.ts +99 -279
- package/dist/adapters/backend/sandbox.d.ts.map +1 -1
- package/dist/adapters/backend/sandbox.js +424 -826
- package/dist/adapters/backend/sandbox.js.map +1 -1
- package/dist/adapters/backend/session-backend-selector.d.ts +10 -1
- package/dist/adapters/backend/session-backend-selector.d.ts.map +1 -1
- package/dist/adapters/backend/session-backend-selector.js +73 -4
- package/dist/adapters/backend/session-backend-selector.js.map +1 -1
- package/dist/adapters/backend/types.d.ts +16 -0
- package/dist/adapters/backend/types.d.ts.map +1 -1
- package/dist/adapters/backend/types.js.map +1 -1
- package/dist/adapters/cli/codex.d.ts.map +1 -1
- package/dist/adapters/cli/codex.js +4 -0
- package/dist/adapters/cli/codex.js.map +1 -1
- package/dist/adapters/cli/fs-policy.d.ts +244 -0
- package/dist/adapters/cli/fs-policy.d.ts.map +1 -0
- package/dist/adapters/cli/fs-policy.js +539 -0
- package/dist/adapters/cli/fs-policy.js.map +1 -0
- package/dist/adapters/cli/pi-initial-prompt.d.ts.map +1 -1
- package/dist/adapters/cli/pi-initial-prompt.js +5 -1
- package/dist/adapters/cli/pi-initial-prompt.js.map +1 -1
- package/dist/adapters/cli/read-isolation.d.ts +14 -200
- package/dist/adapters/cli/read-isolation.d.ts.map +1 -1
- package/dist/adapters/cli/read-isolation.js +18 -477
- package/dist/adapters/cli/read-isolation.js.map +1 -1
- package/dist/adapters/cli/traex.d.ts.map +1 -1
- package/dist/adapters/cli/traex.js +5 -0
- package/dist/adapters/cli/traex.js.map +1 -1
- package/dist/adapters/cli/types.d.ts +3 -3
- package/dist/adapters/cli/types.d.ts.map +1 -1
- package/dist/adapters/hook-installer.d.ts +7 -0
- package/dist/adapters/hook-installer.d.ts.map +1 -1
- package/dist/adapters/hook-installer.js +56 -0
- package/dist/adapters/hook-installer.js.map +1 -1
- package/dist/bot-registry.d.ts +39 -23
- package/dist/bot-registry.d.ts.map +1 -1
- package/dist/bot-registry.js +134 -2
- package/dist/bot-registry.js.map +1 -1
- package/dist/cli/capabilities.d.ts +24 -0
- package/dist/cli/capabilities.d.ts.map +1 -0
- package/dist/cli/capabilities.js +25 -0
- package/dist/cli/capabilities.js.map +1 -0
- package/dist/cli/create-group-resolver.d.ts +24 -0
- package/dist/cli/create-group-resolver.d.ts.map +1 -1
- package/dist/cli/create-group-resolver.js +23 -0
- package/dist/cli/create-group-resolver.js.map +1 -1
- package/dist/cli/daemon-lifecycle-env.d.ts +5 -7
- package/dist/cli/daemon-lifecycle-env.d.ts.map +1 -1
- package/dist/cli/daemon-lifecycle-env.js +13 -7
- package/dist/cli/daemon-lifecycle-env.js.map +1 -1
- package/dist/cli/exact-chat-grant-client.d.ts +48 -0
- package/dist/cli/exact-chat-grant-client.d.ts.map +1 -0
- package/dist/cli/exact-chat-grant-client.js +108 -0
- package/dist/cli/exact-chat-grant-client.js.map +1 -0
- package/dist/cli/exact-chat-grant.d.ts +18 -0
- package/dist/cli/exact-chat-grant.d.ts.map +1 -0
- package/dist/cli/exact-chat-grant.js +97 -0
- package/dist/cli/exact-chat-grant.js.map +1 -0
- package/dist/cli.d.ts +10 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +795 -144
- package/dist/cli.js.map +1 -1
- package/dist/codex-rpc-engine.d.ts +26 -0
- package/dist/codex-rpc-engine.d.ts.map +1 -1
- package/dist/codex-rpc-engine.js +113 -2
- package/dist/codex-rpc-engine.js.map +1 -1
- package/dist/config.d.ts +16 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +16 -0
- package/dist/config.js.map +1 -1
- package/dist/core/bot-live-control.d.ts +41 -0
- package/dist/core/bot-live-control.d.ts.map +1 -0
- package/dist/core/bot-live-control.js +135 -0
- package/dist/core/bot-live-control.js.map +1 -0
- package/dist/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +44 -66
- package/dist/core/command-handler.js.map +1 -1
- package/dist/core/daemon-config-fence.d.ts +8 -0
- package/dist/core/daemon-config-fence.d.ts.map +1 -0
- package/dist/core/daemon-config-fence.js +13 -0
- package/dist/core/daemon-config-fence.js.map +1 -0
- package/dist/core/dashboard-events.d.ts +5 -0
- package/dist/core/dashboard-events.d.ts.map +1 -1
- package/dist/core/dashboard-events.js.map +1 -1
- package/dist/core/dashboard-ipc-server.d.ts +21 -2
- package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
- package/dist/core/dashboard-ipc-server.js +258 -32
- package/dist/core/dashboard-ipc-server.js.map +1 -1
- package/dist/core/dispatch-registry.d.ts +12 -0
- package/dist/core/dispatch-registry.d.ts.map +1 -0
- package/dist/core/dispatch-registry.js +33 -0
- package/dist/core/dispatch-registry.js.map +1 -0
- package/dist/core/dispatch.d.ts +99 -0
- package/dist/core/dispatch.d.ts.map +1 -1
- package/dist/core/dispatch.js +136 -0
- package/dist/core/dispatch.js.map +1 -1
- package/dist/core/maintenance.d.ts.map +1 -1
- package/dist/core/maintenance.js +10 -3
- package/dist/core/maintenance.js.map +1 -1
- package/dist/core/passthrough-commands.d.ts.map +1 -1
- package/dist/core/passthrough-commands.js +1 -1
- package/dist/core/passthrough-commands.js.map +1 -1
- package/dist/core/persistent-backend.d.ts +16 -2
- package/dist/core/persistent-backend.d.ts.map +1 -1
- package/dist/core/persistent-backend.js +40 -0
- package/dist/core/persistent-backend.js.map +1 -1
- package/dist/core/plugins/mcp/gateway-installer.d.ts.map +1 -1
- package/dist/core/plugins/mcp/gateway-installer.js +14 -2
- package/dist/core/plugins/mcp/gateway-installer.js.map +1 -1
- package/dist/core/session-discovery.d.ts +8 -1
- package/dist/core/session-discovery.d.ts.map +1 -1
- package/dist/core/session-discovery.js +140 -8
- package/dist/core/session-discovery.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +102 -68
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-title.d.ts +8 -0
- package/dist/core/session-title.d.ts.map +1 -1
- package/dist/core/session-title.js +54 -0
- package/dist/core/session-title.js.map +1 -1
- package/dist/core/terminal-write-auth.d.ts +15 -0
- package/dist/core/terminal-write-auth.d.ts.map +1 -1
- package/dist/core/terminal-write-auth.js +20 -0
- package/dist/core/terminal-write-auth.js.map +1 -1
- package/dist/core/trigger-session.d.ts.map +1 -1
- package/dist/core/trigger-session.js +3 -2
- package/dist/core/trigger-session.js.map +1 -1
- package/dist/core/types.d.ts +33 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/worker-pool.d.ts +51 -8
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +439 -25
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts +4 -2
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +403 -26
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/bot-onboarding.d.ts +127 -1
- package/dist/dashboard/bot-onboarding.d.ts.map +1 -1
- package/dist/dashboard/bot-onboarding.js +1104 -25
- package/dist/dashboard/bot-onboarding.js.map +1 -1
- package/dist/dashboard/bot-payload.d.ts +5 -0
- package/dist/dashboard/bot-payload.d.ts.map +1 -1
- package/dist/dashboard/bot-payload.js +7 -0
- package/dist/dashboard/bot-payload.js.map +1 -1
- package/dist/dashboard/debug-terminal.d.ts +18 -0
- package/dist/dashboard/debug-terminal.d.ts.map +1 -0
- package/dist/dashboard/debug-terminal.js +338 -0
- package/dist/dashboard/debug-terminal.js.map +1 -0
- package/dist/dashboard/public-redact.d.ts +2 -0
- package/dist/dashboard/public-redact.d.ts.map +1 -1
- package/dist/dashboard/public-redact.js +12 -3
- package/dist/dashboard/public-redact.js.map +1 -1
- package/dist/dashboard/registry.d.ts +8 -0
- package/dist/dashboard/registry.d.ts.map +1 -1
- package/dist/dashboard/registry.js +13 -0
- package/dist/dashboard/registry.js.map +1 -1
- package/dist/dashboard/settings-write-applier.d.ts +47 -1
- package/dist/dashboard/settings-write-applier.d.ts.map +1 -1
- package/dist/dashboard/settings-write-applier.js +102 -1
- package/dist/dashboard/settings-write-applier.js.map +1 -1
- package/dist/dashboard/web/app.d.ts.map +1 -1
- package/dist/dashboard/web/app.js +2 -0
- package/dist/dashboard/web/app.js.map +1 -1
- package/dist/dashboard/web/bot-defaults-page.d.ts +17 -0
- package/dist/dashboard/web/bot-defaults-page.d.ts.map +1 -1
- package/dist/dashboard/web/bot-defaults-page.js +249 -11
- package/dist/dashboard/web/bot-defaults-page.js.map +1 -1
- package/dist/dashboard/web/bot-defaults.d.ts +22 -0
- package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
- package/dist/dashboard/web/bot-defaults.js +19 -0
- package/dist/dashboard/web/bot-defaults.js.map +1 -1
- package/dist/dashboard/web/i18n.d.ts.map +1 -1
- package/dist/dashboard/web/i18n.js +114 -28
- package/dist/dashboard/web/i18n.js.map +1 -1
- package/dist/dashboard/web/icon-tooltip.d.ts +3 -0
- package/dist/dashboard/web/icon-tooltip.d.ts.map +1 -0
- package/dist/dashboard/web/icon-tooltip.js +108 -0
- package/dist/dashboard/web/icon-tooltip.js.map +1 -0
- package/dist/dashboard/web/sessions-kanban.js +2 -2
- 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 +49 -53
- package/dist/dashboard/web/sessions-page.js.map +1 -1
- package/dist/dashboard/web/sessions.d.ts +2 -0
- package/dist/dashboard/web/sessions.d.ts.map +1 -1
- package/dist/dashboard/web/sessions.js +46 -2
- package/dist/dashboard/web/sessions.js.map +1 -1
- package/dist/dashboard/web/settings-page.d.ts +78 -0
- package/dist/dashboard/web/settings-page.d.ts.map +1 -1
- package/dist/dashboard/web/settings-page.js +88 -1
- package/dist/dashboard/web/settings-page.js.map +1 -1
- package/dist/dashboard/web/store.d.ts +2 -0
- package/dist/dashboard/web/store.d.ts.map +1 -1
- package/dist/dashboard/web/store.js +13 -1
- package/dist/dashboard/web/store.js.map +1 -1
- package/dist/dashboard-web/app.js +1 -1
- package/dist/dashboard-web/chunks/bot-defaults-page-AUKV7P4L.js +10 -0
- package/dist/dashboard-web/chunks/{chunk-P6GO5KT4.js → chunk-KPQAUX7V.js} +1 -1
- package/dist/dashboard-web/chunks/{chunk-UBOLHQCK.js → chunk-NRL6CVAS.js} +1 -1
- package/dist/dashboard-web/chunks/{chunk-JNRSPKLS.js → chunk-R7IAKAYX.js} +1 -1
- package/dist/dashboard-web/chunks/{chunk-CUV65BIQ.js → chunk-X2L65IOM.js} +8 -8
- package/dist/dashboard-web/chunks/{connectors-page-KTC2QWNW.js → connectors-page-HDQFEXZI.js} +1 -1
- package/dist/dashboard-web/chunks/{groups-page-B3EKGOLY.js → groups-page-6DKXBYOM.js} +1 -1
- package/dist/dashboard-web/chunks/{insights-page-X6RXNCWX.js → insights-page-NWARTJ6J.js} +1 -1
- package/dist/dashboard-web/chunks/{monitor-room-LLRA5LQW.js → monitor-room-AGX3XRJX.js} +1 -1
- package/dist/dashboard-web/chunks/{monitoring-page-7MT5FEOS.js → monitoring-page-GGGIWN4C.js} +1 -1
- package/dist/dashboard-web/chunks/{office-page-KD46D5TD.js → office-page-6SIVKSK3.js} +1 -1
- package/dist/dashboard-web/chunks/{overview-page-JUWNH32O.js → overview-page-FVR3NKAF.js} +1 -1
- package/dist/dashboard-web/chunks/{roles-page-P6F7NLOI.js → roles-page-WE2YB7KN.js} +1 -1
- package/dist/dashboard-web/chunks/{schedules-page-XIHWZST4.js → schedules-page-TB3I24XN.js} +1 -1
- package/dist/dashboard-web/chunks/sessions-page-KOOOASIL.js +2 -0
- package/dist/dashboard-web/chunks/settings-page-IEBR7TGN.js +2 -0
- package/dist/dashboard-web/chunks/{skills-page-A46KVWBL.js → skills-page-3QAT2IWD.js} +1 -1
- package/dist/dashboard-web/chunks/{team-federation-page-SDUC56DF.js → team-federation-page-K4VVAVAK.js} +1 -1
- package/dist/dashboard-web/chunks/{v3-page-J5JOTUSQ.js → v3-page-V6IOJR2B.js} +1 -1
- package/dist/dashboard-web/chunks/{whiteboards-page-U7LSOAWI.js → whiteboards-page-V6CGKRNX.js} +1 -1
- package/dist/dashboard-web/style.css +218 -10
- package/dist/dashboard.js +380 -8
- package/dist/dashboard.js.map +1 -1
- package/dist/features/codex-notifier/adoption.d.ts +11 -0
- package/dist/features/codex-notifier/adoption.d.ts.map +1 -0
- package/dist/features/codex-notifier/adoption.js +11 -0
- package/dist/features/codex-notifier/adoption.js.map +1 -0
- package/dist/features/codex-notifier/app-opener.d.ts +21 -0
- package/dist/features/codex-notifier/app-opener.d.ts.map +1 -0
- package/dist/features/codex-notifier/app-opener.js +33 -0
- package/dist/features/codex-notifier/app-opener.js.map +1 -0
- package/dist/features/codex-notifier/card-action.d.ts +27 -0
- package/dist/features/codex-notifier/card-action.d.ts.map +1 -0
- package/dist/features/codex-notifier/card-action.js +84 -0
- package/dist/features/codex-notifier/card-action.js.map +1 -0
- package/dist/features/codex-notifier/card.d.ts +12 -0
- package/dist/features/codex-notifier/card.d.ts.map +1 -0
- package/dist/features/codex-notifier/card.js +150 -0
- package/dist/features/codex-notifier/card.js.map +1 -0
- package/dist/features/codex-notifier/codex-context.d.ts +12 -0
- package/dist/features/codex-notifier/codex-context.d.ts.map +1 -0
- package/dist/features/codex-notifier/codex-context.js +161 -0
- package/dist/features/codex-notifier/codex-context.js.map +1 -0
- package/dist/features/codex-notifier/config.d.ts +9 -0
- package/dist/features/codex-notifier/config.d.ts.map +1 -0
- package/dist/features/codex-notifier/config.js +11 -0
- package/dist/features/codex-notifier/config.js.map +1 -0
- package/dist/features/codex-notifier/confirmed-turn.d.ts +20 -0
- package/dist/features/codex-notifier/confirmed-turn.d.ts.map +1 -0
- package/dist/features/codex-notifier/confirmed-turn.js +130 -0
- package/dist/features/codex-notifier/confirmed-turn.js.map +1 -0
- package/dist/features/codex-notifier/emitter.d.ts +13 -0
- package/dist/features/codex-notifier/emitter.d.ts.map +1 -0
- package/dist/features/codex-notifier/emitter.js +33 -0
- package/dist/features/codex-notifier/emitter.js.map +1 -0
- package/dist/features/codex-notifier/event-store.d.ts +29 -0
- package/dist/features/codex-notifier/event-store.d.ts.map +1 -0
- package/dist/features/codex-notifier/event-store.js +217 -0
- package/dist/features/codex-notifier/event-store.js.map +1 -0
- package/dist/features/codex-notifier/event.d.ts +46 -0
- package/dist/features/codex-notifier/event.d.ts.map +1 -0
- package/dist/features/codex-notifier/event.js +188 -0
- package/dist/features/codex-notifier/event.js.map +1 -0
- package/dist/features/codex-notifier/hook-cli.d.ts +23 -0
- package/dist/features/codex-notifier/hook-cli.d.ts.map +1 -0
- package/dist/features/codex-notifier/hook-cli.js +130 -0
- package/dist/features/codex-notifier/hook-cli.js.map +1 -0
- package/dist/features/codex-notifier/hook-installer.d.ts +19 -0
- package/dist/features/codex-notifier/hook-installer.d.ts.map +1 -0
- package/dist/features/codex-notifier/hook-installer.js +158 -0
- package/dist/features/codex-notifier/hook-installer.js.map +1 -0
- package/dist/features/codex-notifier/index.d.ts +21 -0
- package/dist/features/codex-notifier/index.d.ts.map +1 -0
- package/dist/features/codex-notifier/index.js +19 -0
- package/dist/features/codex-notifier/index.js.map +1 -0
- package/dist/features/codex-notifier/internal-turn.d.ts +5 -0
- package/dist/features/codex-notifier/internal-turn.d.ts.map +1 -0
- package/dist/features/codex-notifier/internal-turn.js +43 -0
- package/dist/features/codex-notifier/internal-turn.js.map +1 -0
- package/dist/features/codex-notifier/outbox-worker.d.ts +71 -0
- package/dist/features/codex-notifier/outbox-worker.d.ts.map +1 -0
- package/dist/features/codex-notifier/outbox-worker.js +290 -0
- package/dist/features/codex-notifier/outbox-worker.js.map +1 -0
- package/dist/features/codex-notifier/outbox.d.ts +25 -0
- package/dist/features/codex-notifier/outbox.d.ts.map +1 -0
- package/dist/features/codex-notifier/outbox.js +225 -0
- package/dist/features/codex-notifier/outbox.js.map +1 -0
- package/dist/features/codex-notifier/paths.d.ts +7 -0
- package/dist/features/codex-notifier/paths.d.ts.map +1 -0
- package/dist/features/codex-notifier/paths.js +20 -0
- package/dist/features/codex-notifier/paths.js.map +1 -0
- package/dist/features/codex-notifier/screen-lock.d.ts +12 -0
- package/dist/features/codex-notifier/screen-lock.d.ts.map +1 -0
- package/dist/features/codex-notifier/screen-lock.js +47 -0
- package/dist/features/codex-notifier/screen-lock.js.map +1 -0
- package/dist/features/codex-notifier/side-conversation-monitor.d.ts +102 -0
- package/dist/features/codex-notifier/side-conversation-monitor.d.ts.map +1 -0
- package/dist/features/codex-notifier/side-conversation-monitor.js +774 -0
- package/dist/features/codex-notifier/side-conversation-monitor.js.map +1 -0
- package/dist/features/codex-notifier/types.d.ts +42 -0
- package/dist/features/codex-notifier/types.d.ts.map +1 -0
- package/dist/features/codex-notifier/types.js +2 -0
- package/dist/features/codex-notifier/types.js.map +1 -0
- package/dist/features/codex-notifier/worker-lock.d.ts +8 -0
- package/dist/features/codex-notifier/worker-lock.d.ts.map +1 -0
- package/dist/features/codex-notifier/worker-lock.js +108 -0
- package/dist/features/codex-notifier/worker-lock.js.map +1 -0
- package/dist/global-config.d.ts +13 -0
- package/dist/global-config.d.ts.map +1 -1
- package/dist/global-config.js +34 -1
- package/dist/global-config.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +1 -19
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +1 -19
- package/dist/i18n/zh.js.map +1 -1
- package/dist/im/lark/card-builder.d.ts +0 -13
- package/dist/im/lark/card-builder.d.ts.map +1 -1
- package/dist/im/lark/card-builder.js +2 -36
- package/dist/im/lark/card-builder.js.map +1 -1
- package/dist/im/lark/card-handler.d.ts +2 -0
- package/dist/im/lark/card-handler.d.ts.map +1 -1
- package/dist/im/lark/card-handler.js +166 -64
- package/dist/im/lark/card-handler.js.map +1 -1
- package/dist/im/lark/client.d.ts +98 -4
- package/dist/im/lark/client.d.ts.map +1 -1
- package/dist/im/lark/client.js +317 -29
- package/dist/im/lark/client.js.map +1 -1
- package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
- package/dist/im/lark/event-dispatcher.js +48 -33
- package/dist/im/lark/event-dispatcher.js.map +1 -1
- package/dist/services/codex-app-threads.d.ts +47 -0
- package/dist/services/codex-app-threads.d.ts.map +1 -1
- package/dist/services/codex-app-threads.js +502 -10
- package/dist/services/codex-app-threads.js.map +1 -1
- package/dist/services/exact-chat-grant.d.ts +83 -0
- package/dist/services/exact-chat-grant.d.ts.map +1 -0
- package/dist/services/exact-chat-grant.js +249 -0
- package/dist/services/exact-chat-grant.js.map +1 -0
- package/dist/services/group-creator.d.ts +11 -0
- package/dist/services/group-creator.d.ts.map +1 -1
- package/dist/services/group-creator.js +18 -9
- package/dist/services/group-creator.js.map +1 -1
- package/dist/services/local-cli-opener.d.ts.map +1 -1
- package/dist/services/local-cli-opener.js +10 -5
- package/dist/services/local-cli-opener.js.map +1 -1
- package/dist/services/sandbox-migration.d.ts +14 -0
- package/dist/services/sandbox-migration.d.ts.map +1 -0
- package/dist/services/sandbox-migration.js +85 -0
- package/dist/services/sandbox-migration.js.map +1 -0
- package/dist/services/sandbox-store.d.ts +28 -0
- package/dist/services/sandbox-store.d.ts.map +1 -1
- package/dist/services/sandbox-store.js +82 -0
- package/dist/services/sandbox-store.js.map +1 -1
- package/dist/services/traex-user-input.d.ts +19 -0
- package/dist/services/traex-user-input.d.ts.map +1 -0
- package/dist/services/traex-user-input.js +57 -0
- package/dist/services/traex-user-input.js.map +1 -0
- package/dist/setup/open-platform-automation.d.ts +24 -3
- package/dist/setup/open-platform-automation.d.ts.map +1 -1
- package/dist/setup/open-platform-automation.js +45 -8
- package/dist/setup/open-platform-automation.js.map +1 -1
- package/dist/setup/verify-permissions.d.ts +18 -0
- package/dist/setup/verify-permissions.d.ts.map +1 -1
- package/dist/setup/verify-permissions.js +60 -0
- package/dist/setup/verify-permissions.js.map +1 -1
- package/dist/types.d.ts +83 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/child-env.d.ts +2 -2
- package/dist/utils/child-env.d.ts.map +1 -1
- package/dist/utils/child-env.js +14 -0
- package/dist/utils/child-env.js.map +1 -1
- package/dist/utils/stuck-detector.d.ts +45 -0
- package/dist/utils/stuck-detector.d.ts.map +1 -0
- package/dist/utils/stuck-detector.js +188 -0
- package/dist/utils/stuck-detector.js.map +1 -0
- package/dist/utils/stuck-key-guard.d.ts +75 -0
- package/dist/utils/stuck-key-guard.d.ts.map +1 -0
- package/dist/utils/stuck-key-guard.js +77 -0
- package/dist/utils/stuck-key-guard.js.map +1 -0
- package/dist/worker.js +1119 -588
- package/dist/worker.js.map +1 -1
- package/dist/workflows/events/payloads.d.ts +56 -0
- package/dist/workflows/events/payloads.d.ts.map +1 -1
- package/dist/workflows/events/payloads.js +9 -0
- package/dist/workflows/events/payloads.js.map +1 -1
- package/dist/workflows/events/schema.d.ts +86 -0
- package/dist/workflows/events/schema.d.ts.map +1 -1
- package/dist/workflows/shared/sandbox-policy.d.ts +10 -1
- package/dist/workflows/shared/sandbox-policy.d.ts.map +1 -1
- package/dist/workflows/shared/sandbox-policy.js +12 -0
- package/dist/workflows/shared/sandbox-policy.js.map +1 -1
- package/dist/workflows/v3/bot-resolve.d.ts.map +1 -1
- package/dist/workflows/v3/bot-resolve.js +36 -0
- package/dist/workflows/v3/bot-resolve.js.map +1 -1
- package/dist/workflows/v3/contract.d.ts +8 -0
- package/dist/workflows/v3/contract.d.ts.map +1 -1
- package/dist/workflows/v3/contract.js.map +1 -1
- package/package.json +1 -1
- package/dist/dashboard-web/chunks/bot-defaults-page-B5AY4B2J.js +0 -5
- package/dist/dashboard-web/chunks/sessions-page-2VECQ27T.js +0 -3
- package/dist/dashboard-web/chunks/settings-page-HMIUE2JA.js +0 -2
- package/dist/services/sandbox-land.d.ts +0 -45
- package/dist/services/sandbox-land.d.ts.map +0 -1
- package/dist/services/sandbox-land.js +0 -394
- package/dist/services/sandbox-land.js.map +0 -1
package/dist/core/worker-pool.js
CHANGED
|
@@ -11,7 +11,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
11
11
|
import { ensureSkills, ensureAskSkill, ensurePluginSkills, ensureWhiteboardSkill, removeGlobalBotmuxSkills } from '../skills/installer.js';
|
|
12
12
|
import { shouldInstallGlobalSkills } from '../skills/injection-mode.js';
|
|
13
13
|
import { whiteboardEnabled } from '../services/whiteboard-store.js';
|
|
14
|
-
import { installHook } from '../adapters/hook-installer.js';
|
|
14
|
+
import { cleanupTraexAskHooks, installHook } from '../adapters/hook-installer.js';
|
|
15
15
|
import { hookCommandFor } from '../adapters/hook-command.js';
|
|
16
16
|
import { randomBytes, randomUUID } from 'node:crypto';
|
|
17
17
|
import { config } from '../config.js';
|
|
@@ -24,6 +24,7 @@ import { loadFrozenCards, saveFrozenCards } from '../services/frozen-card-store.
|
|
|
24
24
|
import { hashUrlForLog, cancelRiffTaskById } from '../adapters/backend/riff-backend.js';
|
|
25
25
|
import { logger } from '../utils/logger.js';
|
|
26
26
|
import { createCliAdapterSync } from '../adapters/cli/registry.js';
|
|
27
|
+
import { traeHome } from '../services/traex-paths.js';
|
|
27
28
|
import { botLocale, localeForBot, t as tr } from '../i18n/index.js';
|
|
28
29
|
import { claudeJsonlPathForSession } from '../adapters/cli/claude-code.js';
|
|
29
30
|
import { findUniqueClaudeSessionByCwd } from './session-discovery.js';
|
|
@@ -34,7 +35,7 @@ import { listDocSubscriptionsForSession, removeDocSubscription } from '../servic
|
|
|
34
35
|
import { TmuxBackend } from '../adapters/backend/tmux-backend.js';
|
|
35
36
|
import { HerdrBackend } from '../adapters/backend/herdr-backend.js';
|
|
36
37
|
import { sandboxEnabled } from '../adapters/backend/sandbox.js';
|
|
37
|
-
import { isSuspendableBackendType, getSessionPersistentBackendType,
|
|
38
|
+
import { isSuspendableBackendType, getSessionPersistentBackendType, persistentBackendTargetForSession, killPersistentBackendTarget, managedTargetsForCliChange, resolvePairedSpawnBackendType, resolvePersistentBackendTarget } from './persistent-backend.js';
|
|
38
39
|
import { getBot, getAllBots, resolveBrandLabel } from '../bot-registry.js';
|
|
39
40
|
/** A random id minted once per daemon process (this lifetime). Stamped onto
|
|
40
41
|
* isolated persistent panes so a suspend→resume reattach (same id) is
|
|
@@ -79,7 +80,9 @@ import { isLocalCliOpenEnabled, isLocalCliOpenReady } from '../services/local-cl
|
|
|
79
80
|
import { isSilentScheduledTurn } from './silent-schedule-turns.js';
|
|
80
81
|
import { writeDeferredTopicBinding } from './deferred-topic-binding.js';
|
|
81
82
|
import { deferWorkerSpawnDuringDeviceIsolation } from './device-isolation-activation.js';
|
|
83
|
+
import { buildBotmuxLarkNativeSessionTitle, extractBotmuxLarkNativeSessionTitlePrompt, } from './session-title.js';
|
|
82
84
|
import { acknowledgeSessionReady } from './session-ready-handshake.js';
|
|
85
|
+
import { recordDispatchInputCommit } from './dispatch.js';
|
|
83
86
|
const __filename = fileURLToPath(import.meta.url);
|
|
84
87
|
const __dirname = dirname(__filename);
|
|
85
88
|
const WORKER_SIGTERM_BACKSTOP_MS = 2_000;
|
|
@@ -652,13 +655,13 @@ export async function postPrivateSnapshotCard(ds, audience) {
|
|
|
652
655
|
* Deliver the write-enabled session card (the "🔑 获取操作链接" card, which carries
|
|
653
656
|
* a write-token terminal URL + manage buttons) privately to a single operator.
|
|
654
657
|
*
|
|
655
|
-
* Prefers an in-chat "visible-to-you" ephemeral card
|
|
656
|
-
* to leave the conversation.
|
|
657
|
-
*
|
|
658
|
-
*
|
|
659
|
-
*
|
|
660
|
-
*
|
|
661
|
-
*
|
|
658
|
+
* Prefers an in-chat "visible-to-you" ephemeral card in a flat group so the
|
|
659
|
+
* operator never has to leave the conversation. Thread-scope sessions and
|
|
660
|
+
* chat-scope sessions currently folded into a thread go straight to DM:
|
|
661
|
+
* Feishu may accept their ephemeral message without rendering it in the topic
|
|
662
|
+
* panel. p2p chats also DM directly (the DM lands in that same 1:1 chat).
|
|
663
|
+
*
|
|
664
|
+
* Other group chats attempt ephemeral first and fall back to DM on ANY failure.
|
|
662
665
|
*
|
|
663
666
|
* Both channels are private, so the DM fallback never leaks the write token —
|
|
664
667
|
* unlike the private /card snapshot (which fails closed), here we fail OVER.
|
|
@@ -667,7 +670,10 @@ export async function postPrivateSnapshotCard(ds, audience) {
|
|
|
667
670
|
*/
|
|
668
671
|
export async function deliverWriteLinkCard(ds, operatorOpenId, cardJson) {
|
|
669
672
|
const who = operatorOpenId.substring(0, 8);
|
|
670
|
-
|
|
673
|
+
const replyTarget = ds.currentReplyTarget ?? ds.session.currentReplyTarget;
|
|
674
|
+
const isThreaded = ds.scope === 'thread'
|
|
675
|
+
|| (!!replyTarget?.rootMessageId && replyTarget.quoteOnly !== true);
|
|
676
|
+
if (ds.chatType !== 'p2p' && !isThreaded) {
|
|
671
677
|
try {
|
|
672
678
|
await sendEphemeralCard(ds.larkAppId, ds.chatId, operatorOpenId, cardJson);
|
|
673
679
|
logger.info(`[${tag(ds)}] write link delivered via ephemeral card to ${who}…`);
|
|
@@ -909,6 +915,12 @@ const skillsInstalledCliIds = new Set();
|
|
|
909
915
|
*/
|
|
910
916
|
export function ensureCliSkills(cliId, cliPathOverride) {
|
|
911
917
|
const adapter = createCliAdapterSync(cliId, cliPathOverride);
|
|
918
|
+
if (cliId === 'traex') {
|
|
919
|
+
cleanupTraexAskHooks([
|
|
920
|
+
join(traeHome(), 'hooks.json'),
|
|
921
|
+
join(traeHome(), 'cli', 'hooks.json'),
|
|
922
|
+
]);
|
|
923
|
+
}
|
|
912
924
|
// Claude-family CLIs deliver skills per-session via `--plugin-dir` (no global
|
|
913
925
|
// leak), so they always materialise their plugin dir — the builtin-injection
|
|
914
926
|
// mode does not apply to them. Everything below the branch is the global-dir
|
|
@@ -1084,6 +1096,10 @@ export function killWorker(ds) {
|
|
|
1084
1096
|
// daemon-side copy synchronously; the worker may never get a chance to send
|
|
1085
1097
|
// its ordered revoke IPC on close/crash paths.
|
|
1086
1098
|
ds.managedTurnOrigin = undefined;
|
|
1099
|
+
// The worker/CLI generation is ending — any outstanding stuck-warning card
|
|
1100
|
+
// must be invalidated so a late click cannot inject keys into a replacement
|
|
1101
|
+
// worker (or into nothing, if no replacement comes).
|
|
1102
|
+
invalidateStuckWarning(ds, 'killWorker');
|
|
1087
1103
|
if (!ds.worker || ds.worker.killed) {
|
|
1088
1104
|
// No live worker to receive {type:'close'}, so its destroySession() — which
|
|
1089
1105
|
// tears down the persistent backing session (tmux/herdr/zellij) — never
|
|
@@ -1149,7 +1165,7 @@ function destroyOrphanedBackingSession(ds) {
|
|
|
1149
1165
|
if (!backendType)
|
|
1150
1166
|
return;
|
|
1151
1167
|
try {
|
|
1152
|
-
|
|
1168
|
+
killPersistentBackendTarget(persistentBackendTargetForSession(ds));
|
|
1153
1169
|
logger.info(`[${tag(ds)}] killWorker: no live worker — destroyed orphaned ${backendType} backing session`);
|
|
1154
1170
|
}
|
|
1155
1171
|
catch (err) {
|
|
@@ -1201,6 +1217,10 @@ export function suspendWorker(ds, reason = 'suspended_idle') {
|
|
|
1201
1217
|
ds.workerToken = null;
|
|
1202
1218
|
ds.workerViewToken = null;
|
|
1203
1219
|
ds.managedTurnOrigin = undefined;
|
|
1220
|
+
// The worker is being suspended — the CLI will be destroyed and cold-resumed
|
|
1221
|
+
// later. Invalidate any stuck-warning card so a late click cannot inject keys
|
|
1222
|
+
// after the CLI is gone (or into a different CLI on resume).
|
|
1223
|
+
invalidateStuckWarning(ds, 'suspendWorker');
|
|
1204
1224
|
// Screen state describes the process we just stopped. Keeping it would make
|
|
1205
1225
|
// the dashboard hydrate this process-less logical session as idle/working.
|
|
1206
1226
|
ds.lastScreenStatus = undefined;
|
|
@@ -1617,11 +1637,32 @@ export function sendWorkerInput(ds, payload, turnId, opts = {}) {
|
|
|
1617
1637
|
return false;
|
|
1618
1638
|
const normalized = typeof payload === 'string' ? { content: payload } : payload;
|
|
1619
1639
|
const codexAppInput = codexAppInputForSession(ds, normalized.codexAppInput, turnId);
|
|
1640
|
+
let nativeSessionTitlePrompt;
|
|
1641
|
+
let nativeSessionTitle;
|
|
1642
|
+
if (ds.session.nativeSessionTitleAwaitingContent && !ds.session.nativeSessionTitleUserDefined && !ds.adoptedFrom) {
|
|
1643
|
+
const bot = getBot(ds.larkAppId);
|
|
1644
|
+
const effectiveCliId = ds.session.cliId ?? bot.config.cliId;
|
|
1645
|
+
if (effectiveCliId === 'codex') {
|
|
1646
|
+
nativeSessionTitlePrompt = extractBotmuxLarkNativeSessionTitlePrompt(normalized.codexAppInput?.text ?? normalized.content, bot.botName ? [{ name: bot.botName }] : undefined);
|
|
1647
|
+
if (nativeSessionTitlePrompt) {
|
|
1648
|
+
nativeSessionTitle = buildBotmuxLarkNativeSessionTitle(nativeSessionTitlePrompt);
|
|
1649
|
+
ds.session.nativeSessionTitle = nativeSessionTitle;
|
|
1650
|
+
ds.session.nativeSessionTitleAwaitingContent = undefined;
|
|
1651
|
+
if (ds.initConfig) {
|
|
1652
|
+
ds.initConfig.nativeSessionTitle = nativeSessionTitle;
|
|
1653
|
+
ds.initConfig.nativeSessionTitlePrompt = nativeSessionTitlePrompt;
|
|
1654
|
+
}
|
|
1655
|
+
sessionStore.updateSession(ds.session);
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1620
1659
|
const vcMeetingImTurnOrigin = resolveVcMeetingImTurnOrigin(ds.session, turnId);
|
|
1621
1660
|
ds.worker.send({
|
|
1622
1661
|
type: 'message',
|
|
1623
1662
|
content: normalized.content,
|
|
1624
1663
|
...(codexAppInput ? { codexAppInput } : {}),
|
|
1664
|
+
...(nativeSessionTitle ? { nativeSessionTitle } : {}),
|
|
1665
|
+
...(nativeSessionTitlePrompt ? { nativeSessionTitlePrompt } : {}),
|
|
1625
1666
|
...(turnId ? { turnId } : {}),
|
|
1626
1667
|
...(opts.dispatchAttempt !== undefined ? { dispatchAttempt: opts.dispatchAttempt } : {}),
|
|
1627
1668
|
...(vcMeetingImTurnOrigin
|
|
@@ -1725,6 +1766,7 @@ export function forkWorker(ds, promptInput, resumeOrTurnId = false) {
|
|
|
1725
1766
|
if (ds.session.sandbox === undefined) {
|
|
1726
1767
|
if (!resume) {
|
|
1727
1768
|
ds.session.sandbox = botCfg.sandbox === true;
|
|
1769
|
+
ds.session.sandboxPaths = botCfg.sandboxPaths;
|
|
1728
1770
|
ds.session.sandboxHidePaths = botCfg.sandboxHidePaths ?? [];
|
|
1729
1771
|
ds.session.sandboxReadonlyPaths = botCfg.sandboxReadonlyPaths ?? [];
|
|
1730
1772
|
ds.session.sandboxNetwork = botCfg.sandboxNetwork !== false;
|
|
@@ -1737,6 +1779,10 @@ export function forkWorker(ds, promptInput, resumeOrTurnId = false) {
|
|
|
1737
1779
|
}
|
|
1738
1780
|
sessionStore.updateSession(ds.session);
|
|
1739
1781
|
}
|
|
1782
|
+
// Reserve and durably publish the replacement lifetime before killing an
|
|
1783
|
+
// existing worker. A failed reservation leaves the old worker untouched;
|
|
1784
|
+
// a successful reservation immediately invalidates any late old-worker ACK.
|
|
1785
|
+
const workerGeneration = reserveWorkerGeneration(ds);
|
|
1740
1786
|
// Guard against double-fork: if a worker is already running, kill it first
|
|
1741
1787
|
if (ds.worker && !ds.worker.killed) {
|
|
1742
1788
|
logger.warn(`[${t}] Worker already running (pid: ${ds.worker.pid}), killing before re-fork`);
|
|
@@ -1769,6 +1815,38 @@ export function forkWorker(ds, promptInput, resumeOrTurnId = false) {
|
|
|
1769
1815
|
reclaimParkedCrashDiagnostic(ds);
|
|
1770
1816
|
const agentCfg = sessionAgentConfig(ds, botCfg);
|
|
1771
1817
|
ensureCliEnv(agentCfg.cliId, agentCfg.cliPathOverride);
|
|
1818
|
+
let nativeSessionTitle;
|
|
1819
|
+
let nativeSessionTitlePrompt;
|
|
1820
|
+
if (agentCfg.cliId === 'codex' && !ds.adoptedFrom) {
|
|
1821
|
+
const isFreshNativeSession = !resume && !ds.session.cliSessionId;
|
|
1822
|
+
const titlePrompt = extractBotmuxLarkNativeSessionTitlePrompt(promptPayload.codexAppInput?.text ?? prompt, bot.botName ? [{ name: bot.botName }] : undefined);
|
|
1823
|
+
if (isFreshNativeSession && !ds.session.nativeSessionTitleUserDefined) {
|
|
1824
|
+
ds.session.nativeSessionTitle = buildBotmuxLarkNativeSessionTitle(titlePrompt ? ds.session.title : undefined, bot.botName ? [{ name: bot.botName }] : undefined, ds.chatType === 'group' ? ds.session.chatDisplayName : undefined);
|
|
1825
|
+
ds.session.nativeSessionTitleAwaitingContent = titlePrompt ? undefined : true;
|
|
1826
|
+
nativeSessionTitlePrompt = titlePrompt;
|
|
1827
|
+
sessionStore.updateSession(ds.session);
|
|
1828
|
+
}
|
|
1829
|
+
else if (ds.session.nativeSessionTitleAwaitingContent
|
|
1830
|
+
&& !ds.session.nativeSessionTitleUserDefined
|
|
1831
|
+
&& titlePrompt) {
|
|
1832
|
+
ds.session.nativeSessionTitle = buildBotmuxLarkNativeSessionTitle(titlePrompt);
|
|
1833
|
+
ds.session.nativeSessionTitleAwaitingContent = undefined;
|
|
1834
|
+
nativeSessionTitlePrompt = titlePrompt;
|
|
1835
|
+
sessionStore.updateSession(ds.session);
|
|
1836
|
+
}
|
|
1837
|
+
else if (isFreshNativeSession && !ds.session.nativeSessionTitle) {
|
|
1838
|
+
ds.session.nativeSessionTitle = ds.session.title;
|
|
1839
|
+
sessionStore.updateSession(ds.session);
|
|
1840
|
+
}
|
|
1841
|
+
if (isFreshNativeSession
|
|
1842
|
+
|| (resume && !!ds.session.cliSessionId && !!ds.session.nativeSessionTitle)
|
|
1843
|
+
|| (resume
|
|
1844
|
+
&& !!ds.session.nativeSessionTitleAwaitingContent
|
|
1845
|
+
&& !!ds.session.nativeSessionTitle)
|
|
1846
|
+
|| (!!nativeSessionTitlePrompt && !!ds.session.nativeSessionTitle)) {
|
|
1847
|
+
nativeSessionTitle = ds.session.nativeSessionTitle;
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1772
1850
|
// Claude Code blocks on the interactive folder-trust dialog the first time
|
|
1773
1851
|
// it runs in an untrusted workingDir; pre-accept it so the spawn doesn't hang.
|
|
1774
1852
|
// Seed CLI (Claude Code fork) has the same dialog — drive both off the
|
|
@@ -1874,6 +1952,7 @@ export function forkWorker(ds, promptInput, resumeOrTurnId = false) {
|
|
|
1874
1952
|
// Use the decision recorded on the session (above), NOT the live bot flag, so
|
|
1875
1953
|
// historical sessions never get retroactively sandboxed on restart.
|
|
1876
1954
|
sandbox: ds.session.sandbox === true,
|
|
1955
|
+
sandboxPaths: ds.session.sandboxPaths ?? botCfg.sandboxPaths,
|
|
1877
1956
|
sandboxHidePaths: ds.session.sandboxHidePaths ?? [],
|
|
1878
1957
|
sandboxReadonlyPaths: ds.session.sandboxReadonlyPaths ?? [],
|
|
1879
1958
|
sandboxNetwork: ds.session.sandboxNetwork !== false,
|
|
@@ -1893,10 +1972,15 @@ export function forkWorker(ds, promptInput, resumeOrTurnId = false) {
|
|
|
1893
1972
|
// getSessionPersistentBackendType). A brand-new session (no stamp) resolves
|
|
1894
1973
|
// from live config, so a dashboard backend switch only affects NEW sessions.
|
|
1895
1974
|
backendType: resolvePairedSpawnBackendType(agentCfg.cliId, ds.session.backendType, botCfg.backendType, config.daemon.backendType),
|
|
1975
|
+
// Shared Herdr is not derivable from sessionId: preserve the exact host +
|
|
1976
|
+
// managed-agent affinity across daemon/worker replacement.
|
|
1977
|
+
persistentBackendTarget: ds.session.persistentBackendTarget,
|
|
1896
1978
|
backendConfig: botCfg.riff,
|
|
1897
1979
|
riffParentTaskId: ds.session.riffParentTaskId,
|
|
1898
1980
|
riffRepoDirs: ds.session.riffRepoDirs,
|
|
1899
1981
|
deferredScheduleRun: ds.session.deferredScheduleRun,
|
|
1982
|
+
...(nativeSessionTitle ? { nativeSessionTitle } : {}),
|
|
1983
|
+
...(nativeSessionTitlePrompt ? { nativeSessionTitlePrompt } : {}),
|
|
1900
1984
|
prompt,
|
|
1901
1985
|
...(promptCodexAppInput ? { promptCodexAppInput } : {}),
|
|
1902
1986
|
resume,
|
|
@@ -1937,7 +2021,7 @@ export function forkWorker(ds, promptInput, resumeOrTurnId = false) {
|
|
|
1937
2021
|
sessionStore.updateSession(ds.session);
|
|
1938
2022
|
}
|
|
1939
2023
|
// Use shared handler for IPC messages and exit
|
|
1940
|
-
setupWorkerHandlers(ds, worker, startupState);
|
|
2024
|
+
setupWorkerHandlers(ds, worker, startupState, workerGeneration);
|
|
1941
2025
|
ds.worker = worker;
|
|
1942
2026
|
ds.spawnedAt = Date.now();
|
|
1943
2027
|
ds.cliVersion = currentCliVersion;
|
|
@@ -1967,11 +2051,57 @@ export function forkWorker(ds, promptInput, resumeOrTurnId = false) {
|
|
|
1967
2051
|
recordOwnershipForDaemonSession(ds);
|
|
1968
2052
|
}
|
|
1969
2053
|
// ─── Shared worker IPC handler ──────────────────────────────────────────────
|
|
1970
|
-
|
|
2054
|
+
/**
|
|
2055
|
+
* Clear the stuck-warning authority markers WITHOUT patching the card. Used by
|
|
2056
|
+
* ACK paths (tui_keys_delivered / stuck_warning_expired) where the caller has
|
|
2057
|
+
* already resolved the card to its final state — we only need to drop the
|
|
2058
|
+
* nonce/cardId/turnId so a late duplicate click cannot re-inject keys.
|
|
2059
|
+
*/
|
|
2060
|
+
export function clearStuckWarningAuthority(ds) {
|
|
2061
|
+
ds.stuckWarningCardId = undefined;
|
|
2062
|
+
ds.stuckWarningTurnId = undefined;
|
|
2063
|
+
ds.stuckWarningNonce = undefined;
|
|
2064
|
+
ds.stuckWarningPageType = undefined;
|
|
2065
|
+
ds.stuckWarningProcessing = false;
|
|
2066
|
+
ds.stuckWarningCliLifetime = undefined;
|
|
2067
|
+
}
|
|
2068
|
+
/**
|
|
2069
|
+
* Resolve any active stuck-warning card (PATCH it to "done") AND clear its
|
|
2070
|
+
* markers. Called from every path where the CLI can recover or be replaced:
|
|
2071
|
+
* prompt_ready, claude_exit, worker exit/kill/suspend/refork, and the worker's
|
|
2072
|
+
* own stale-card notification. Centralising here avoids missing an exit path
|
|
2073
|
+
* and leaving a clickable card that can inject keys into a different CLI.
|
|
2074
|
+
*
|
|
2075
|
+
* ACK paths (tui_keys_delivered / stuck_warning_expired) do NOT use this —
|
|
2076
|
+
* they patch the card themselves with a context-specific message and then call
|
|
2077
|
+
* clearStuckWarningAuthority() to drop the markers.
|
|
2078
|
+
*/
|
|
2079
|
+
export function invalidateStuckWarning(ds, reason) {
|
|
2080
|
+
if (!ds.stuckWarningCardId && !ds.stuckWarningTurnId && ds.stuckWarningNonce === undefined)
|
|
2081
|
+
return;
|
|
2082
|
+
const t = tag(ds);
|
|
2083
|
+
if (ds.stuckWarningCardId) {
|
|
2084
|
+
const locDs = localeForBot(ds.larkAppId);
|
|
2085
|
+
const resolvedCard = buildTuiPromptResolvedCard(tr('card.action.tui_done', undefined, locDs), locDs);
|
|
2086
|
+
updateMessage(ds.larkAppId, ds.stuckWarningCardId, resolvedCard).catch(err => logger.debug(`[${t}] Failed to resolve stuck-warning card (${reason}): ${err}`));
|
|
2087
|
+
}
|
|
2088
|
+
logger.debug(`[${t}] invalidateStuckWarning (${reason}): turn=${ds.stuckWarningTurnId ?? 'none'} nonce=${ds.stuckWarningNonce ?? 'none'}`);
|
|
2089
|
+
clearStuckWarningAuthority(ds);
|
|
2090
|
+
}
|
|
2091
|
+
function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureNotified: false }, reservedWorkerGeneration) {
|
|
1971
2092
|
const cb = requireCallbacks();
|
|
1972
2093
|
const t = tag(ds);
|
|
1973
|
-
const workerGeneration =
|
|
1974
|
-
|
|
2094
|
+
const workerGeneration = reservedWorkerGeneration
|
|
2095
|
+
?? reserveWorkerGeneration(ds);
|
|
2096
|
+
if (ds.workerGeneration !== workerGeneration
|
|
2097
|
+
|| ds.session.workerGeneration !== workerGeneration) {
|
|
2098
|
+
throw new Error('worker generation reservation changed before IPC setup');
|
|
2099
|
+
}
|
|
2100
|
+
// A new worker generation is starting. As a backstop, invalidate any
|
|
2101
|
+
// stuck-warning card posted by the previous generation — explicit kill/suspend/
|
|
2102
|
+
// exit paths should already have done this, but fork/refork/takeover paths
|
|
2103
|
+
// can leave a stale card that would otherwise inject keys into the new CLI.
|
|
2104
|
+
invalidateStuckWarning(ds, 'new_worker_generation');
|
|
1975
2105
|
// Managed turn authority is issued by one concrete worker lifetime. A
|
|
1976
2106
|
// replacement must advertise a fresh capability before daemon-mediated
|
|
1977
2107
|
// exits may use it; carrying the old value across a restore/refork would
|
|
@@ -2063,6 +2193,29 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
|
|
|
2063
2193
|
worker.on('message', async (msg) => {
|
|
2064
2194
|
const effectiveCliId = sessionCliId(ds, botCfg);
|
|
2065
2195
|
switch (msg.type) {
|
|
2196
|
+
case 'persistent_backend_target': {
|
|
2197
|
+
ds.session.persistentBackendTarget = msg.target;
|
|
2198
|
+
sessionStore.updateSession(ds.session);
|
|
2199
|
+
break;
|
|
2200
|
+
}
|
|
2201
|
+
case 'turn_input_committed': {
|
|
2202
|
+
// Bind the receipt to the exact live worker generation. A late ACK
|
|
2203
|
+
// from a replaced worker cannot make the replacement appear to have
|
|
2204
|
+
// accepted this dispatch turn.
|
|
2205
|
+
if (ds.worker !== worker
|
|
2206
|
+
|| ds.workerGeneration !== workerGeneration
|
|
2207
|
+
|| ds.session.workerGeneration !== workerGeneration) {
|
|
2208
|
+
logger.warn(`[${t}] Ignored turn_input_committed from stale worker generation`);
|
|
2209
|
+
break;
|
|
2210
|
+
}
|
|
2211
|
+
if (recordDispatchInputCommit(ds.session, msg.turnId, workerGeneration)) {
|
|
2212
|
+
sessionStore.updateSession(ds.session);
|
|
2213
|
+
}
|
|
2214
|
+
else {
|
|
2215
|
+
logger.warn(`[${t}] Ignored unbound input commit turn=${msg.turnId.slice(0, 16)}`);
|
|
2216
|
+
}
|
|
2217
|
+
break;
|
|
2218
|
+
}
|
|
2066
2219
|
case 'session_ready_ack': {
|
|
2067
2220
|
if (ds.worker !== worker) {
|
|
2068
2221
|
logger.warn(`[${t}] Ignored session_ready_ack from stale worker generation`);
|
|
@@ -2096,6 +2249,10 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
|
|
|
2096
2249
|
ds.workerViewToken = msg.viewToken ?? null;
|
|
2097
2250
|
// Persist port so it can be reused after daemon restart
|
|
2098
2251
|
ds.session.webPort = msg.port;
|
|
2252
|
+
// Dashboard「复现命令」:worker 上报本次冷启的近似复现命令。只存内存字段、
|
|
2253
|
+
// 绝不落盘(含凭证);warm reattach 时 worker 不重算,故为空——这是有意的
|
|
2254
|
+
// (reattach 不代表本次新算命令真的执行了)。worker 每次 ready 会重报。
|
|
2255
|
+
ds.spawnCommand = msg.spawnCommand;
|
|
2099
2256
|
sessionStore.updateSession(ds.session);
|
|
2100
2257
|
const readOnlyUrl = buildTerminalUrl(ds);
|
|
2101
2258
|
const writeUrl = buildTerminalUrl(ds, { write: true });
|
|
@@ -2123,6 +2280,18 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
|
|
|
2123
2280
|
logger.info(`[${t}] Managed/silent turn — suppressing ready/streaming card output`);
|
|
2124
2281
|
break;
|
|
2125
2282
|
}
|
|
2283
|
+
// Pi can finish a very fast startup turn through `botmux send` before
|
|
2284
|
+
// Herdr reports idle and the worker emits ready. Posting the initial
|
|
2285
|
+
// card now would put a stale "Starting" card *after* the final reply,
|
|
2286
|
+
// with no later transcript output to withdraw it (the explicit send
|
|
2287
|
+
// intentionally suppresses bridge fallback). Keep the terminal/runtime
|
|
2288
|
+
// state above, but suppress this already-completed turn's card.
|
|
2289
|
+
if (msg.replyAlreadySent) {
|
|
2290
|
+
ds.streamCardPending = false;
|
|
2291
|
+
persistStreamCardState(ds);
|
|
2292
|
+
logger.info(`[${t}] Explicit reply landed before worker ready — skipping stale starting card`);
|
|
2293
|
+
break;
|
|
2294
|
+
}
|
|
2126
2295
|
// Bot opted out of the streaming card: the terminal is up and the
|
|
2127
2296
|
// final answer will still arrive via `botmux send`; just don't post the
|
|
2128
2297
|
// live status card. (workerPort/token above are still set so the web
|
|
@@ -2277,6 +2446,8 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
|
|
|
2277
2446
|
break;
|
|
2278
2447
|
}
|
|
2279
2448
|
case 'prompt_ready': {
|
|
2449
|
+
if (ds.worker !== worker)
|
|
2450
|
+
break;
|
|
2280
2451
|
logger.info(`[${t}] ${getCliDisplayName(effectiveCliId)} is ready for input`);
|
|
2281
2452
|
// A live prompt means a (re)spawn reached a working CLI — clear the lazy
|
|
2282
2453
|
// cold-resume marker set when we parked a crash diagnostic shell. The
|
|
@@ -2319,6 +2490,8 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
|
|
|
2319
2490
|
...(followUpCodexAppInput ? { codexAppInput: followUpCodexAppInput } : {}),
|
|
2320
2491
|
}, { codexAppInputAccepted: !!followUpCodexAppInput });
|
|
2321
2492
|
}
|
|
2493
|
+
// CLI reached its prompt — any previously posted stuck warning is stale.
|
|
2494
|
+
invalidateStuckWarning(ds, 'prompt_ready');
|
|
2322
2495
|
break;
|
|
2323
2496
|
}
|
|
2324
2497
|
case 'cli_session_id': {
|
|
@@ -2340,6 +2513,21 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
|
|
|
2340
2513
|
}
|
|
2341
2514
|
break;
|
|
2342
2515
|
}
|
|
2516
|
+
case 'native_session_title_generated': {
|
|
2517
|
+
if (ds.worker !== worker || ds.session.nativeSessionTitleUserDefined)
|
|
2518
|
+
break;
|
|
2519
|
+
const title = msg.title.trim();
|
|
2520
|
+
if (!title)
|
|
2521
|
+
break;
|
|
2522
|
+
ds.session.nativeSessionTitle = title;
|
|
2523
|
+
ds.session.nativeSessionTitleAwaitingContent = undefined;
|
|
2524
|
+
if (ds.initConfig) {
|
|
2525
|
+
ds.initConfig.nativeSessionTitle = title;
|
|
2526
|
+
ds.initConfig.nativeSessionTitlePrompt = undefined;
|
|
2527
|
+
}
|
|
2528
|
+
sessionStore.updateSession(ds.session);
|
|
2529
|
+
break;
|
|
2530
|
+
}
|
|
2343
2531
|
case 'screen_update': {
|
|
2344
2532
|
// Wait for `ready` (workerPort) before any card work — the read link
|
|
2345
2533
|
// is the LOCAL log terminal for every backend including riff
|
|
@@ -2551,6 +2739,137 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
|
|
|
2551
2739
|
}
|
|
2552
2740
|
break;
|
|
2553
2741
|
}
|
|
2742
|
+
case 'stuck_warning': {
|
|
2743
|
+
// Ignore stuck_warning from a stale worker generation — the CLI may have
|
|
2744
|
+
// been replaced and a new worker owns the session now.
|
|
2745
|
+
if (ds.worker !== worker)
|
|
2746
|
+
break;
|
|
2747
|
+
// AI-free StuckDetector fired: a written input hasn't produced a
|
|
2748
|
+
// completed turn within the timeout AND the PTY has been quiet, AND the
|
|
2749
|
+
// snapshot matches a known Codex hook-review screen. The detector only
|
|
2750
|
+
// emits when it matches level 1 or level 2 — there is no "unknown
|
|
2751
|
+
// stall" path, so we always build an interactive card with the exact
|
|
2752
|
+
// keys the screen documents.
|
|
2753
|
+
// Dedup: skip if we already posted a warning for THIS turn, OR if a
|
|
2754
|
+
// stuck-warning card is already active (covers the no-turnId case where
|
|
2755
|
+
// a second stall fires before the first card is resolved).
|
|
2756
|
+
const isDuplicateTurn = msg.turnId !== undefined && ds.stuckWarningTurnId === msg.turnId;
|
|
2757
|
+
const hasActiveCard = !!ds.stuckWarningCardId;
|
|
2758
|
+
if (isDuplicateTurn || hasActiveCard) {
|
|
2759
|
+
logger.debug(`[${t}] Stuck warning dedup skipped (turn=${msg.turnId ?? 'none'}, activeCard=${hasActiveCard})`);
|
|
2760
|
+
break;
|
|
2761
|
+
}
|
|
2762
|
+
// Allocate a daemon-side nonce for this warning (NOT the worker's
|
|
2763
|
+
// generation — the daemon is the authority on which warning is active).
|
|
2764
|
+
// If the CLI recovers (prompt_ready) or the worker is replaced while the
|
|
2765
|
+
// POST is in flight, invalidateStuckWarning clears the nonce; when the
|
|
2766
|
+
// POST returns we check it is still current and, if not, resolve the card
|
|
2767
|
+
// immediately instead of registering it as active.
|
|
2768
|
+
// Allocate a daemon-side nonce for this warning from a monotonic
|
|
2769
|
+
// counter that is NEVER cleared (even when the active authority is
|
|
2770
|
+
// dropped). This prevents the nonce-reuse race: warning nonce=1 POST
|
|
2771
|
+
// awaits → prompt_ready clears active nonce → warning nonce=1 again
|
|
2772
|
+
// would let the old POST register against the new authority. With a
|
|
2773
|
+
// monotonic counter the second warning gets nonce=2, and the old
|
|
2774
|
+
// POST's nonce=1 check fails.
|
|
2775
|
+
const nonce = (ds.stuckWarningNonceCounter ?? 0) + 1;
|
|
2776
|
+
ds.stuckWarningNonceCounter = nonce;
|
|
2777
|
+
ds.stuckWarningNonce = nonce;
|
|
2778
|
+
ds.stuckWarningTurnId = msg.turnId;
|
|
2779
|
+
ds.stuckWarningCliLifetime = msg.cliLifetime;
|
|
2780
|
+
const pageType = msg.matchedPattern;
|
|
2781
|
+
ds.stuckWarningPageType = pageType;
|
|
2782
|
+
const secs = Math.round(msg.elapsedMs / 1000);
|
|
2783
|
+
logger.info(`[${t}] Stuck warning: turn unresolved for ${secs}s (${pageType}) nonce=${nonce}`);
|
|
2784
|
+
emitSessionLifecycleHook(ds, 'session.requires_attention', {
|
|
2785
|
+
reason: 'stuck_warning',
|
|
2786
|
+
elapsedMs: msg.elapsedMs,
|
|
2787
|
+
matchedPattern: pageType,
|
|
2788
|
+
});
|
|
2789
|
+
if (managedAuxUiSuppressed(msg.turnId, msg.dispatchAttempt))
|
|
2790
|
+
break;
|
|
2791
|
+
let description;
|
|
2792
|
+
let options;
|
|
2793
|
+
if (pageType === 'hook review level 2') {
|
|
2794
|
+
// Level 2 — per-hook review: t=trust, Esc=go back. No Enter here.
|
|
2795
|
+
description = `⚠️ Codex 卡在单项 hook 审核界面——消息已写入但 ${secs}s 未完成处理。\n\n选择操作以继续,或打开终端手动处理:`;
|
|
2796
|
+
options = [
|
|
2797
|
+
{ label: 't', text: '信任 (trust)', selected: false, type: 'confirm', keys: ['t'] },
|
|
2798
|
+
{ label: 'Esc', text: '返回 (go back)', selected: false, type: 'select', keys: ['Escape'] },
|
|
2799
|
+
];
|
|
2800
|
+
}
|
|
2801
|
+
else {
|
|
2802
|
+
// Level 1 — hooks browser: t=trust all, Enter=review hooks, Esc=close.
|
|
2803
|
+
description = `⚠️ Codex 卡在 hook 审核界面——消息已写入但 ${secs}s 未完成处理。\n\n选择操作以继续,或打开终端手动处理:`;
|
|
2804
|
+
options = [
|
|
2805
|
+
{ label: 't', text: '信任全部 (trust all)', selected: false, type: 'confirm', keys: ['t'] },
|
|
2806
|
+
{ label: 'Enter', text: '逐项审核 (review hooks)', selected: false, type: 'select', keys: ['Enter'] },
|
|
2807
|
+
{ label: 'Esc', text: '关闭 (close)', selected: false, type: 'select', keys: ['Escape'] },
|
|
2808
|
+
];
|
|
2809
|
+
}
|
|
2810
|
+
try {
|
|
2811
|
+
const cardJson = buildTuiPromptCard(sessionAnchorId(ds), ds.session.sessionId, description, options, false, undefined, loc);
|
|
2812
|
+
const cardMsgId = await scopedReply(cardJson, 'interactive', msg.turnId);
|
|
2813
|
+
// Authority check: if the warning was invalidated (prompt_ready,
|
|
2814
|
+
// CLI exit, worker replace) OR the worker was replaced while the POST
|
|
2815
|
+
// was in flight, resolve the card immediately and do NOT register it
|
|
2816
|
+
// as active — a late click would otherwise inject keys into a
|
|
2817
|
+
// recovered/replaced CLI. Verify the full tuple: same worker process,
|
|
2818
|
+
// same worker generation, same nonce.
|
|
2819
|
+
if (ds.worker !== worker || ds.workerGeneration !== workerGeneration || ds.stuckWarningNonce !== nonce) {
|
|
2820
|
+
logger.debug(`[${t}] Stuck warning card POSTed but authority stale (nonce=${nonce}, current=${ds.stuckWarningNonce ?? 'none'}, workerGen=${workerGeneration}) — resolving`);
|
|
2821
|
+
const locDs = localeForBot(ds.larkAppId);
|
|
2822
|
+
const resolvedCard = buildTuiPromptResolvedCard(tr('card.action.tui_done', undefined, locDs), locDs);
|
|
2823
|
+
updateMessage(ds.larkAppId, cardMsgId, resolvedCard).catch(err => logger.debug(`[${t}] Failed to resolve stale stuck-warning card: ${err}`));
|
|
2824
|
+
break;
|
|
2825
|
+
}
|
|
2826
|
+
ds.stuckWarningCardId = cardMsgId;
|
|
2827
|
+
publishAttentionPatch(ds);
|
|
2828
|
+
}
|
|
2829
|
+
catch (err) {
|
|
2830
|
+
logger.warn(`[${t}] Failed to post stuck warning card: ${err}`);
|
|
2831
|
+
// Card send failed — clear markers ONLY if this nonce is still
|
|
2832
|
+
// current AND we still own the worker. A newer warning (nonce+1) may
|
|
2833
|
+
// have started while this POST was in flight; we must not wipe the
|
|
2834
|
+
// newer state.
|
|
2835
|
+
if (ds.worker === worker && ds.workerGeneration === workerGeneration && ds.stuckWarningNonce === nonce) {
|
|
2836
|
+
clearStuckWarningAuthority(ds);
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
break;
|
|
2840
|
+
}
|
|
2841
|
+
case 'stuck_warning_expired': {
|
|
2842
|
+
// Ignore from a stale worker generation.
|
|
2843
|
+
if (ds.worker !== worker || ds.workerGeneration !== workerGeneration)
|
|
2844
|
+
break;
|
|
2845
|
+
// Worker refused to inject keys from a stuck-warning card because the
|
|
2846
|
+
// current screen no longer matches the page type the card was built for.
|
|
2847
|
+
// Resolve the card with a "page changed" message so the user knows the
|
|
2848
|
+
// action was NOT performed, then clear the authority.
|
|
2849
|
+
if (ds.stuckWarningNonce === msg.nonce && ds.stuckWarningCardId) {
|
|
2850
|
+
const locDs = localeForBot(ds.larkAppId);
|
|
2851
|
+
const resolvedCard = buildTuiPromptResolvedCard('页面已变化,未发送按键', locDs);
|
|
2852
|
+
updateMessage(ds.larkAppId, ds.stuckWarningCardId, resolvedCard).catch(err => logger.debug(`[${t}] Failed to update stuck-warning card on expired: ${err}`));
|
|
2853
|
+
clearStuckWarningAuthority(ds);
|
|
2854
|
+
}
|
|
2855
|
+
break;
|
|
2856
|
+
}
|
|
2857
|
+
case 'tui_keys_delivered': {
|
|
2858
|
+
// Ignore from a stale worker generation.
|
|
2859
|
+
if (ds.worker !== worker || ds.workerGeneration !== workerGeneration)
|
|
2860
|
+
break;
|
|
2861
|
+
// Worker confirmed the keys were written to the PTY. Clear the card
|
|
2862
|
+
// authority and render success. We only do this AFTER the worker ACK
|
|
2863
|
+
// (not on click), so a rejected click (stuck_warning_expired) does not
|
|
2864
|
+
// falsely report success.
|
|
2865
|
+
if (ds.stuckWarningNonce === msg.nonce && ds.stuckWarningCardId) {
|
|
2866
|
+
const locDs = localeForBot(ds.larkAppId);
|
|
2867
|
+
const resolvedCard = buildTuiPromptResolvedCard(tr('card.action.tui_done', undefined, locDs), locDs);
|
|
2868
|
+
updateMessage(ds.larkAppId, ds.stuckWarningCardId, resolvedCard).catch(err => logger.debug(`[${t}] Failed to resolve stuck-warning card on delivered: ${err}`));
|
|
2869
|
+
clearStuckWarningAuthority(ds);
|
|
2870
|
+
}
|
|
2871
|
+
break;
|
|
2872
|
+
}
|
|
2554
2873
|
case 'claude_exit': {
|
|
2555
2874
|
// CLI-generation authority must not outlive the concrete worker/CLI
|
|
2556
2875
|
// pair that issued it. A delayed message from a replaced Node worker
|
|
@@ -2560,6 +2879,10 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
|
|
|
2560
2879
|
break;
|
|
2561
2880
|
}
|
|
2562
2881
|
ds.managedTurnOrigin = undefined;
|
|
2882
|
+
// The worker/CLI generation ended. Disable an outstanding stuck-warning
|
|
2883
|
+
// card before any replacement worker can be attached; otherwise a late
|
|
2884
|
+
// click could inject its keys into the replacement CLI.
|
|
2885
|
+
invalidateStuckWarning(ds, 'claude_exit');
|
|
2563
2886
|
logger.info(`[${t}] ${getCliDisplayName(effectiveCliId)} exited (code: ${msg.code}, signal: ${msg.signal})`);
|
|
2564
2887
|
ds.hasHistory = true;
|
|
2565
2888
|
try {
|
|
@@ -2979,6 +3302,19 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
|
|
|
2979
3302
|
ds.worker = null;
|
|
2980
3303
|
ds.workerPort = null;
|
|
2981
3304
|
ds.managedTurnOrigin = undefined;
|
|
3305
|
+
// This worker generation is gone. Invalidate any stuck-warning card it
|
|
3306
|
+
// posted so a late click cannot inject keys into a replacement worker.
|
|
3307
|
+
invalidateStuckWarning(ds, 'worker_exit');
|
|
3308
|
+
// Fence this lifetime before a polling dispatcher can observe its last
|
|
3309
|
+
// ACK. Keeping the old receipt is useful audit evidence, but the
|
|
3310
|
+
// persisted current generation advances immediately so it cannot count
|
|
3311
|
+
// as acceptance after the worker has died. A stale takeover worker never
|
|
3312
|
+
// enters this branch and therefore cannot fence the replacement.
|
|
3313
|
+
const fencedGeneration = Math.max(workerGeneration, ds.workerGeneration ?? 0, ds.session.workerGeneration ?? 0) + 1;
|
|
3314
|
+
ds.workerGeneration = fencedGeneration;
|
|
3315
|
+
ds.session.workerGeneration = fencedGeneration;
|
|
3316
|
+
ds.session.pid = undefined;
|
|
3317
|
+
sessionStore.updateSession(ds.session);
|
|
2982
3318
|
}
|
|
2983
3319
|
try {
|
|
2984
3320
|
const notified = cb.onWorkerExit?.(ds, {
|
|
@@ -3345,6 +3681,52 @@ export const __testOnly_setupWorkerHandlers = setupWorkerHandlers;
|
|
|
3345
3681
|
export const __testOnly_finishTurnReactions = finishTurnReactions;
|
|
3346
3682
|
export const __testOnly_finalOutputDedupeKey = finalOutputDedupeKey;
|
|
3347
3683
|
// ─── Fork adopt worker ──────────────────────────────────────────────────────
|
|
3684
|
+
function reserveWorkerGeneration(ds) {
|
|
3685
|
+
const previousDaemonGeneration = ds.workerGeneration;
|
|
3686
|
+
const previousSessionGeneration = ds.session.workerGeneration;
|
|
3687
|
+
const workerGeneration = Math.max(previousDaemonGeneration ?? 0, previousSessionGeneration ?? 0) + 1;
|
|
3688
|
+
ds.workerGeneration = workerGeneration;
|
|
3689
|
+
ds.session.workerGeneration = workerGeneration;
|
|
3690
|
+
try {
|
|
3691
|
+
sessionStore.updateSession(ds.session);
|
|
3692
|
+
}
|
|
3693
|
+
catch (error) {
|
|
3694
|
+
if (previousDaemonGeneration === undefined)
|
|
3695
|
+
delete ds.workerGeneration;
|
|
3696
|
+
else
|
|
3697
|
+
ds.workerGeneration = previousDaemonGeneration;
|
|
3698
|
+
if (previousSessionGeneration === undefined)
|
|
3699
|
+
delete ds.session.workerGeneration;
|
|
3700
|
+
else
|
|
3701
|
+
ds.session.workerGeneration = previousSessionGeneration;
|
|
3702
|
+
throw error;
|
|
3703
|
+
}
|
|
3704
|
+
return workerGeneration;
|
|
3705
|
+
}
|
|
3706
|
+
/**
|
|
3707
|
+
* Is the file sandbox active such that /adopt must be refused? A sandbox can
|
|
3708
|
+
* only be established at spawn time (bwrap wrap / Seatbelt profile); adopt
|
|
3709
|
+
* attaches to an ALREADY-running host process, so it could only ever run
|
|
3710
|
+
* UNsandboxed. From-strict UNION of every source that can require isolation:
|
|
3711
|
+
* - live per-bot `sandbox` / legacy `readIsolation`,
|
|
3712
|
+
* - the global `BOTMUX_SANDBOX=1` (sandboxEnabled()),
|
|
3713
|
+
* - the session's FROZEN sandbox decision (`session.sandbox`, recorded at
|
|
3714
|
+
* creation). forkWorker treats the frozen decision as authoritative, so a
|
|
3715
|
+
* session created sandboxed must stay un-adoptable even if the admin later
|
|
3716
|
+
* toggles the bot's global flag OFF — otherwise its `/adopt` would attach to
|
|
3717
|
+
* an unsandboxed live process. The reverse (bot=true / session=false) is
|
|
3718
|
+
* also caught by the union, which is the safe direction.
|
|
3719
|
+
* Callers reject at the ENTRY point (before persisting `adoptedFrom` / replying
|
|
3720
|
+
* "adopted") — see command-handler's `/adopt`, the adopt_select card, and the
|
|
3721
|
+
* session-manager restore branch. Resume/cold-start is unaffected: those spawn
|
|
3722
|
+
* a fresh CLI, which the sandbox wraps normally.
|
|
3723
|
+
*/
|
|
3724
|
+
export function adoptSandboxBlocked(botCfg, session) {
|
|
3725
|
+
return botCfg.sandbox === true
|
|
3726
|
+
|| botCfg.readIsolation === true
|
|
3727
|
+
|| session?.sandbox === true
|
|
3728
|
+
|| sandboxEnabled();
|
|
3729
|
+
}
|
|
3348
3730
|
export function forkAdoptWorker(ds, opts) {
|
|
3349
3731
|
const cb = requireCallbacks();
|
|
3350
3732
|
const workerPath = join(__dirname, '..', 'worker.js');
|
|
@@ -3354,14 +3736,35 @@ export function forkAdoptWorker(ds, opts) {
|
|
|
3354
3736
|
throw new Error('forkAdoptWorker called without adoptedFrom');
|
|
3355
3737
|
const bot = getBot(ds.larkAppId);
|
|
3356
3738
|
const botCfg = bot.config;
|
|
3357
|
-
//
|
|
3358
|
-
// to an existing pane
|
|
3359
|
-
//
|
|
3360
|
-
//
|
|
3361
|
-
|
|
3362
|
-
|
|
3739
|
+
// A file sandbox cannot be applied to an already-running CLI: adopt ATTACHES
|
|
3740
|
+
// to an existing host pane/process, and confinement (bwrap wrap on Linux /
|
|
3741
|
+
// Seatbelt profile on macOS) can only be established at spawn time — there is
|
|
3742
|
+
// no way to retro-fit a sandbox around a live process. Refuse to adopt when
|
|
3743
|
+
// the sandbox is active (live bot flag OR the session's FROZEN decision)
|
|
3744
|
+
// rather than run it UNsandboxed. Real adopt entry points (`/adopt`, the
|
|
3745
|
+
// adopt_select card) reject BEFORE persisting `adoptedFrom`, and the
|
|
3746
|
+
// session-manager restore branch converts a sandbox adopt to a cold-start
|
|
3747
|
+
// before it ever reaches here; this is the last-line fail-closed backstop,
|
|
3748
|
+
// which also strips any stale adopt metadata it is handed.
|
|
3749
|
+
if (adoptSandboxBlocked(botCfg, ds.session)) {
|
|
3750
|
+
logger.warn(`[${t}] sandbox-enabled session: refusing to adopt existing CLI (would run unsandboxed)`);
|
|
3751
|
+
// Strip stale adopt state so the session doesn't linger as a worker=null
|
|
3752
|
+
// pseudo-adopt whose NEXT message
|
|
3753
|
+
// would still be routed as a bridge/adopt session. It cold-starts sandboxed
|
|
3754
|
+
// via the normal resume path instead.
|
|
3755
|
+
ds.adoptedFrom = undefined;
|
|
3756
|
+
if (ds.session.adoptedFrom) {
|
|
3757
|
+
ds.session.adoptedFrom = undefined;
|
|
3758
|
+
try {
|
|
3759
|
+
sessionStore.updateSession(ds.session);
|
|
3760
|
+
}
|
|
3761
|
+
catch { /* best-effort */ }
|
|
3762
|
+
}
|
|
3363
3763
|
return;
|
|
3364
3764
|
}
|
|
3765
|
+
// Reserve before replacing an existing bridge worker for the same reason as
|
|
3766
|
+
// forkWorker: persistence failure must leave the old lifetime untouched.
|
|
3767
|
+
const workerGeneration = reserveWorkerGeneration(ds);
|
|
3365
3768
|
// Guard against double-fork
|
|
3366
3769
|
if (ds.worker && !ds.worker.killed) {
|
|
3367
3770
|
logger.warn(`[${t}] Worker already running, killing before adopt-fork`);
|
|
@@ -3541,7 +3944,7 @@ export function forkAdoptWorker(ds, opts) {
|
|
|
3541
3944
|
sessionStore.updateSession(ds.session);
|
|
3542
3945
|
}
|
|
3543
3946
|
// Use shared handler
|
|
3544
|
-
setupWorkerHandlers(ds, worker, startupState);
|
|
3947
|
+
setupWorkerHandlers(ds, worker, startupState, workerGeneration);
|
|
3545
3948
|
ds.worker = worker;
|
|
3546
3949
|
ds.spawnedAt = Date.now();
|
|
3547
3950
|
ds.cliVersion = '';
|
|
@@ -3705,9 +4108,17 @@ function cleanupPersistentBackendSessions(backendType, activeSessions_) {
|
|
|
3705
4108
|
const currentCliId = config.daemon.cliId;
|
|
3706
4109
|
if (!multiBot && lastCliId && lastCliId !== currentCliId) {
|
|
3707
4110
|
logger.info(`CLI_ID changed (${lastCliId} → ${currentCliId}), killing all ${backendType} sessions`);
|
|
4111
|
+
// Legacy per-topic hosts are still enumerable by bmx-* name.
|
|
3708
4112
|
for (const name of backend.listBotmuxSessions()) {
|
|
3709
4113
|
backend.killSession(name);
|
|
3710
4114
|
}
|
|
4115
|
+
// Machine-wide Herdr agents are not separate bmx-* sessions. Tear down
|
|
4116
|
+
// each persisted managed target precisely so a CLI switch cannot reattach
|
|
4117
|
+
// the old executable, while never stopping the shared `botmux` host or an
|
|
4118
|
+
// explicitly adopted user pane.
|
|
4119
|
+
for (const target of managedTargetsForCliChange(backendType, activeSessions_)) {
|
|
4120
|
+
killPersistentBackendTarget(target);
|
|
4121
|
+
}
|
|
3711
4122
|
}
|
|
3712
4123
|
else {
|
|
3713
4124
|
const activeNames = new Set(activeSessions_.map(s => backend.sessionName(s.sessionId)));
|
|
@@ -3730,9 +4141,12 @@ function cleanupPersistentBackendSessions(backendType, activeSessions_) {
|
|
|
3730
4141
|
continue;
|
|
3731
4142
|
}
|
|
3732
4143
|
if (botCliId && sessionCliId !== botCliId) {
|
|
3733
|
-
const
|
|
3734
|
-
|
|
3735
|
-
|
|
4144
|
+
const target = resolvePersistentBackendTarget(backendType, session.sessionId, session.persistentBackendTarget);
|
|
4145
|
+
const label = target.backendType === 'herdr' && target.agentName
|
|
4146
|
+
? `${target.sessionName}/${target.agentName}`
|
|
4147
|
+
: target.sessionName;
|
|
4148
|
+
logger.info(`CLI mismatch for ${session.sessionId.substring(0, 8)} (session=${sessionCliId}, bot=${botCliId}), killing ${backendType} ${label}`);
|
|
4149
|
+
killPersistentBackendTarget(target);
|
|
3736
4150
|
}
|
|
3737
4151
|
}
|
|
3738
4152
|
}
|