botmux 2.47.0 → 2.47.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 -5
- package/README.md +10 -5
- package/dist/adapters/adopt-route.d.ts +63 -0
- package/dist/adapters/adopt-route.d.ts.map +1 -0
- package/dist/adapters/adopt-route.js +195 -0
- package/dist/adapters/adopt-route.js.map +1 -0
- package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
- package/dist/adapters/backend/tmux-backend.js +11 -0
- package/dist/adapters/backend/tmux-backend.js.map +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.d.ts +11 -0
- package/dist/adapters/backend/tmux-pipe-backend.d.ts.map +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.js +17 -1
- package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
- package/dist/adapters/cli/claude-code.d.ts.map +1 -1
- package/dist/adapters/cli/claude-code.js +36 -9
- package/dist/adapters/cli/claude-code.js.map +1 -1
- package/dist/adapters/cli/coco.d.ts.map +1 -1
- package/dist/adapters/cli/coco.js +26 -1
- package/dist/adapters/cli/coco.js.map +1 -1
- package/dist/adapters/cli/codex-app.d.ts +4 -0
- package/dist/adapters/cli/codex-app.d.ts.map +1 -0
- package/dist/adapters/cli/codex-app.js +72 -0
- package/dist/adapters/cli/codex-app.js.map +1 -0
- package/dist/adapters/cli/codex.d.ts.map +1 -1
- package/dist/adapters/cli/codex.js +34 -17
- package/dist/adapters/cli/codex.js.map +1 -1
- package/dist/adapters/cli/cursor.d.ts.map +1 -1
- package/dist/adapters/cli/cursor.js +58 -12
- package/dist/adapters/cli/cursor.js.map +1 -1
- package/dist/adapters/cli/gemini.d.ts.map +1 -1
- package/dist/adapters/cli/gemini.js +5 -1
- package/dist/adapters/cli/gemini.js.map +1 -1
- package/dist/adapters/cli/hermes.d.ts +4 -0
- package/dist/adapters/cli/hermes.d.ts.map +1 -0
- package/dist/adapters/cli/hermes.js +40 -0
- package/dist/adapters/cli/hermes.js.map +1 -0
- package/dist/adapters/cli/mira.d.ts +4 -0
- package/dist/adapters/cli/mira.d.ts.map +1 -0
- package/dist/adapters/cli/mira.js +67 -0
- package/dist/adapters/cli/mira.js.map +1 -0
- package/dist/adapters/cli/mtr.d.ts +5 -0
- package/dist/adapters/cli/mtr.d.ts.map +1 -0
- package/dist/adapters/cli/mtr.js +62 -0
- package/dist/adapters/cli/mtr.js.map +1 -0
- package/dist/adapters/cli/opencode.d.ts.map +1 -1
- package/dist/adapters/cli/opencode.js +19 -1
- package/dist/adapters/cli/opencode.js.map +1 -1
- package/dist/adapters/cli/registry.d.ts +5 -1
- package/dist/adapters/cli/registry.d.ts.map +1 -1
- package/dist/adapters/cli/registry.js +22 -2
- package/dist/adapters/cli/registry.js.map +1 -1
- package/dist/adapters/cli/shared-hints.d.ts +1 -1
- package/dist/adapters/cli/shared-hints.d.ts.map +1 -1
- package/dist/adapters/cli/shared-hints.js +2 -1
- package/dist/adapters/cli/shared-hints.js.map +1 -1
- package/dist/adapters/cli/types.d.ts +35 -2
- package/dist/adapters/cli/types.d.ts.map +1 -1
- package/dist/adapters/hook-command.d.ts +18 -0
- package/dist/adapters/hook-command.d.ts.map +1 -0
- package/dist/adapters/hook-command.js +38 -0
- package/dist/adapters/hook-command.js.map +1 -0
- package/dist/adapters/hook-installer.d.ts +14 -0
- package/dist/adapters/hook-installer.d.ts.map +1 -0
- package/dist/adapters/hook-installer.js +192 -0
- package/dist/adapters/hook-installer.js.map +1 -0
- package/dist/bot-registry.d.ts +59 -0
- package/dist/bot-registry.d.ts.map +1 -1
- package/dist/bot-registry.js +67 -0
- package/dist/bot-registry.js.map +1 -1
- package/dist/cli/bots-list-output.d.ts +8 -0
- package/dist/cli/bots-list-output.d.ts.map +1 -1
- package/dist/cli/bots-list-output.js +9 -0
- package/dist/cli/bots-list-output.js.map +1 -1
- package/dist/cli.d.ts +15 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +603 -106
- package/dist/cli.js.map +1 -1
- package/dist/codex-app-runner.d.ts +3 -0
- package/dist/codex-app-runner.d.ts.map +1 -0
- package/dist/codex-app-runner.js +512 -0
- package/dist/codex-app-runner.js.map +1 -0
- package/dist/config.d.ts +11 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +17 -4
- package/dist/config.js.map +1 -1
- package/dist/core/ask-api.d.ts +47 -0
- package/dist/core/ask-api.d.ts.map +1 -0
- package/dist/core/ask-api.js +139 -0
- package/dist/core/ask-api.js.map +1 -0
- package/dist/core/ask-args.d.ts +53 -0
- package/dist/core/ask-args.d.ts.map +1 -0
- package/dist/core/ask-args.js +122 -0
- package/dist/core/ask-args.js.map +1 -0
- package/dist/core/ask-broker.d.ts +98 -0
- package/dist/core/ask-broker.d.ts.map +1 -0
- package/dist/core/ask-broker.js +329 -0
- package/dist/core/ask-broker.js.map +1 -0
- package/dist/core/ask-hook/claude-code.d.ts +50 -0
- package/dist/core/ask-hook/claude-code.d.ts.map +1 -0
- package/dist/core/ask-hook/claude-code.js +145 -0
- package/dist/core/ask-hook/claude-code.js.map +1 -0
- package/dist/core/ask-hook/codex.d.ts +43 -0
- package/dist/core/ask-hook/codex.d.ts.map +1 -0
- package/dist/core/ask-hook/codex.js +69 -0
- package/dist/core/ask-hook/codex.js.map +1 -0
- package/dist/core/ask-hook/opencode.d.ts +41 -0
- package/dist/core/ask-hook/opencode.d.ts.map +1 -0
- package/dist/core/ask-hook/opencode.js +108 -0
- package/dist/core/ask-hook/opencode.js.map +1 -0
- package/dist/core/ask-hook/registry.d.ts +3 -0
- package/dist/core/ask-hook/registry.d.ts.map +1 -0
- package/dist/core/ask-hook/registry.js +12 -0
- package/dist/core/ask-hook/registry.js.map +1 -0
- package/dist/core/ask-hook/types.d.ts +26 -0
- package/dist/core/ask-hook/types.d.ts.map +1 -0
- package/dist/core/ask-hook/types.js +2 -0
- package/dist/core/ask-hook/types.js.map +1 -0
- package/dist/core/ask-types.d.ts +146 -0
- package/dist/core/ask-types.d.ts.map +1 -0
- package/dist/core/ask-types.js +18 -0
- package/dist/core/ask-types.js.map +1 -0
- package/dist/core/command-handler.d.ts +29 -0
- package/dist/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +787 -312
- package/dist/core/command-handler.js.map +1 -1
- package/dist/core/dashboard-ipc-server.d.ts +2 -0
- package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
- package/dist/core/dashboard-ipc-server.js +222 -2
- package/dist/core/dashboard-ipc-server.js.map +1 -1
- package/dist/core/role-resolver.d.ts +17 -1
- package/dist/core/role-resolver.d.ts.map +1 -1
- package/dist/core/role-resolver.js +64 -10
- package/dist/core/role-resolver.js.map +1 -1
- package/dist/core/session-discovery.d.ts.map +1 -1
- package/dist/core/session-discovery.js +19 -5
- package/dist/core/session-discovery.js.map +1 -1
- package/dist/core/session-manager.d.ts +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +37 -20
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/trigger-session.d.ts +9 -0
- package/dist/core/trigger-session.d.ts.map +1 -0
- package/dist/core/trigger-session.js +158 -0
- package/dist/core/trigger-session.js.map +1 -0
- package/dist/core/types.d.ts +5 -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 +141 -0
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +543 -24
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +224 -60
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/auth.d.ts +6 -1
- package/dist/dashboard/auth.d.ts.map +1 -1
- package/dist/dashboard/auth.js +9 -1
- package/dist/dashboard/auth.js.map +1 -1
- package/dist/dashboard/connector-api.d.ts +3 -0
- package/dist/dashboard/connector-api.d.ts.map +1 -0
- package/dist/dashboard/connector-api.js +351 -0
- package/dist/dashboard/connector-api.js.map +1 -0
- package/dist/dashboard/federated-group-core.d.ts +54 -0
- package/dist/dashboard/federated-group-core.d.ts.map +1 -0
- package/dist/dashboard/federated-group-core.js +165 -0
- package/dist/dashboard/federated-group-core.js.map +1 -0
- package/dist/dashboard/federation-api.d.ts +42 -0
- package/dist/dashboard/federation-api.d.ts.map +1 -0
- package/dist/dashboard/federation-api.js +408 -0
- package/dist/dashboard/federation-api.js.map +1 -0
- package/dist/dashboard/federation-spoke-api.d.ts +76 -0
- package/dist/dashboard/federation-spoke-api.d.ts.map +1 -0
- package/dist/dashboard/federation-spoke-api.js +618 -0
- package/dist/dashboard/federation-spoke-api.js.map +1 -0
- package/dist/dashboard/team-group.d.ts +18 -0
- package/dist/dashboard/team-group.d.ts.map +1 -0
- package/dist/dashboard/team-group.js +7 -0
- package/dist/dashboard/team-group.js.map +1 -0
- package/dist/dashboard/trigger-api.d.ts +13 -0
- package/dist/dashboard/trigger-api.d.ts.map +1 -0
- package/dist/dashboard/trigger-api.js +77 -0
- package/dist/dashboard/trigger-api.js.map +1 -0
- package/dist/dashboard/web/app.js +8 -0
- package/dist/dashboard/web/app.js.map +1 -1
- package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
- package/dist/dashboard/web/bot-defaults.js +205 -21
- package/dist/dashboard/web/bot-defaults.js.map +1 -1
- package/dist/dashboard/web/connectors.d.ts +2 -0
- package/dist/dashboard/web/connectors.d.ts.map +1 -0
- package/dist/dashboard/web/connectors.js +187 -0
- package/dist/dashboard/web/connectors.js.map +1 -0
- package/dist/dashboard/web/i18n.d.ts.map +1 -1
- package/dist/dashboard/web/i18n.js +43 -5
- package/dist/dashboard/web/i18n.js.map +1 -1
- package/dist/dashboard/web/sessions.d.ts.map +1 -1
- package/dist/dashboard/web/sessions.js +4 -0
- package/dist/dashboard/web/sessions.js.map +1 -1
- package/dist/dashboard/web/team-federation.d.ts +3 -0
- package/dist/dashboard/web/team-federation.d.ts.map +1 -0
- package/dist/dashboard/web/team-federation.js +487 -0
- package/dist/dashboard/web/team-federation.js.map +1 -0
- package/dist/dashboard/web/workflows.js +3 -3
- package/dist/dashboard/web/workflows.js.map +1 -1
- package/dist/dashboard/webhook-routes.d.ts +19 -0
- package/dist/dashboard/webhook-routes.d.ts.map +1 -0
- package/dist/dashboard/webhook-routes.js +321 -0
- package/dist/dashboard/webhook-routes.js.map +1 -0
- package/dist/dashboard/workflow-api.d.ts +8 -1
- package/dist/dashboard/workflow-api.d.ts.map +1 -1
- package/dist/dashboard/workflow-api.js +19 -4
- package/dist/dashboard/workflow-api.js.map +1 -1
- package/dist/dashboard-web/app.js +539 -375
- package/dist/dashboard-web/index.html +3 -1
- package/dist/dashboard-web/style.css +22 -0
- package/dist/dashboard.js +199 -2
- package/dist/dashboard.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +104 -11
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +104 -11
- package/dist/i18n/zh.js.map +1 -1
- package/dist/im/lark/ask-card.d.ts +55 -0
- package/dist/im/lark/ask-card.d.ts.map +1 -0
- package/dist/im/lark/ask-card.js +328 -0
- package/dist/im/lark/ask-card.js.map +1 -0
- package/dist/im/lark/card-builder.d.ts +108 -3
- package/dist/im/lark/card-builder.d.ts.map +1 -1
- package/dist/im/lark/card-builder.js +480 -50
- package/dist/im/lark/card-builder.js.map +1 -1
- package/dist/im/lark/card-handler.d.ts.map +1 -1
- package/dist/im/lark/card-handler.js +241 -18
- package/dist/im/lark/card-handler.js.map +1 -1
- package/dist/im/lark/client.d.ts +83 -0
- package/dist/im/lark/client.d.ts.map +1 -1
- package/dist/im/lark/client.js +286 -70
- 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 +29 -4
- package/dist/im/lark/event-dispatcher.js.map +1 -1
- package/dist/im/lark/grant-command.d.ts +2 -1
- package/dist/im/lark/grant-command.d.ts.map +1 -1
- package/dist/im/lark/grant-command.js +3 -2
- package/dist/im/lark/grant-command.js.map +1 -1
- package/dist/im/lark/identity-cache.d.ts.map +1 -1
- package/dist/im/lark/identity-cache.js +3 -3
- package/dist/im/lark/identity-cache.js.map +1 -1
- package/dist/im/lark/md-card.d.ts +20 -2
- package/dist/im/lark/md-card.d.ts.map +1 -1
- package/dist/im/lark/md-card.js +49 -17
- package/dist/im/lark/md-card.js.map +1 -1
- package/dist/im/lark/message-parser.d.ts.map +1 -1
- package/dist/im/lark/message-parser.js +87 -31
- package/dist/im/lark/message-parser.js.map +1 -1
- package/dist/im/lark/workflow-card-handler.d.ts +2 -2
- package/dist/im/lark/workflow-card-handler.d.ts.map +1 -1
- package/dist/im/lark/workflow-card-handler.js +12 -1
- package/dist/im/lark/workflow-card-handler.js.map +1 -1
- package/dist/im/lark/workflow-progress-card.d.ts.map +1 -1
- package/dist/im/lark/workflow-progress-card.js +53 -0
- package/dist/im/lark/workflow-progress-card.js.map +1 -1
- package/dist/mira-output.d.ts +3 -0
- package/dist/mira-output.d.ts.map +1 -0
- package/dist/mira-output.js +136 -0
- package/dist/mira-output.js.map +1 -0
- package/dist/mira-runner.d.ts +3 -0
- package/dist/mira-runner.d.ts.map +1 -0
- package/dist/mira-runner.js +534 -0
- package/dist/mira-runner.js.map +1 -0
- package/dist/services/bot-owner-store.d.ts +28 -0
- package/dist/services/bot-owner-store.d.ts.map +1 -0
- package/dist/services/bot-owner-store.js +82 -0
- package/dist/services/bot-owner-store.js.map +1 -0
- package/dist/services/bot-profile-store.d.ts +16 -0
- package/dist/services/bot-profile-store.d.ts.map +1 -0
- package/dist/services/bot-profile-store.js +98 -0
- package/dist/services/bot-profile-store.js.map +1 -0
- package/dist/services/brand-store.d.ts +15 -0
- package/dist/services/brand-store.d.ts.map +1 -0
- package/dist/services/brand-store.js +47 -0
- package/dist/services/brand-store.js.map +1 -0
- package/dist/services/card-prefs-store.d.ts +20 -0
- package/dist/services/card-prefs-store.d.ts.map +1 -0
- package/dist/services/card-prefs-store.js +82 -0
- package/dist/services/card-prefs-store.js.map +1 -0
- 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 +23 -0
- package/dist/services/codex-bridge-queue.js.map +1 -1
- package/dist/services/codex-transcript.d.ts +1 -0
- package/dist/services/codex-transcript.d.ts.map +1 -1
- package/dist/services/codex-transcript.js.map +1 -1
- package/dist/services/connector-store.d.ts +58 -0
- package/dist/services/connector-store.d.ts.map +1 -0
- package/dist/services/connector-store.js +79 -0
- package/dist/services/connector-store.js.map +1 -0
- package/dist/services/deployment-identity.d.ts +22 -0
- package/dist/services/deployment-identity.d.ts.map +1 -0
- package/dist/services/deployment-identity.js +67 -0
- package/dist/services/deployment-identity.js.map +1 -0
- package/dist/services/federation-membership-store.d.ts +23 -0
- package/dist/services/federation-membership-store.d.ts.map +1 -0
- package/dist/services/federation-membership-store.js +66 -0
- package/dist/services/federation-membership-store.js.map +1 -0
- package/dist/services/federation-roster.d.ts +54 -0
- package/dist/services/federation-roster.d.ts.map +1 -0
- package/dist/services/federation-roster.js +51 -0
- package/dist/services/federation-roster.js.map +1 -0
- package/dist/services/federation-store.d.ts +76 -0
- package/dist/services/federation-store.d.ts.map +1 -0
- package/dist/services/federation-store.js +133 -0
- package/dist/services/federation-store.js.map +1 -0
- package/dist/services/grant-store.d.ts +12 -2
- package/dist/services/grant-store.d.ts.map +1 -1
- package/dist/services/grant-store.js +51 -4
- package/dist/services/grant-store.js.map +1 -1
- package/dist/services/group-creator.d.ts +10 -0
- package/dist/services/group-creator.d.ts.map +1 -1
- package/dist/services/group-creator.js +26 -1
- package/dist/services/group-creator.js.map +1 -1
- package/dist/services/groups-store.d.ts +30 -0
- package/dist/services/groups-store.d.ts.map +1 -1
- package/dist/services/groups-store.js +85 -12
- package/dist/services/groups-store.js.map +1 -1
- package/dist/services/hermes-transcript.d.ts +7 -0
- package/dist/services/hermes-transcript.d.ts.map +1 -0
- package/dist/services/hermes-transcript.js +117 -0
- package/dist/services/hermes-transcript.js.map +1 -0
- package/dist/services/invite-store.d.ts +28 -0
- package/dist/services/invite-store.d.ts.map +1 -0
- package/dist/services/invite-store.js +85 -0
- package/dist/services/invite-store.js.map +1 -0
- package/dist/services/pairing-store.d.ts +47 -0
- package/dist/services/pairing-store.d.ts.map +1 -0
- package/dist/services/pairing-store.js +132 -0
- package/dist/services/pairing-store.js.map +1 -0
- package/dist/services/project-scanner.d.ts +10 -0
- package/dist/services/project-scanner.d.ts.map +1 -1
- package/dist/services/project-scanner.js +11 -0
- package/dist/services/project-scanner.js.map +1 -1
- package/dist/services/relay-picker.d.ts +22 -0
- package/dist/services/relay-picker.d.ts.map +1 -0
- package/dist/services/relay-picker.js +62 -0
- package/dist/services/relay-picker.js.map +1 -0
- package/dist/services/send-policy.d.ts +55 -0
- package/dist/services/send-policy.d.ts.map +1 -0
- package/dist/services/send-policy.js +47 -0
- package/dist/services/send-policy.js.map +1 -0
- package/dist/services/session-store.js +1 -1
- package/dist/services/session-store.js.map +1 -1
- package/dist/services/team-roster.d.ts +38 -0
- package/dist/services/team-roster.d.ts.map +1 -0
- package/dist/services/team-roster.js +82 -0
- package/dist/services/team-roster.js.map +1 -0
- package/dist/services/team-store.d.ts +54 -0
- package/dist/services/team-store.d.ts.map +1 -0
- package/dist/services/team-store.js +156 -0
- package/dist/services/team-store.js.map +1 -0
- package/dist/services/trigger-log-store.d.ts +46 -0
- package/dist/services/trigger-log-store.d.ts.map +1 -0
- package/dist/services/trigger-log-store.js +132 -0
- package/dist/services/trigger-log-store.js.map +1 -0
- package/dist/services/trigger-types.d.ts +57 -0
- package/dist/services/trigger-types.d.ts.map +1 -0
- package/dist/services/trigger-types.js +28 -0
- package/dist/services/trigger-types.js.map +1 -0
- package/dist/services/webhook-key.d.ts +16 -0
- package/dist/services/webhook-key.d.ts.map +1 -0
- package/dist/services/webhook-key.js +123 -0
- package/dist/services/webhook-key.js.map +1 -0
- package/dist/services/webhook-lifecycle-extractors.d.ts +15 -0
- package/dist/services/webhook-lifecycle-extractors.d.ts.map +1 -0
- package/dist/services/webhook-lifecycle-extractors.js +59 -0
- package/dist/services/webhook-lifecycle-extractors.js.map +1 -0
- package/dist/services/webhook-lifecycle-store.d.ts +45 -0
- package/dist/services/webhook-lifecycle-store.d.ts.map +1 -0
- package/dist/services/webhook-lifecycle-store.js +159 -0
- package/dist/services/webhook-lifecycle-store.js.map +1 -0
- package/dist/setup/bot-config-editor.d.ts +8 -1
- package/dist/setup/bot-config-editor.d.ts.map +1 -1
- package/dist/setup/bot-config-editor.js +20 -2
- package/dist/setup/bot-config-editor.js.map +1 -1
- package/dist/setup/ensure-tmux.d.ts +0 -22
- package/dist/setup/ensure-tmux.d.ts.map +1 -1
- package/dist/setup/ensure-tmux.js +25 -1
- package/dist/setup/ensure-tmux.js.map +1 -1
- package/dist/setup/verify-permissions.d.ts.map +1 -1
- package/dist/setup/verify-permissions.js +15 -1
- package/dist/setup/verify-permissions.js.map +1 -1
- package/dist/skills/definitions.d.ts +2 -0
- package/dist/skills/definitions.d.ts.map +1 -1
- package/dist/skills/definitions.js +178 -12
- package/dist/skills/definitions.js.map +1 -1
- package/dist/skills/installer.d.ts +34 -0
- package/dist/skills/installer.d.ts.map +1 -1
- package/dist/skills/installer.js +119 -2
- package/dist/skills/installer.js.map +1 -1
- package/dist/types.d.ts +29 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/bot-routing.d.ts +50 -0
- package/dist/utils/bot-routing.d.ts.map +1 -1
- package/dist/utils/bot-routing.js +83 -0
- package/dist/utils/bot-routing.js.map +1 -1
- package/dist/utils/daemon-discovery.d.ts +11 -0
- package/dist/utils/daemon-discovery.d.ts.map +1 -0
- package/dist/utils/daemon-discovery.js +59 -0
- package/dist/utils/daemon-discovery.js.map +1 -0
- package/dist/utils/user-token.d.ts.map +1 -1
- package/dist/utils/user-token.js +0 -2
- package/dist/utils/user-token.js.map +1 -1
- package/dist/worker.js +233 -51
- package/dist/worker.js.map +1 -1
- package/dist/workflows/attempt-resume.d.ts.map +1 -1
- package/dist/workflows/attempt-resume.js +2 -2
- package/dist/workflows/attempt-resume.js.map +1 -1
- package/dist/workflows/definition.d.ts +412 -9
- package/dist/workflows/definition.d.ts.map +1 -1
- package/dist/workflows/definition.js +238 -3
- package/dist/workflows/definition.js.map +1 -1
- package/dist/workflows/events/payloads.d.ts +114 -11
- package/dist/workflows/events/payloads.d.ts.map +1 -1
- package/dist/workflows/events/payloads.js +46 -0
- package/dist/workflows/events/payloads.js.map +1 -1
- package/dist/workflows/events/replay.d.ts +21 -0
- package/dist/workflows/events/replay.d.ts.map +1 -1
- package/dist/workflows/events/replay.js +103 -0
- package/dist/workflows/events/replay.js.map +1 -1
- package/dist/workflows/events/schema.d.ts +1301 -606
- package/dist/workflows/events/schema.d.ts.map +1 -1
- package/dist/workflows/events/schema.js +37 -1
- package/dist/workflows/events/schema.js.map +1 -1
- package/dist/workflows/events/types.d.ts +5 -1
- package/dist/workflows/events/types.d.ts.map +1 -1
- package/dist/workflows/loader.d.ts +14 -0
- package/dist/workflows/loader.d.ts.map +1 -1
- package/dist/workflows/loader.js +27 -0
- package/dist/workflows/loader.js.map +1 -1
- package/dist/workflows/loop.js +58 -0
- package/dist/workflows/loop.js.map +1 -1
- package/dist/workflows/ops-projection.d.ts +58 -0
- package/dist/workflows/ops-projection.d.ts.map +1 -1
- package/dist/workflows/ops-projection.js +74 -0
- package/dist/workflows/ops-projection.js.map +1 -1
- package/dist/workflows/orchestrator.d.ts +65 -1
- package/dist/workflows/orchestrator.d.ts.map +1 -1
- package/dist/workflows/orchestrator.js +486 -74
- package/dist/workflows/orchestrator.js.map +1 -1
- package/dist/workflows/output-binding.d.ts +8 -1
- package/dist/workflows/output-binding.d.ts.map +1 -1
- package/dist/workflows/output-binding.js +75 -11
- package/dist/workflows/output-binding.js.map +1 -1
- package/dist/workflows/runtime.d.ts +1 -1
- package/dist/workflows/runtime.d.ts.map +1 -1
- package/dist/workflows/runtime.js +39 -4
- package/dist/workflows/runtime.js.map +1 -1
- package/dist/workflows/trigger-from-envelope.d.ts +13 -0
- package/dist/workflows/trigger-from-envelope.d.ts.map +1 -0
- package/dist/workflows/trigger-from-envelope.js +67 -0
- package/dist/workflows/trigger-from-envelope.js.map +1 -0
- package/dist/workflows/wait.d.ts +23 -2
- package/dist/workflows/wait.d.ts.map +1 -1
- package/dist/workflows/wait.js +39 -17
- package/dist/workflows/wait.js.map +1 -1
- package/package.json +1 -1
- package/dist/services/feishu-task-client.d.ts +0 -28
- package/dist/services/feishu-task-client.d.ts.map +0 -1
- package/dist/services/feishu-task-client.js +0 -123
- package/dist/services/feishu-task-client.js.map +0 -1
- package/dist/services/task-store.d.ts +0 -37
- package/dist/services/task-store.d.ts.map +0 -1
- package/dist/services/task-store.js +0 -115
- package/dist/services/task-store.js.map +0 -1
package/dist/core/worker-pool.js
CHANGED
|
@@ -7,13 +7,15 @@ import { join, dirname } from 'node:path';
|
|
|
7
7
|
import { homedir } from 'node:os';
|
|
8
8
|
import { readFileSync, writeFileSync, mkdirSync, existsSync, realpathSync } from 'node:fs';
|
|
9
9
|
import { fileURLToPath } from 'node:url';
|
|
10
|
-
import { ensureSkills } from '../skills/installer.js';
|
|
10
|
+
import { ensureSkills, ensureAskSkill, ensurePluginSkills, removeGlobalBotmuxSkills } from '../skills/installer.js';
|
|
11
|
+
import { installHook } from '../adapters/hook-installer.js';
|
|
12
|
+
import { hookCommandFor } from '../adapters/hook-command.js';
|
|
11
13
|
import { randomBytes } from 'node:crypto';
|
|
12
14
|
import { config } from '../config.js';
|
|
13
15
|
import * as sessionStore from '../services/session-store.js';
|
|
14
16
|
import { persistStreamCardState } from './session-manager.js';
|
|
15
|
-
import { updateMessage, deleteMessage, MessageWithdrawnError } from '../im/lark/client.js';
|
|
16
|
-
import { buildStreamingCard, buildSessionCard, buildTuiPromptCard, buildTuiPromptResolvedCard, getCliDisplayName } from '../im/lark/card-builder.js';
|
|
17
|
+
import { updateMessage, deleteMessage, sendEphemeralCard, MessageWithdrawnError } from '../im/lark/client.js';
|
|
18
|
+
import { buildStreamingCard, buildPrivateSnapshotCard, buildSessionCard, buildTuiPromptCard, buildTuiPromptResolvedCard, buildRelayedFrozenCard, getCliDisplayName } from '../im/lark/card-builder.js';
|
|
17
19
|
import { loadFrozenCards, saveFrozenCards } from '../services/frozen-card-store.js';
|
|
18
20
|
import { logger } from '../utils/logger.js';
|
|
19
21
|
import { createCliAdapterSync } from '../adapters/cli/registry.js';
|
|
@@ -21,9 +23,10 @@ import { botLocale, localeForBot, t as tr } from '../i18n/index.js';
|
|
|
21
23
|
import { claudeJsonlPathForSession } from '../adapters/cli/claude-code.js';
|
|
22
24
|
import { buildMarkdownCard, buildContextualReplyCard } from '../im/lark/md-card.js';
|
|
23
25
|
import { TmuxBackend } from '../adapters/backend/tmux-backend.js';
|
|
24
|
-
import { getBot, getAllBots } from '../bot-registry.js';
|
|
26
|
+
import { getBot, getAllBots, resolveBrandLabel } from '../bot-registry.js';
|
|
25
27
|
import { dashboardEventBus } from './dashboard-events.js';
|
|
26
28
|
import { composeRowFromActive } from './dashboard-rows.js';
|
|
29
|
+
import { knownBotOpenIdsFromCrossRef } from '../utils/bot-routing.js';
|
|
27
30
|
import { sessionKey, sessionAnchorId } from './types.js';
|
|
28
31
|
import { usageLimitStateKey } from '../utils/cli-usage-limit.js';
|
|
29
32
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -71,6 +74,84 @@ export function findActiveBySessionId(sessionId) {
|
|
|
71
74
|
export function getActiveSessionsRegistry() {
|
|
72
75
|
return activeSessionsRegistry;
|
|
73
76
|
}
|
|
77
|
+
// ─── "Real relayable session" predicate ─────────────────────────────────────
|
|
78
|
+
/**
|
|
79
|
+
* True iff this DaemonSession represents a real CLI-backed conversation
|
|
80
|
+
* that's safe to migrate via /relay. Returns false for daemon-command
|
|
81
|
+
* scratch placeholders (the `worker:null + hasHistory:false` records that
|
|
82
|
+
* daemon.ts creates for /help, an unfinished picker /relay, etc.) — those
|
|
83
|
+
* have no CLI history, no tmux, and migrating them yields an empty shell
|
|
84
|
+
* in the target chat with a fake "已就绪" M1.
|
|
85
|
+
*
|
|
86
|
+
* Why not just `!!ds.worker || ds.hasHistory`:
|
|
87
|
+
* - `ds.worker` is runtime-only; null after daemon restart until
|
|
88
|
+
* forkWorker re-attaches.
|
|
89
|
+
* - `ds.hasHistory` is a runtime field too — restoreActiveSessions sets
|
|
90
|
+
* it `true` UNCONDITIONALLY for any persisted non-adopt session
|
|
91
|
+
* (session-manager.ts:618). A scratch that survived a restart comes
|
|
92
|
+
* back with hasHistory:true, defeating the guard.
|
|
93
|
+
*
|
|
94
|
+
* Use persisted markers instead: `ds.session.cliId` and
|
|
95
|
+
* `ds.session.lastCliInput` are written ONLY after a real worker started
|
|
96
|
+
* the CLI (worker-pool's fork path stamps cliId; rememberLastCliInput
|
|
97
|
+
* writes lastCliInput on every input). Daemon-command scratches never set
|
|
98
|
+
* either, so the predicate survives restart and is robust across paths.
|
|
99
|
+
*
|
|
100
|
+
* Apply at every relay surface that consumes a candidate `ds`:
|
|
101
|
+
* - relay-picker.ts collectRelayPickerEntries (don't list scratches)
|
|
102
|
+
* - card-handler.ts relay_confirm preflight (don't M1 + transferSession a scratch)
|
|
103
|
+
* - this file's transferSession depth defense (catch any caller that bypassed both upstream guards)
|
|
104
|
+
* - command-handler.ts /relay --create leader guard
|
|
105
|
+
*/
|
|
106
|
+
export function isRelayableRealSession(ds) {
|
|
107
|
+
if (ds.worker)
|
|
108
|
+
return true;
|
|
109
|
+
if (ds.session.cliId)
|
|
110
|
+
return true;
|
|
111
|
+
if (ds.session.lastCliInput)
|
|
112
|
+
return true;
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
// ─── Terminal URL helpers ──────────────────────────────────────────────────
|
|
116
|
+
// config.web.externalHost is a live getter (re-resolves the LAN IP each read
|
|
117
|
+
// when WEB_EXTERNAL_HOST is unset), so building the URL fresh at every card
|
|
118
|
+
// render/patch is enough to keep links pointing at the current network.
|
|
119
|
+
function terminalReadUrl(port) {
|
|
120
|
+
return `http://${config.web.externalHost}:${port}`;
|
|
121
|
+
}
|
|
122
|
+
function terminalWriteUrl(port, token) {
|
|
123
|
+
return `${terminalReadUrl(port)}?token=${encodeURIComponent(token)}`;
|
|
124
|
+
}
|
|
125
|
+
// Per-bot opt-out: when true, botmux never posts/patches the live streaming
|
|
126
|
+
// session card. Read fresh from the in-memory registry so a dashboard toggle
|
|
127
|
+
// takes effect without a daemon restart. The `/card` command can override it
|
|
128
|
+
// per-session via `ds.streamingCardForced` (manually summon a live card).
|
|
129
|
+
function streamingCardDisabled(ds) {
|
|
130
|
+
if (ds.streamingCardForced)
|
|
131
|
+
return false;
|
|
132
|
+
try {
|
|
133
|
+
return getBot(ds.larkAppId).config.disableStreamingCard === true;
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Per-bot opt-in: the writable terminal link to embed directly in the streaming
|
|
140
|
+
// card body (token included). Returns undefined unless the bot enabled it AND
|
|
141
|
+
// the worker port/token are known. Exported for card-handler's re-renders so the
|
|
142
|
+
// link stays put across button-driven card updates.
|
|
143
|
+
export function writableTerminalLinkFor(ds) {
|
|
144
|
+
try {
|
|
145
|
+
if (getBot(ds.larkAppId).config.writableTerminalLinkInCard !== true)
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
if (!ds.workerPort || !ds.workerToken)
|
|
152
|
+
return undefined;
|
|
153
|
+
return terminalWriteUrl(ds.workerPort, ds.workerToken);
|
|
154
|
+
}
|
|
74
155
|
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
75
156
|
function tag(ds) {
|
|
76
157
|
return ds.session.sessionId.substring(0, 8);
|
|
@@ -78,6 +159,36 @@ function tag(ds) {
|
|
|
78
159
|
function sessionCliId(ds, botCfg) {
|
|
79
160
|
return ds.session.cliId ?? botCfg.cliId;
|
|
80
161
|
}
|
|
162
|
+
function loadKnownBotOpenIdsForApp(larkAppId) {
|
|
163
|
+
const dataDir = config.session.dataDir;
|
|
164
|
+
let crossRef = {};
|
|
165
|
+
const crossRefPath = join(dataDir, `bot-openids-${larkAppId}.json`);
|
|
166
|
+
if (existsSync(crossRefPath)) {
|
|
167
|
+
const parsed = JSON.parse(readFileSync(crossRefPath, 'utf-8'));
|
|
168
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
169
|
+
crossRef = parsed;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
let botEntries = [];
|
|
173
|
+
const botInfoPath = join(dataDir, 'bots-info.json');
|
|
174
|
+
if (existsSync(botInfoPath)) {
|
|
175
|
+
const parsed = JSON.parse(readFileSync(botInfoPath, 'utf-8'));
|
|
176
|
+
if (Array.isArray(parsed))
|
|
177
|
+
botEntries = parsed;
|
|
178
|
+
}
|
|
179
|
+
return knownBotOpenIdsFromCrossRef(crossRef, botEntries, larkAppId);
|
|
180
|
+
}
|
|
181
|
+
function daemonCardFooterRecipientOpenId(ds) {
|
|
182
|
+
const owner = ds.session.ownerOpenId;
|
|
183
|
+
if (!owner)
|
|
184
|
+
return undefined;
|
|
185
|
+
try {
|
|
186
|
+
return loadKnownBotOpenIdsForApp(ds.larkAppId).has(owner) ? undefined : owner;
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return owner;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
81
192
|
export function clearUsageLimitState(ds) {
|
|
82
193
|
if (ds.usageLimitRetryTimer) {
|
|
83
194
|
clearTimeout(ds.usageLimitRetryTimer);
|
|
@@ -97,9 +208,9 @@ function scheduleUsageLimitCardPatch(ds) {
|
|
|
97
208
|
return;
|
|
98
209
|
const bot = getBot(ds.larkAppId);
|
|
99
210
|
const effectiveCliId = sessionCliId(ds, bot.config);
|
|
100
|
-
const readUrl =
|
|
211
|
+
const readUrl = terminalReadUrl(port);
|
|
101
212
|
const turnTitle = ds.currentTurnTitle || ds.session.title || getCliDisplayName(effectiveCliId);
|
|
102
|
-
const cardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readUrl, turnTitle, ds.lastScreenContent ?? '', 'limited', effectiveCliId, ds.displayMode ?? 'hidden', ds.streamCardNonce, ds.currentImageKey, !!ds.adoptedFrom, false, localeForBot(ds.larkAppId), ds.usageLimit);
|
|
213
|
+
const cardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readUrl, turnTitle, ds.lastScreenContent ?? '', 'limited', effectiveCliId, ds.displayMode ?? 'hidden', ds.streamCardNonce, ds.currentImageKey, !!ds.adoptedFrom, false, localeForBot(ds.larkAppId), ds.usageLimit, writableTerminalLinkFor(ds));
|
|
103
214
|
scheduleCardPatch(ds, cardJson);
|
|
104
215
|
}
|
|
105
216
|
function armUsageLimitRetryTimer(ds, previous) {
|
|
@@ -232,6 +343,110 @@ export function recallFrozenCards(ds) {
|
|
|
232
343
|
}
|
|
233
344
|
logger.info(`[${tag(ds)}] Recalled ${targets.length} previous streaming card(s)`);
|
|
234
345
|
}
|
|
346
|
+
/**
|
|
347
|
+
* Force-post a fresh streaming card for `ds`, bypassing the per-bot
|
|
348
|
+
* `disableStreamingCard` opt-out. Backs the `/card` command: a user can
|
|
349
|
+
* manually summon a live card in an otherwise-quiet session. Parks the current
|
|
350
|
+
* card (if any) first so `recallFrozenCards` withdraws it once the fresh one
|
|
351
|
+
* lands — the thread ends up with a single live card. Returns false when the
|
|
352
|
+
* worker terminal isn't ready yet (no port), so the caller can surface a
|
|
353
|
+
* friendly "not ready" message.
|
|
354
|
+
*
|
|
355
|
+
* Note: this does NOT itself flip `ds.streamingCardForced` — the caller sets
|
|
356
|
+
* that so the card keeps live-patching afterwards even when the bot opted out.
|
|
357
|
+
*/
|
|
358
|
+
export async function postFreshStreamingCard(ds, sessionReply) {
|
|
359
|
+
const port = ds.workerPort ?? ds.session.webPort;
|
|
360
|
+
if (!port)
|
|
361
|
+
return false;
|
|
362
|
+
const botCfg = getBot(ds.larkAppId).config;
|
|
363
|
+
const effectiveCliId = sessionCliId(ds, botCfg);
|
|
364
|
+
const readUrl = terminalReadUrl(port);
|
|
365
|
+
const title = ds.currentTurnTitle || ds.session.title || getCliDisplayName(effectiveCliId);
|
|
366
|
+
const status = ds.lastScreenStatus ?? 'idle';
|
|
367
|
+
// Park the current card (no-op when there's none) so the fresh one replaces
|
|
368
|
+
// rather than duplicates it.
|
|
369
|
+
parkStreamCard(ds);
|
|
370
|
+
// Snapshot prior identity for rollback on POST failure (restore all three
|
|
371
|
+
// together so a failed /card leaves no orphaned nonce/pending state).
|
|
372
|
+
const prevCardId = ds.streamCardId;
|
|
373
|
+
const prevNonce = ds.streamCardNonce;
|
|
374
|
+
const prevPending = ds.streamCardPending;
|
|
375
|
+
ds.streamCardNonce = randomBytes(4).toString('hex');
|
|
376
|
+
const cardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readUrl, title, ds.lastScreenContent ?? '', status, effectiveCliId, ds.displayMode ?? 'hidden', ds.streamCardNonce, ds.currentImageKey, !!ds.adoptedFrom, false, localeForBot(ds.larkAppId), cardUsageLimit(ds), writableTerminalLinkFor(ds));
|
|
377
|
+
ds.streamCardId = CARD_POSTING_SENTINEL;
|
|
378
|
+
try {
|
|
379
|
+
ds.streamCardId = await sessionReply(sessionAnchorId(ds), cardJson, 'interactive', ds.larkAppId);
|
|
380
|
+
// This card is now the live one for the current turn. Clear the new-turn
|
|
381
|
+
// pending flag so the next screen_update PATCHes it instead of POSTing a
|
|
382
|
+
// duplicate (the gate above only suppresses cards when disabled+unforced;
|
|
383
|
+
// /card forces them on, so a stale pending flag would otherwise re-POST).
|
|
384
|
+
ds.streamCardPending = false;
|
|
385
|
+
persistStreamCardState(ds);
|
|
386
|
+
recallFrozenCards(ds);
|
|
387
|
+
logger.info(`[${tag(ds)}] Posted streaming card via /card`);
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
catch (err) {
|
|
391
|
+
ds.streamCardId = prevCardId;
|
|
392
|
+
ds.streamCardNonce = prevNonce;
|
|
393
|
+
ds.streamCardPending = prevPending;
|
|
394
|
+
logger.warn(`[${tag(ds)}] /card POST failed: ${err}`);
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Audience for a private `/card`: the bot's `allowedUsers` (the canOperate set —
|
|
400
|
+
* owner & co-owners), deduped, `ou_` only. Talk-only grants (`globalGrants` /
|
|
401
|
+
* `chatGrants`) and a bare triggerer are intentionally NOT included: the private
|
|
402
|
+
* card is owner-only. A grant-authorized user who runs `/card` therefore does
|
|
403
|
+
* not receive a card (matches the "授权人不发" rule). Empty when the bot has no
|
|
404
|
+
* `allowedUsers` (fully-open mode → no owner to send to).
|
|
405
|
+
*/
|
|
406
|
+
export function resolvePrivateCardAudience(ds) {
|
|
407
|
+
const bot = getBot(ds.larkAppId);
|
|
408
|
+
const set = new Set();
|
|
409
|
+
for (const u of bot.resolvedAllowedUsers)
|
|
410
|
+
if (u.startsWith('ou_'))
|
|
411
|
+
set.add(u);
|
|
412
|
+
return [...set];
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Private `/card`: build a one-shot snapshot of the current terminal and send it
|
|
416
|
+
* as an ephemeral (visible-to-one) card to each open_id in `audience`, one API
|
|
417
|
+
* call each (concurrency-capped). Never posts a group-visible card and never
|
|
418
|
+
* patches — privacy is the whole point, so there is deliberately no fallback.
|
|
419
|
+
* Returns per-recipient counts so the caller can report progress without leaking
|
|
420
|
+
* the audience list into the chat.
|
|
421
|
+
*/
|
|
422
|
+
export async function postPrivateSnapshotCard(ds, audience) {
|
|
423
|
+
const port = ds.workerPort ?? ds.session.webPort;
|
|
424
|
+
if (!port)
|
|
425
|
+
return { sent: 0, total: audience.length, notReady: true };
|
|
426
|
+
const botCfg = getBot(ds.larkAppId).config;
|
|
427
|
+
const effectiveCliId = sessionCliId(ds, botCfg);
|
|
428
|
+
const readUrl = terminalReadUrl(port);
|
|
429
|
+
const title = ds.currentTurnTitle || ds.session.title || getCliDisplayName(effectiveCliId);
|
|
430
|
+
const status = ds.lastScreenStatus ?? 'idle';
|
|
431
|
+
const cardJson = buildPrivateSnapshotCard(readUrl, title, status, effectiveCliId, ds.currentImageKey, ds.lastScreenContent ?? '', ds.session.sessionId, sessionAnchorId(ds), localeForBot(ds.larkAppId), cardUsageLimit(ds));
|
|
432
|
+
let sent = 0;
|
|
433
|
+
// Cap concurrency: Feishu per-chat ~40 QPS, ephemeral total 50/s.
|
|
434
|
+
const CONCURRENCY = 5;
|
|
435
|
+
for (let i = 0; i < audience.length; i += CONCURRENCY) {
|
|
436
|
+
const batch = audience.slice(i, i + CONCURRENCY);
|
|
437
|
+
await Promise.all(batch.map(async (openId) => {
|
|
438
|
+
try {
|
|
439
|
+
await sendEphemeralCard(ds.larkAppId, ds.chatId, openId, cardJson);
|
|
440
|
+
sent++;
|
|
441
|
+
}
|
|
442
|
+
catch (err) {
|
|
443
|
+
logger.warn(`[${tag(ds)}] private /card ephemeral send to ${openId.substring(0, 8)}… failed: ${err}`);
|
|
444
|
+
}
|
|
445
|
+
}));
|
|
446
|
+
}
|
|
447
|
+
logger.info(`[${tag(ds)}] private /card: ephemeral sent ${sent}/${audience.length}`);
|
|
448
|
+
return { sent, total: audience.length, notReady: false };
|
|
449
|
+
}
|
|
235
450
|
// ─── Card PATCH serialization queue ─────────────────────────────────────────
|
|
236
451
|
// Only one PATCH in-flight at a time per session. New PATCHes queue on
|
|
237
452
|
// ds.pendingCardJson (latest wins). When the in-flight PATCH completes,
|
|
@@ -244,6 +459,9 @@ export function recallFrozenCards(ds) {
|
|
|
244
459
|
* any previously queued value — only the latest state matters).
|
|
245
460
|
*/
|
|
246
461
|
export function scheduleCardPatch(ds, cardJson) {
|
|
462
|
+
// Bot opted out of the streaming card — never patch one into existence.
|
|
463
|
+
if (streamingCardDisabled(ds))
|
|
464
|
+
return;
|
|
247
465
|
ds.pendingCardJson = cardJson;
|
|
248
466
|
// Capture the card ID now — by the time flushCardPatch runs, ds.streamCardId
|
|
249
467
|
// may have been overwritten by a new turn's card (CARD_POSTING_SENTINEL).
|
|
@@ -305,7 +523,32 @@ export function ensureCliSkills(cliId, cliPathOverride) {
|
|
|
305
523
|
if (skillsInstalledCliIds.has(cliId))
|
|
306
524
|
return;
|
|
307
525
|
const adapter = createCliAdapterSync(cliId, cliPathOverride);
|
|
308
|
-
|
|
526
|
+
if (adapter.pluginDir) {
|
|
527
|
+
// 动态注入:skill 写进插件目录,spawn 时用 --plugin-dir 注入,仅本次会话可见。
|
|
528
|
+
// 不再写全局 skillsDir。(全局 ~/.claude/skills 的历史残留清理改由
|
|
529
|
+
// cleanupGlobalBotmuxSkillsOnce 在启动时独立于 cliId 执行。)
|
|
530
|
+
ensurePluginSkills(cliId, adapter.pluginDir);
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
ensureSkills(cliId, adapter.skillsDir);
|
|
534
|
+
}
|
|
535
|
+
// askUserQuestion 接管策略:hook 优先 + 非 hook CLI 用 skill 兜底。
|
|
536
|
+
// - asksViaHook=true(Claude/OpenCode):通过 hook 拦截原生 AskUserQuestion,删掉
|
|
537
|
+
// botmux-ask skill,避免 skill 与 hook 双重弹卡。
|
|
538
|
+
// Claude 走 --settings 进程级注入;OpenCode 走 hookInstall 插件写文件。
|
|
539
|
+
// - asksViaHook 未设(Codex/Cursor/尚未接 hook 的终端原生 CLI):保留 botmux-ask
|
|
540
|
+
// skill 作兜底,让 agent 仍可用 `botmux ask` 把选择题引到飞书。
|
|
541
|
+
if (adapter.hookInstall) {
|
|
542
|
+
try {
|
|
543
|
+
installHook(cliId, adapter.hookInstall, hookCommandFor(cliId));
|
|
544
|
+
}
|
|
545
|
+
catch (err) {
|
|
546
|
+
logger.warn(`[hook] install failed for ${cliId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
// botmux-ask 落在与其它 skill 同一目录:plugin 模式下是 {pluginDir}/skills。
|
|
550
|
+
const askSkillsDir = adapter.pluginDir ? join(adapter.pluginDir, 'skills') : adapter.skillsDir;
|
|
551
|
+
ensureAskSkill(cliId, askSkillsDir, !adapter.asksViaHook);
|
|
309
552
|
skillsInstalledCliIds.add(cliId);
|
|
310
553
|
}
|
|
311
554
|
// ─── Legacy MCP config cleanup ──────────────────────────────────────────────
|
|
@@ -381,9 +624,11 @@ export function cleanupLegacyMcpConfig(cliId) {
|
|
|
381
624
|
}
|
|
382
625
|
break;
|
|
383
626
|
}
|
|
384
|
-
case 'opencode':
|
|
385
|
-
|
|
386
|
-
|
|
627
|
+
case 'opencode':
|
|
628
|
+
case 'mtr': {
|
|
629
|
+
// ~/.config/opencode/{opencode,mtr}.json → { mcp: { botmux } } or { mcpServers: { botmux } }
|
|
630
|
+
const file = cliId === 'mtr' ? 'mtr.json' : 'opencode.json';
|
|
631
|
+
const p = join(home, '.config', 'opencode', file);
|
|
387
632
|
const removed = removeJsonKey(p, ['mcp'], 'botmux') ||
|
|
388
633
|
removeJsonKey(p, ['mcpServers'], 'botmux') ||
|
|
389
634
|
removeJsonKey(p, ['mcp', 'servers'], 'botmux');
|
|
@@ -413,9 +658,23 @@ export function cleanupLegacyMcpConfig(cliId) {
|
|
|
413
658
|
* Both steps are idempotent and best-effort.
|
|
414
659
|
*/
|
|
415
660
|
export function ensureCliEnv(cliId, cliPathOverride) {
|
|
661
|
+
cleanupGlobalBotmuxSkillsOnce();
|
|
416
662
|
ensureCliSkills(cliId, cliPathOverride);
|
|
417
663
|
cleanupLegacyMcpConfig(cliId);
|
|
418
664
|
}
|
|
665
|
+
let globalBotmuxSkillsCleaned = false;
|
|
666
|
+
/** One-time, CLI-independent cleanup of botmux skills that older versions
|
|
667
|
+
* installed into the global `~/.claude/skills`. Claude now injects skills via
|
|
668
|
+
* `--plugin-dir`, so any leftover `botmux-*` there leaks into the user's
|
|
669
|
+
* standalone `claude` regardless of which CLI THIS daemon's bot uses — so the
|
|
670
|
+
* cleanup must NOT be gated on `adapter.pluginDir` (which only fires for a
|
|
671
|
+
* Claude bot). Runs at daemon startup via ensureCliEnv. */
|
|
672
|
+
function cleanupGlobalBotmuxSkillsOnce() {
|
|
673
|
+
if (globalBotmuxSkillsCleaned)
|
|
674
|
+
return;
|
|
675
|
+
globalBotmuxSkillsCleaned = true;
|
|
676
|
+
removeGlobalBotmuxSkills('~/.claude/skills');
|
|
677
|
+
}
|
|
419
678
|
// ─── Claude Code folder-trust pre-acceptance ─────────────────────────────────
|
|
420
679
|
//
|
|
421
680
|
// A freshly spawned `claude` in a workingDir that has never been trusted blocks
|
|
@@ -549,6 +808,234 @@ export async function closeSession(sessionId) {
|
|
|
549
808
|
const alreadyClosed = !killedLive && !wasOpen;
|
|
550
809
|
return { ok: true, alreadyClosed };
|
|
551
810
|
}
|
|
811
|
+
/**
|
|
812
|
+
* Set an entry on an active-sessions Map, but if the key is already occupied
|
|
813
|
+
* by a DIFFERENT DaemonSession, close that occupant first. Replaces bare
|
|
814
|
+
* `activeSessions.set(key, ds)` at sites where a silent overwrite would leak
|
|
815
|
+
* the prior entry's worker + leave its store row stuck in `status='active'`.
|
|
816
|
+
*
|
|
817
|
+
* The Map is passed explicitly so callers operate on the same instance they
|
|
818
|
+
* already hold (restoreActiveSessions takes the daemon's Map as a parameter;
|
|
819
|
+
* transferSession reaches it through `activeSessionsRegistry`). In production
|
|
820
|
+
* both refer to the same object — the daemon registers its Map at boot — but
|
|
821
|
+
* decoupling avoids module-state assumptions in tests.
|
|
822
|
+
*
|
|
823
|
+
* Canonical collision case: restoreActiveSessions at daemon boot iterating
|
|
824
|
+
* two on-disk active sessions that resolve to the same chat-scope key (e.g.
|
|
825
|
+
* a /relay command's scratch session + the real session that was transferred
|
|
826
|
+
* into the same chat by a prior daemon run). Without this helper the later
|
|
827
|
+
* iterated entry silently wins, the earlier one becomes a ghost-active.
|
|
828
|
+
*
|
|
829
|
+
* Setting the same `ds` at its own key is a no-op (no close).
|
|
830
|
+
*/
|
|
831
|
+
export async function setActiveSessionSafe(map, key, ds) {
|
|
832
|
+
const prev = map.get(key);
|
|
833
|
+
if (prev && prev !== ds) {
|
|
834
|
+
logger.warn(`[setActiveSessionSafe] key already occupied by ${prev.session.sessionId.substring(0, 8)} ` +
|
|
835
|
+
`(worker=${prev.worker ? 'live' : 'null'}); closing it before set`);
|
|
836
|
+
await closeSession(prev.session.sessionId);
|
|
837
|
+
}
|
|
838
|
+
map.set(key, ds);
|
|
839
|
+
}
|
|
840
|
+
// ─── Session transfer (cross-chat relay) ────────────────────────────────────
|
|
841
|
+
/**
|
|
842
|
+
* Transfer an active session from its current chat to a new chat. The CLI
|
|
843
|
+
* process keeps running inside its tmux session — only the routing fields
|
|
844
|
+
* (chatId, rootMessageId, scope) and activeSessions key are rewritten. After
|
|
845
|
+
* the rewrite, forkWorker spawns a new worker that re-attaches to the same
|
|
846
|
+
* `bmx-<sessionId>` tmux, so the AI's transcript continues without break.
|
|
847
|
+
*
|
|
848
|
+
* Visible side effects:
|
|
849
|
+
* - Lark messages in the *source* chat remain where they were — we have no
|
|
850
|
+
* API to move them. Only the worker's *routing* moves; the AI's memory
|
|
851
|
+
* follows via the CLI's persistent jsonl on disk.
|
|
852
|
+
* - Cards posted by the prior worker stay in the source chat. We clear
|
|
853
|
+
* streamCardId/Nonce/imageKey so the new worker posts fresh cards in the
|
|
854
|
+
* target chat instead of trying to PATCH unreachable old ones.
|
|
855
|
+
*
|
|
856
|
+
* Pre-conditions (entry guards, all checked synchronously up-front — no
|
|
857
|
+
* idle-wait loop; busy workers are refused immediately so the caller can
|
|
858
|
+
* report a deterministic outcome and the user retries when the worker
|
|
859
|
+
* quiets):
|
|
860
|
+
* - Session must be currently active (live worker + activeSessions entry)
|
|
861
|
+
* - Source must not be a pendingRepo placeholder (no CLI ever started)
|
|
862
|
+
* - Source must not be an adopted external-tmux session
|
|
863
|
+
* - Source worker must be in idle/limited (or already dead) — otherwise
|
|
864
|
+
* refuse with `worker_busy`
|
|
865
|
+
* - Target chat must not already host a real chat-scope session for the
|
|
866
|
+
* same bot (`target_chat_has_session`). Scratch (worker:null) occupants
|
|
867
|
+
* are NOT a conflict — they're command-time placeholders and we close
|
|
868
|
+
* them in-line to free the slot before continuing.
|
|
869
|
+
*
|
|
870
|
+
* Idempotent for `same_chat`: returns error without side effects when the
|
|
871
|
+
* source chat equals the target chat.
|
|
872
|
+
*/
|
|
873
|
+
export async function transferSession(sessionId, targetChatId, targetRootMessageId,
|
|
874
|
+
/**
|
|
875
|
+
* Target chat type — narrowed to `'group'` at the type level. The picker-
|
|
876
|
+
* mode entry guard in command-handler.ts refuses p2p and topic chats
|
|
877
|
+
* upfront; `/relay --create` builds the target by createGroupWithBots so
|
|
878
|
+
* it's a regular group by construction; the cross-daemon migrate-to-chat
|
|
879
|
+
* IPC inherits the same target. Every call site can vouch — TS prevents
|
|
880
|
+
* any non-'group' literal from reaching here, and the runtime check just
|
|
881
|
+
* below catches mock data / future bypasses.
|
|
882
|
+
*/
|
|
883
|
+
targetChatType, opts) {
|
|
884
|
+
// Depth defense — unreachable per TS narrowing above, but guards against
|
|
885
|
+
// raw-string casting at module boundaries (mocks, HTTP body parses, etc.).
|
|
886
|
+
if (targetChatType !== 'group') {
|
|
887
|
+
return { ok: false, error: 'target_chat_type_unsupported' };
|
|
888
|
+
}
|
|
889
|
+
const ds = findActiveBySessionId(sessionId);
|
|
890
|
+
if (!ds)
|
|
891
|
+
return { ok: false, error: 'session_not_active' };
|
|
892
|
+
if (targetChatId === ds.chatId)
|
|
893
|
+
return { ok: false, error: 'same_chat' };
|
|
894
|
+
// pendingRepo: the user created a session via M0 but hasn't picked a repo
|
|
895
|
+
// yet, so worker is null and the CLI has never run. Relaying produces an
|
|
896
|
+
// empty new-chat session with no AI memory — refuse so the user finishes
|
|
897
|
+
// setup in the original chat first.
|
|
898
|
+
if (ds.pendingRepo)
|
|
899
|
+
return { ok: false, error: 'not_started_yet' };
|
|
900
|
+
// Depth defense: daemon-command scratch (worker:null + no persisted CLI
|
|
901
|
+
// markers) must not be migrated. Upstream paths (picker filter, card-
|
|
902
|
+
// handler confirm preflight, /relay --create leader guard) should already
|
|
903
|
+
// refuse these — this catches any caller that bypassed all three (e.g.
|
|
904
|
+
// a future code path, a direct dashboard IPC, a test reaching in
|
|
905
|
+
// manually). Using `isRelayableRealSession` instead of `ds.hasHistory`
|
|
906
|
+
// makes the predicate survive restoreActiveSessions which currently sets
|
|
907
|
+
// hasHistory:true unconditionally (session-manager.ts:618).
|
|
908
|
+
if (!isRelayableRealSession(ds))
|
|
909
|
+
return { ok: false, error: 'not_started_yet' };
|
|
910
|
+
// Adopt sessions wrap a CLI process that botmux didn't spawn — the user
|
|
911
|
+
// owns it inside their own tmux pane, so moving routing here would be
|
|
912
|
+
// surprising and we don't control the tmux session's lifecycle. Refuse.
|
|
913
|
+
if (ds.session.adoptedFrom)
|
|
914
|
+
return { ok: false, error: 'adopt_not_relayable' };
|
|
915
|
+
// Busy worker: refuse immediately rather than waiting. An idle-wait loop
|
|
916
|
+
// (previously 60s) created an asymmetry with the peer-dispatch HTTP
|
|
917
|
+
// timeout (5s) — peer's transferSession was still polling while the
|
|
918
|
+
// leader had already abort+report 'busy', producing reports that
|
|
919
|
+
// disagreed with reality. Cleaner contract: refuse on first miss, let
|
|
920
|
+
// the user retry when the turn settles.
|
|
921
|
+
const st = ds.lastScreenStatus;
|
|
922
|
+
if (ds.worker && !ds.worker.killed && st !== 'idle' && st !== 'limited') {
|
|
923
|
+
return { ok: false, error: 'worker_busy' };
|
|
924
|
+
}
|
|
925
|
+
// Existing-session guard: a chat-scope session at the target chatId would
|
|
926
|
+
// collide on sessionKey(targetChatId, larkAppId) after the rewrite, and
|
|
927
|
+
// Map.set would silently orphan the prior entry's worker. We split the
|
|
928
|
+
// collision predicate two ways:
|
|
929
|
+
// - real session (worker !== null): refuse the transfer
|
|
930
|
+
// - scratch session (worker === null): a daemon-command placeholder
|
|
931
|
+
// (e.g. the /relay command itself created one when typed in this
|
|
932
|
+
// chat); the slot is logically free, but the placeholder lingers in
|
|
933
|
+
// the store with status='active'. Collect and close it so the post-
|
|
934
|
+
// transfer Map.set doesn't silently overwrite it (which leaves the
|
|
935
|
+
// scratch as a ghost-active on next daemon restart — exact bug we're
|
|
936
|
+
// fixing).
|
|
937
|
+
// We only check chat-scope entries — thread-scope sessions in the same
|
|
938
|
+
// chat are keyed by rootMessageId, so they don't collide.
|
|
939
|
+
const scratchesToClose = [];
|
|
940
|
+
if (activeSessionsRegistry) {
|
|
941
|
+
for (const existing of activeSessionsRegistry.values()) {
|
|
942
|
+
if (existing === ds)
|
|
943
|
+
continue;
|
|
944
|
+
if (existing.larkAppId !== ds.larkAppId)
|
|
945
|
+
continue;
|
|
946
|
+
if (existing.chatId !== targetChatId)
|
|
947
|
+
continue;
|
|
948
|
+
if (existing.scope !== 'chat')
|
|
949
|
+
continue;
|
|
950
|
+
if (!existing.worker) {
|
|
951
|
+
scratchesToClose.push(existing.session.sessionId);
|
|
952
|
+
continue;
|
|
953
|
+
}
|
|
954
|
+
return { ok: false, error: 'target_chat_has_session' };
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
for (const sid of scratchesToClose) {
|
|
958
|
+
await closeSession(sid);
|
|
959
|
+
}
|
|
960
|
+
const fkw = opts?.forkWorkerImpl ?? forkWorker;
|
|
961
|
+
const kw = opts?.killWorkerImpl ?? killWorker;
|
|
962
|
+
const tagPrefix = sessionId.substring(0, 8);
|
|
963
|
+
const oldAnchor = sessionAnchorId(ds);
|
|
964
|
+
const oldChatId = ds.chatId;
|
|
965
|
+
// Freeze the source-chat streaming card BEFORE we kill the worker (and
|
|
966
|
+
// before we clear streamCardId below). The live card's action buttons
|
|
967
|
+
// (close / toggle / get write link) carry `session_id` in their value, so
|
|
968
|
+
// clicks AFTER relay still reach the now-relocated session — closing it,
|
|
969
|
+
// toggling its display mode, etc. — with feedback landing on the NEW
|
|
970
|
+
// card in the target chat. PATCH the source-chat card to an inert
|
|
971
|
+
// snapshot so the user sees clearly it's historical, and so the buttons
|
|
972
|
+
// are gone. Best-effort: on PATCH failure (card withdrawn, expired) we
|
|
973
|
+
// log and continue; the relay itself must not depend on this.
|
|
974
|
+
if (ds.streamCardId && ds.streamCardId !== CARD_POSTING_SENTINEL) {
|
|
975
|
+
try {
|
|
976
|
+
const cliId = ds.session.cliId
|
|
977
|
+
?? (() => { try {
|
|
978
|
+
return getBot(ds.larkAppId).config.cliId;
|
|
979
|
+
}
|
|
980
|
+
catch {
|
|
981
|
+
return undefined;
|
|
982
|
+
} })();
|
|
983
|
+
const frozenJson = buildRelayedFrozenCard(ds.currentTurnTitle || ds.session.title || '', cliId, ds.currentImageKey, localeForBot(ds.larkAppId));
|
|
984
|
+
await updateMessage(ds.larkAppId, ds.streamCardId, frozenJson);
|
|
985
|
+
}
|
|
986
|
+
catch (err) {
|
|
987
|
+
logger.warn(`[${tagPrefix}] freeze source-chat card failed: ${err instanceof Error ? err.message : err}`);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
// Detach worker — TmuxBackend.kill() does NOT destroy the tmux session, so
|
|
991
|
+
// the CLI process and its rolling jsonl continue running.
|
|
992
|
+
kw(ds);
|
|
993
|
+
activeSessionsRegistry?.delete(sessionKey(oldAnchor, ds.larkAppId));
|
|
994
|
+
// Rewrite routing fields. Target chat is always chat-scope: leader posts a
|
|
995
|
+
// notification message (M1) used as `targetRootMessageId` for trace, but
|
|
996
|
+
// chat-scope routes by chatId anyway, so M1 is purely audit/UX.
|
|
997
|
+
ds.session.chatId = targetChatId;
|
|
998
|
+
ds.session.rootMessageId = targetRootMessageId;
|
|
999
|
+
ds.session.scope = 'chat';
|
|
1000
|
+
ds.session.chatType = targetChatType;
|
|
1001
|
+
ds.session.lastMessageAt = new Date().toISOString();
|
|
1002
|
+
// Card state was pinned to the source chat — clear so the new worker posts
|
|
1003
|
+
// a fresh card in the target chat instead of trying to PATCH a message that
|
|
1004
|
+
// lives in another chat entirely (the source card was just frozen above).
|
|
1005
|
+
ds.session.streamCardId = undefined;
|
|
1006
|
+
ds.session.streamCardNonce = undefined;
|
|
1007
|
+
ds.session.currentImageKey = undefined;
|
|
1008
|
+
// Mirror onto runtime DaemonSession.
|
|
1009
|
+
ds.chatId = targetChatId;
|
|
1010
|
+
ds.chatType = targetChatType;
|
|
1011
|
+
ds.scope = 'chat';
|
|
1012
|
+
ds.streamCardId = undefined;
|
|
1013
|
+
ds.streamCardNonce = undefined;
|
|
1014
|
+
ds.currentImageKey = undefined;
|
|
1015
|
+
sessionStore.updateSession(ds.session);
|
|
1016
|
+
const newAnchor = sessionAnchorId(ds);
|
|
1017
|
+
if (activeSessionsRegistry) {
|
|
1018
|
+
await setActiveSessionSafe(activeSessionsRegistry, sessionKey(newAnchor, ds.larkAppId), ds);
|
|
1019
|
+
}
|
|
1020
|
+
dashboardEventBus.publish({
|
|
1021
|
+
type: 'session.update',
|
|
1022
|
+
body: {
|
|
1023
|
+
sessionId,
|
|
1024
|
+
patch: {
|
|
1025
|
+
chatId: targetChatId,
|
|
1026
|
+
rootMessageId: targetRootMessageId,
|
|
1027
|
+
scope: 'chat',
|
|
1028
|
+
chatType: targetChatType,
|
|
1029
|
+
},
|
|
1030
|
+
},
|
|
1031
|
+
});
|
|
1032
|
+
// forkWorker with resume=true — TmuxBackend.spawn detects the surviving
|
|
1033
|
+
// `bmx-<sessionId>` session and re-attaches instead of creating a new one.
|
|
1034
|
+
fkw(ds, '', /*resume*/ true);
|
|
1035
|
+
logger.info(`[${tagPrefix}] transferred ${oldChatId} → ${targetChatId} ` +
|
|
1036
|
+
`(anchor ${oldAnchor.substring(0, 8)} → ${newAnchor.substring(0, 8)})`);
|
|
1037
|
+
return { ok: true };
|
|
1038
|
+
}
|
|
552
1039
|
// ─── Fork worker ────────────────────────────────────────────────────────────
|
|
553
1040
|
export function forkWorker(ds, prompt, resume = false) {
|
|
554
1041
|
const cb = requireCallbacks();
|
|
@@ -622,6 +1109,7 @@ export function forkWorker(ds, prompt, resume = false) {
|
|
|
622
1109
|
workingDir: cwd,
|
|
623
1110
|
cliId: botCfg.cliId,
|
|
624
1111
|
cliPathOverride: botCfg.cliPathOverride,
|
|
1112
|
+
model: botCfg.model,
|
|
625
1113
|
backendType: botCfg.backendType ?? config.daemon.backendType,
|
|
626
1114
|
prompt,
|
|
627
1115
|
resume,
|
|
@@ -680,8 +1168,8 @@ function setupWorkerHandlers(ds, worker) {
|
|
|
680
1168
|
// Persist port so it can be reused after daemon restart
|
|
681
1169
|
ds.session.webPort = msg.port;
|
|
682
1170
|
sessionStore.updateSession(ds.session);
|
|
683
|
-
const readOnlyUrl =
|
|
684
|
-
const writeUrl =
|
|
1171
|
+
const readOnlyUrl = terminalReadUrl(msg.port);
|
|
1172
|
+
const writeUrl = terminalWriteUrl(msg.port, msg.token);
|
|
685
1173
|
logger.info(`[${t}] Worker ready, terminal at ${readOnlyUrl}`);
|
|
686
1174
|
if (ds.usageLimit) {
|
|
687
1175
|
ds.lastScreenStatus = 'limited';
|
|
@@ -695,6 +1183,14 @@ function setupWorkerHandlers(ds, worker) {
|
|
|
695
1183
|
patch: { webPort: msg.port },
|
|
696
1184
|
},
|
|
697
1185
|
});
|
|
1186
|
+
// Bot opted out of the streaming card: the terminal is up and the
|
|
1187
|
+
// final answer will still arrive via `botmux send`; just don't post the
|
|
1188
|
+
// live status card. (workerPort/token above are still set so the web
|
|
1189
|
+
// terminal + dashboard keep working.)
|
|
1190
|
+
if (streamingCardDisabled(ds)) {
|
|
1191
|
+
logger.info(`[${t}] Streaming card disabled for this bot — skipping card post`);
|
|
1192
|
+
break;
|
|
1193
|
+
}
|
|
698
1194
|
// If a previous streaming card survived (e.g. daemon restart), try to
|
|
699
1195
|
// PATCH it with the new "starting" state instead of POSTing a fresh card.
|
|
700
1196
|
// ds.streamCardPending forces a new card (e.g. mid-session repo switch
|
|
@@ -708,8 +1204,14 @@ function setupWorkerHandlers(ds, worker) {
|
|
|
708
1204
|
// Reuse persisted nonce so existing card buttons (toggle/etc) keep working.
|
|
709
1205
|
if (!ds.streamCardNonce)
|
|
710
1206
|
ds.streamCardNonce = randomBytes(4).toString('hex');
|
|
711
|
-
|
|
712
|
-
|
|
1207
|
+
// Prefer the last-known screen status when we have one — for /relay
|
|
1208
|
+
// resume the worker was idle/limited at transfer time and the
|
|
1209
|
+
// CLI didn't actually stop, so showing "starting" right after
|
|
1210
|
+
// the M1 "已接力" announcement is misleading. Fresh-spawn worker
|
|
1211
|
+
// and post-daemon-restart paths still see lastScreenStatus
|
|
1212
|
+
// undefined and fall back to 'starting' (unchanged behavior).
|
|
1213
|
+
const initStatus = ds.usageLimit ? 'limited' : (ds.lastScreenStatus ?? 'starting');
|
|
1214
|
+
const streamCardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readOnlyUrl, initTitle, ds.lastScreenContent ?? '', initStatus, effectiveCliId, ds.displayMode ?? 'hidden', ds.streamCardNonce, ds.currentImageKey, isAdopt, showTakeover, loc, initStatus === 'limited' ? ds.usageLimit : undefined, writableTerminalLinkFor(ds));
|
|
713
1215
|
await updateMessage(ds.larkAppId, restoredCardId, streamCardJson);
|
|
714
1216
|
persistStreamCardState(ds);
|
|
715
1217
|
// Re-sync worker's display mode (it starts fresh in 'hidden')
|
|
@@ -737,8 +1239,16 @@ function setupWorkerHandlers(ds, worker) {
|
|
|
737
1239
|
try {
|
|
738
1240
|
ds.streamCardNonce = randomBytes(4).toString('hex');
|
|
739
1241
|
const initTitle = ds.currentTurnTitle || ds.session.title || getCliDisplayName(effectiveCliId);
|
|
740
|
-
|
|
741
|
-
|
|
1242
|
+
// See PATCH-branch comment above re: lastScreenStatus preference.
|
|
1243
|
+
// For relay (kill+fork with surviving tmux/CLI), this avoids the
|
|
1244
|
+
// jarring "启动中" right after the M1 "已接力" announcement.
|
|
1245
|
+
const initStatus = ds.usageLimit ? 'limited' : (ds.lastScreenStatus ?? 'starting');
|
|
1246
|
+
const streamCardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readOnlyUrl, initTitle,
|
|
1247
|
+
// For /relay resume, ds.lastScreenContent is the cached pane
|
|
1248
|
+
// from before the kill+fork — using it avoids a blank flash
|
|
1249
|
+
// before the first screen_update lands. Fresh worker spawn
|
|
1250
|
+
// has lastScreenContent undefined → '' (unchanged).
|
|
1251
|
+
ds.lastScreenContent ?? '', initStatus, effectiveCliId, ds.displayMode ?? 'hidden', ds.streamCardNonce, ds.currentImageKey, isAdopt, showTakeover, loc, initStatus === 'limited' ? ds.usageLimit : undefined, writableTerminalLinkFor(ds));
|
|
742
1252
|
ds.streamCardId = await cb.sessionReply(sessionAnchorId(ds), streamCardJson, 'interactive', ds.larkAppId);
|
|
743
1253
|
persistStreamCardState(ds);
|
|
744
1254
|
// Re-sync worker's display mode (it starts fresh in 'hidden')
|
|
@@ -811,6 +1321,10 @@ function setupWorkerHandlers(ds, worker) {
|
|
|
811
1321
|
},
|
|
812
1322
|
});
|
|
813
1323
|
}
|
|
1324
|
+
// Bot opted out of the streaming card — dashboard SSE above already got
|
|
1325
|
+
// the status patch; just don't touch any Lark card.
|
|
1326
|
+
if (streamingCardDisabled(ds))
|
|
1327
|
+
break;
|
|
814
1328
|
const readUrl = `http://${config.web.externalHost}:${ds.workerPort}`;
|
|
815
1329
|
const turnTitle = ds.currentTurnTitle || ds.session.title || getCliDisplayName(effectiveCliId);
|
|
816
1330
|
const mode = ds.displayMode ?? 'hidden';
|
|
@@ -826,7 +1340,7 @@ function setupWorkerHandlers(ds, worker) {
|
|
|
826
1340
|
// New turn → image_key from previous turn no longer valid
|
|
827
1341
|
if (isNewTurn)
|
|
828
1342
|
ds.currentImageKey = undefined;
|
|
829
|
-
const cardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readUrl, turnTitle, isNewTurn ? '' : msg.content, ds.lastScreenStatus, effectiveCliId, mode, ds.streamCardNonce, ds.currentImageKey, isAdopt, showTakeover, loc, cardUsageLimit(ds));
|
|
1343
|
+
const cardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readUrl, turnTitle, isNewTurn ? '' : msg.content, ds.lastScreenStatus, effectiveCliId, mode, ds.streamCardNonce, ds.currentImageKey, isAdopt, showTakeover, loc, cardUsageLimit(ds), writableTerminalLinkFor(ds));
|
|
830
1344
|
// Mark POST in-flight so subsequent screen_updates are dropped,
|
|
831
1345
|
// not POSTed as duplicate cards.
|
|
832
1346
|
ds.streamCardPending = false;
|
|
@@ -860,7 +1374,7 @@ function setupWorkerHandlers(ds, worker) {
|
|
|
860
1374
|
const statusChanged = prevStatus !== ds.lastScreenStatus;
|
|
861
1375
|
if (!statusChanged)
|
|
862
1376
|
break;
|
|
863
|
-
const cardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readUrl, turnTitle, msg.content, ds.lastScreenStatus, effectiveCliId, mode, ds.streamCardNonce, ds.currentImageKey, isAdopt, showTakeover, loc, cardUsageLimit(ds));
|
|
1377
|
+
const cardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readUrl, turnTitle, msg.content, ds.lastScreenStatus, effectiveCliId, mode, ds.streamCardNonce, ds.currentImageKey, isAdopt, showTakeover, loc, cardUsageLimit(ds), writableTerminalLinkFor(ds));
|
|
864
1378
|
scheduleCardPatch(ds, cardJson);
|
|
865
1379
|
}
|
|
866
1380
|
break;
|
|
@@ -880,7 +1394,7 @@ function setupWorkerHandlers(ds, worker) {
|
|
|
880
1394
|
break;
|
|
881
1395
|
const readUrl = `http://${config.web.externalHost}:${ds.workerPort}`;
|
|
882
1396
|
const turnTitle = ds.currentTurnTitle || ds.session.title || getCliDisplayName(effectiveCliId);
|
|
883
|
-
const cardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readUrl, turnTitle, ds.lastScreenContent ?? '', ds.lastScreenStatus, effectiveCliId, 'screenshot', ds.streamCardNonce, ds.currentImageKey, isAdopt, showTakeover, loc, cardUsageLimit(ds));
|
|
1397
|
+
const cardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readUrl, turnTitle, ds.lastScreenContent ?? '', ds.lastScreenStatus, effectiveCliId, 'screenshot', ds.streamCardNonce, ds.currentImageKey, isAdopt, showTakeover, loc, cardUsageLimit(ds), writableTerminalLinkFor(ds));
|
|
884
1398
|
scheduleCardPatch(ds, cardJson);
|
|
885
1399
|
break;
|
|
886
1400
|
}
|
|
@@ -937,7 +1451,7 @@ function setupWorkerHandlers(ds, worker) {
|
|
|
937
1451
|
if (ds.streamCardId && ds.workerPort) {
|
|
938
1452
|
const readUrl = `http://${config.web.externalHost}:${ds.workerPort}`;
|
|
939
1453
|
const turnTitle = ds.currentTurnTitle || ds.session.title || getCliDisplayName(effectiveCliId);
|
|
940
|
-
const frozenCard = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readUrl, turnTitle, ds.lastScreenContent ?? '', 'idle', effectiveCliId, ds.displayMode ?? 'hidden', ds.streamCardNonce, ds.currentImageKey, isAdopt, showTakeover);
|
|
1454
|
+
const frozenCard = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readUrl, turnTitle, ds.lastScreenContent ?? '', 'idle', effectiveCliId, ds.displayMode ?? 'hidden', ds.streamCardNonce, ds.currentImageKey, isAdopt, showTakeover, loc, undefined, writableTerminalLinkFor(ds));
|
|
941
1455
|
scheduleCardPatch(ds, frozenCard);
|
|
942
1456
|
}
|
|
943
1457
|
killWorker(ds);
|
|
@@ -969,7 +1483,7 @@ function setupWorkerHandlers(ds, worker) {
|
|
|
969
1483
|
if (ds.streamCardId && ds.workerPort) {
|
|
970
1484
|
const readUrl = `http://${config.web.externalHost}:${ds.workerPort}`;
|
|
971
1485
|
const turnTitle = ds.currentTurnTitle || ds.session.title || getCliDisplayName(effectiveCliId);
|
|
972
|
-
const frozenCard = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readUrl, turnTitle, ds.lastScreenContent ?? '', 'idle', effectiveCliId, ds.displayMode ?? 'hidden', ds.streamCardNonce, ds.currentImageKey, isAdopt, showTakeover, loc);
|
|
1486
|
+
const frozenCard = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readUrl, turnTitle, ds.lastScreenContent ?? '', 'idle', effectiveCliId, ds.displayMode ?? 'hidden', ds.streamCardNonce, ds.currentImageKey, isAdopt, showTakeover, loc, undefined, writableTerminalLinkFor(ds));
|
|
973
1487
|
scheduleCardPatch(ds, frozenCard);
|
|
974
1488
|
}
|
|
975
1489
|
// Kill the worker process to free resources
|
|
@@ -1037,12 +1551,14 @@ function setupWorkerHandlers(ds, worker) {
|
|
|
1037
1551
|
}
|
|
1038
1552
|
if (!msg.userText.trim() && !msg.assistantText.trim())
|
|
1039
1553
|
break;
|
|
1554
|
+
const recipientOpenId = daemonCardFooterRecipientOpenId(ds);
|
|
1040
1555
|
const cardJson = buildContextualReplyCard({
|
|
1041
1556
|
title: '📜 /adopt 前最后一轮',
|
|
1042
1557
|
userText: msg.userText,
|
|
1043
1558
|
assistantText: msg.assistantText,
|
|
1044
1559
|
assistantLabel: getCliDisplayName(effectiveCliId),
|
|
1045
|
-
recipientOpenId
|
|
1560
|
+
recipientOpenId,
|
|
1561
|
+
brand: resolveBrandLabel(ds.larkAppId),
|
|
1046
1562
|
});
|
|
1047
1563
|
cb.sessionReply(sessionAnchorId(ds), cardJson, 'interactive', ds.larkAppId).catch((err) => {
|
|
1048
1564
|
logger.warn(`[${t}] Failed to deliver adopt_preamble to Lark: ${err.message}`);
|
|
@@ -1104,6 +1620,7 @@ function deliverFinalOutput(ds, msg, t, attempt) {
|
|
|
1104
1620
|
// they use the contextual card so the user prompt sits in a
|
|
1105
1621
|
// blockquote and only the assistant body goes through full markdown
|
|
1106
1622
|
// rendering.
|
|
1623
|
+
const recipientOpenId = daemonCardFooterRecipientOpenId(ds);
|
|
1107
1624
|
const cardJson = msg.kind === 'local-turn' || msg.kind === 'local-turn-headless'
|
|
1108
1625
|
? buildContextualReplyCard({
|
|
1109
1626
|
title: msg.kind === 'local-turn-headless'
|
|
@@ -1112,9 +1629,10 @@ function deliverFinalOutput(ds, msg, t, attempt) {
|
|
|
1112
1629
|
userText: msg.kind === 'local-turn' ? msg.userText ?? '' : undefined,
|
|
1113
1630
|
assistantText: msg.content,
|
|
1114
1631
|
assistantLabel: getCliDisplayName(effectiveCliId),
|
|
1115
|
-
recipientOpenId
|
|
1632
|
+
recipientOpenId,
|
|
1633
|
+
brand: resolveBrandLabel(ds.larkAppId),
|
|
1116
1634
|
})
|
|
1117
|
-
: buildMarkdownCard(msg.content, ds.
|
|
1635
|
+
: buildMarkdownCard(msg.content, recipientOpenId, resolveBrandLabel(ds.larkAppId));
|
|
1118
1636
|
await cb.sessionReply(sessionAnchorId(ds), cardJson, 'interactive', ds.larkAppId);
|
|
1119
1637
|
ds.lastBridgeEmittedUuid = msg.lastUuid;
|
|
1120
1638
|
logger.info(`[${t}] Bridge final_output forwarded (turn ${msg.turnId.substring(0, 8)}, ${msg.content.length} chars, kind=${msg.kind ?? 'bridge'}, attempt ${attempt + 1})`);
|
|
@@ -1219,6 +1737,7 @@ export function forkAdoptWorker(ds, opts) {
|
|
|
1219
1737
|
workingDir: adopted.cwd,
|
|
1220
1738
|
cliId: adoptedCliId,
|
|
1221
1739
|
cliSessionId: isStructuredBridge ? adopted.sessionId : undefined,
|
|
1740
|
+
model: botCfg.model,
|
|
1222
1741
|
backendType: 'tmux',
|
|
1223
1742
|
prompt: '',
|
|
1224
1743
|
resume: false,
|