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/cli.js
CHANGED
|
@@ -29,10 +29,12 @@ import { enableAutostart, disableAutostart, autostartStatus, refreshAutostart }
|
|
|
29
29
|
import { tmuxEnv } from './setup/ensure-tmux.js';
|
|
30
30
|
import { writeBotsJsonAtomic as writeBotsAtomic } from './setup/bots-store.js';
|
|
31
31
|
import { applyBotConfigEdits, assertUniqueBotProcessNames, botProcessName, normalizeBotConfig, parseBotConfigsJson, parseBotSelection, removeBotConfig, resolveCliId, assertOwnerWhenChatGroups, findInvalidAllowedUserEntries, hasOwnerEntry, } from './setup/bot-config-editor.js';
|
|
32
|
+
import { createCliAdapterSync } from './adapters/cli/registry.js';
|
|
32
33
|
import { logger } from './utils/logger.js';
|
|
33
34
|
import { invalidWorkingDirs } from './utils/working-dir.js';
|
|
34
35
|
import { firstPositional } from './cli/arg-utils.js';
|
|
35
36
|
import { formatBotInfoEntriesForCli, formatChatBotsForCli, } from './cli/bots-list-output.js';
|
|
37
|
+
import { buildFooterAddressing, hasKnownBotMention, knownBotOpenIdsFromCrossRef, orderedFooterRecipients, } from './utils/bot-routing.js';
|
|
36
38
|
import { isLocale, setDefaultLocale, SUPPORTED_LOCALES } from './i18n/index.js';
|
|
37
39
|
import { readGlobalConfig, setGlobalLocale, globalConfigPath } from './global-config.js';
|
|
38
40
|
// Resolve the CLI's UI locale once from the global config file, so subsequent
|
|
@@ -241,6 +243,12 @@ function ecosystemConfig() {
|
|
|
241
243
|
out_file: join(LOG_DIR, 'dashboard-out.log'),
|
|
242
244
|
merge_logs: true,
|
|
243
245
|
env: {
|
|
246
|
+
// MUST match the bot daemons' SESSION_DATA_DIR: the dashboard shares
|
|
247
|
+
// pairings/federations/memberships with them via {dataDir}/*.json. Without
|
|
248
|
+
// it the dashboard falls back to an install-relative ../data and reads a
|
|
249
|
+
// DIFFERENT store → /pair「配对码无效」, auto-bind hubsSynced:0,
|
|
250
|
+
// remote-group not_a_member (cross-deployment 拉群 silently broken).
|
|
251
|
+
SESSION_DATA_DIR: DATA_DIR,
|
|
244
252
|
BOTMUX_DASHBOARD_HOST: process.env.BOTMUX_DASHBOARD_HOST ?? '0.0.0.0',
|
|
245
253
|
BOTMUX_DASHBOARD_PORT: process.env.BOTMUX_DASHBOARD_PORT ?? '7891',
|
|
246
254
|
},
|
|
@@ -263,6 +271,63 @@ function printInputHelp(title, lines) {
|
|
|
263
271
|
console.log(` ${line}`);
|
|
264
272
|
}
|
|
265
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* 读取指定 CLI 适配器声明的候选 model 列表 —— 不支持 model 配置的 CLI(aiden/
|
|
276
|
+
* antigravity/mtr 等没声明 modelChoices)返回 null,promptModel 据此整段
|
|
277
|
+
* 跳过提问。适配器解析失败也按"不支持"处理,避免在 setup 中冒出陌生堆栈。
|
|
278
|
+
*/
|
|
279
|
+
function cliModelChoices(cliId) {
|
|
280
|
+
try {
|
|
281
|
+
const adapter = createCliAdapterSync(cliId);
|
|
282
|
+
return adapter.modelChoices && adapter.modelChoices.length > 0
|
|
283
|
+
? adapter.modelChoices
|
|
284
|
+
: null;
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* 询问"指定 CLI 用哪个 model"。
|
|
292
|
+
* - cliId 对应的 adapter 没声明 modelChoices → return undefined(跳过提问)
|
|
293
|
+
* - 用户输入序号 → 取候选列表对应项
|
|
294
|
+
* - 输入空 + 提供 current → 保留当前值(current 透传出去)
|
|
295
|
+
* - 输入空 + 无 current → return undefined(用 CLI 默认)
|
|
296
|
+
* - 输入 `-` → return null(语义:清空,调用方据此 delete model 字段)
|
|
297
|
+
* - 输入自由文本 → 原样返回
|
|
298
|
+
*/
|
|
299
|
+
async function promptModel(rl, cliId, current) {
|
|
300
|
+
const choices = cliModelChoices(cliId);
|
|
301
|
+
if (!choices)
|
|
302
|
+
return undefined;
|
|
303
|
+
const numbered = choices.map((m, i) => `${i + 1}) ${m}`).join(' ');
|
|
304
|
+
printInputHelp(`CLI Model(${cliId})`, [
|
|
305
|
+
'可选。在 spawn CLI 时注入 model 参数;同一 CLI 配多个 bot 可以跑不同 model。',
|
|
306
|
+
`候选: ${numbered} ${choices.length + 1}) Other(自定义输入)`,
|
|
307
|
+
current
|
|
308
|
+
? '留空保留当前值;输入 - 清空(回到 CLI 默认)。'
|
|
309
|
+
: '留空 = 不设置(用 CLI 默认)。',
|
|
310
|
+
]);
|
|
311
|
+
const label = current ? formatOptionalValue(current) : '未设置';
|
|
312
|
+
const raw = (await ask(rl, `CLI Model [${label}]: `)).trim();
|
|
313
|
+
if (!raw)
|
|
314
|
+
return current ?? undefined;
|
|
315
|
+
if (raw === '-')
|
|
316
|
+
return null;
|
|
317
|
+
const numIdx = Number(raw);
|
|
318
|
+
if (Number.isInteger(numIdx) && numIdx >= 1 && numIdx <= choices.length) {
|
|
319
|
+
return choices[numIdx - 1];
|
|
320
|
+
}
|
|
321
|
+
if (Number.isInteger(numIdx) && numIdx === choices.length + 1) {
|
|
322
|
+
// 选了 Other,进一步要 free-form
|
|
323
|
+
const customRaw = (await ask(rl, '请输入 model 名: ')).trim();
|
|
324
|
+
if (!customRaw)
|
|
325
|
+
return current ?? undefined;
|
|
326
|
+
return customRaw;
|
|
327
|
+
}
|
|
328
|
+
// 直接当成自由输入
|
|
329
|
+
return raw;
|
|
330
|
+
}
|
|
266
331
|
// Thin wrapper around setup/bots-store.writeBotsJsonAtomic so call-sites keep
|
|
267
332
|
// the same name without passing BOTS_JSON_FILE explicitly each time.
|
|
268
333
|
function writeBotsJsonAtomic(bots) {
|
|
@@ -478,7 +543,7 @@ async function promptBotConfig(rl) {
|
|
|
478
543
|
return null;
|
|
479
544
|
}
|
|
480
545
|
console.log('✅ 凭证有效(tenant_access_token 已成功获取)\n');
|
|
481
|
-
console.log('支持的 CLI: 1) claude-code 2) aiden 3) coco 4) codex 5) cursor 6) gemini 7) opencode 8) antigravity');
|
|
546
|
+
console.log('支持的 CLI: 1) claude-code 2) aiden 3) coco 4) codex 5) cursor 6) gemini 7) opencode 8) antigravity 9) mtr 10) hermes 11) codex-app 12) mira');
|
|
482
547
|
const cliChoice = await ask(rl, 'CLI 适配器 [1]: ');
|
|
483
548
|
let cliId;
|
|
484
549
|
try {
|
|
@@ -490,6 +555,7 @@ async function promptBotConfig(rl) {
|
|
|
490
555
|
return null;
|
|
491
556
|
}
|
|
492
557
|
const workingDir = await ask(rl, '默认工作目录 [~]: ');
|
|
558
|
+
const modelChoice = await promptModel(rl, cliId);
|
|
493
559
|
// 不再持久化 brand 字段: setup 阶段 brand=lark 直接被 obtainCredentials 中止,
|
|
494
560
|
// 落盘的永远是 'feishu', 写进配置是死字段. 等 lark 完整接入再加回来, 那时
|
|
495
561
|
// 同步打开 daemon 链路的 brand 透传. botBrand() helper 读不到字段会 default
|
|
@@ -502,6 +568,10 @@ async function promptBotConfig(rl) {
|
|
|
502
568
|
// 在哪儿, 不用去 README 查字段名.
|
|
503
569
|
workingDir: workingDir.trim() || '~',
|
|
504
570
|
};
|
|
571
|
+
// modelChoice === undefined → 该 CLI 没声明候选 / 用户跳过;不写 model 字段
|
|
572
|
+
if (typeof modelChoice === 'string' && modelChoice) {
|
|
573
|
+
bot.model = modelChoice;
|
|
574
|
+
}
|
|
505
575
|
// 扫码场景默认填扫码人自己 (registerApp 返回里有 open_id), 天然就是 owner.
|
|
506
576
|
// 手动 fallback 场景没 open_id —— 必须显式指定 owner, 否则配置无 owner:
|
|
507
577
|
// allowedUsers 为空时虽然"全开放", 但一旦后续加了 allowedChatGroups 就会变成
|
|
@@ -562,7 +632,7 @@ async function promptEditBotConfig(rl, bot) {
|
|
|
562
632
|
'留空保留当前值。',
|
|
563
633
|
]);
|
|
564
634
|
input.larkAppSecret = await ask(rl, `LARK_APP_SECRET [保留当前值]: `);
|
|
565
|
-
console.log('\n支持的 CLI: 1) claude-code 2) aiden 3) coco 4) codex 5) cursor 6) gemini 7) opencode 8) antigravity');
|
|
635
|
+
console.log('\n支持的 CLI: 1) claude-code 2) aiden 3) coco 4) codex 5) cursor 6) gemini 7) opencode 8) antigravity 9) mtr 10) hermes 11) codex-app 12) mira');
|
|
566
636
|
printInputHelp('CLI 适配器', [
|
|
567
637
|
'选择 botmux 需要套用哪一种 CLI 参数协议和会话恢复方式。',
|
|
568
638
|
'留空保留当前值;可以输入序号,也可以直接输入适配器 ID。',
|
|
@@ -574,6 +644,29 @@ async function promptEditBotConfig(rl, bot) {
|
|
|
574
644
|
'留空保留当前值;输入 - 清空覆盖,回到 PATH 查 cliId 对应的默认二进制。',
|
|
575
645
|
]);
|
|
576
646
|
input.cliPathOverride = await ask(rl, `CLI 可执行文件路径覆盖 [${formatOptionalValue(bot.cliPathOverride)}]: `);
|
|
647
|
+
// promptModel 返回 string | null | undefined,直接灌进 BotConfigEditInput.model:
|
|
648
|
+
// undefined = 用户跳过 / adapter 不支持 → applyBotConfigEdits 不改 model
|
|
649
|
+
// null = 用户输 `-` 清空 → delete model
|
|
650
|
+
// string = 设值
|
|
651
|
+
// 用本轮编辑后的 cliId 而非 bot.cliId —— 用户可能刚换了 CLI。
|
|
652
|
+
const effectiveCliIdForModel = (resolveCliId(input.cliChoice) ?? bot.cliId ?? 'claude-code');
|
|
653
|
+
const cliChanged = !!resolveCliId(input.cliChoice) && resolveCliId(input.cliChoice) !== bot.cliId;
|
|
654
|
+
if (cliChanged && !cliModelChoices(effectiveCliIdForModel)) {
|
|
655
|
+
// 切到一个不支持 model 的 adapter(例如 aiden / mtr / antigravity):
|
|
656
|
+
// 即使原本配了 model 也要主动清空,避免 bots.json 里残留陈旧字段——
|
|
657
|
+
// 否则用户后面再换回支持 model 的 adapter 一路回车时,旧 model
|
|
658
|
+
// 会被当作"当前值"保留下来误套到新 CLI 上。
|
|
659
|
+
console.log('\n⚠️ 新 CLI 不支持 --model 参数,已清空原 model 字段。');
|
|
660
|
+
input.model = null;
|
|
661
|
+
}
|
|
662
|
+
else {
|
|
663
|
+
const promptCurrent = cliChanged ? undefined : (typeof bot.model === 'string' ? bot.model : undefined);
|
|
664
|
+
const result = await promptModel(rl, effectiveCliIdForModel, promptCurrent);
|
|
665
|
+
// 切换 CLI 时哪怕用户留空也要清掉旧 model —— 旧值是上一个 CLI 的 model,
|
|
666
|
+
// 套到新 CLI 上没意义。result === undefined 在"未变 CLI"分支等价于"保留旧值",
|
|
667
|
+
// 但在 cliChanged 分支等价于"用户没指定,回到新 CLI 默认",必须 force null。
|
|
668
|
+
input.model = cliChanged && result === undefined ? null : result;
|
|
669
|
+
}
|
|
577
670
|
printInputHelp('会话后端 backendType', [
|
|
578
671
|
'可选。pty 更轻量;tmux 支持 adopt 和 Web Terminal 附着。',
|
|
579
672
|
'留空保留当前值;输入 - 回到自动检测;只接受 pty 或 tmux。',
|
|
@@ -2008,9 +2101,17 @@ botmux v${getVersion()} — IM ↔ AI 编程 CLI 桥接
|
|
|
2008
2101
|
--images <path> 内联图片(可重复)
|
|
2009
2102
|
--files <path> 附件(可重复)
|
|
2010
2103
|
--mention <open_id:name> @提及(可重复)
|
|
2104
|
+
--mention-back @回本轮触发消息的发送者(open_id 自动取自会话)
|
|
2105
|
+
--no-mention 明确声明本条不@任何人
|
|
2106
|
+
--quote <message_id> 指定引用某条消息(普通群,默认引用本轮触发消息)
|
|
2107
|
+
--no-quote 不引用,发独立消息(普通群)
|
|
2011
2108
|
--card | --text 强制卡片 / 纯文本(默认按 md 语法自动判断)
|
|
2012
2109
|
--top-level 发顶层消息(不回复进当前话题)
|
|
2013
2110
|
--chat-id <oc_xxx> 指定目标群(默认当前话题所在群)
|
|
2111
|
+
@ 硬门:每条回复须三选一 --mention/--mention-back/--no-mention,否则报错不发。
|
|
2112
|
+
按内容价值选:有实质结论要对方看/确认/决策→--mention-back(或--mention点名);
|
|
2113
|
+
纯记录/低优先级进度/简短确认→--no-mention;没信息量的"收到"不如不发。
|
|
2114
|
+
(可设 BOTMUX_REQUIRE_MENTION_DECISION=false 关闭硬门)
|
|
2014
2115
|
bots list 列出当前群聊中的机器人(含 open_id)
|
|
2015
2116
|
history [--limit N] [--scope session|thread|chat|ambient]
|
|
2016
2117
|
拉取当前会话的消息历史 (JSON)。默认按 session scope:话题/话题群 → 话题内,普通群 → 整群;
|
|
@@ -2455,21 +2556,10 @@ function argValues(args, ...flags) {
|
|
|
2455
2556
|
// Card v2 body builder helpers — extracted to im/lark/md-card.ts so the
|
|
2456
2557
|
// daemon's bridge fallback path can produce identical cards. cmdSend
|
|
2457
2558
|
// keeps using `buildCardBodyElements` and `hasMarkdown` from there.
|
|
2458
|
-
import { buildCardBodyElements, hasMarkdown } from './im/lark/md-card.js';
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
* Non-oncall chats: `发送给: @<owner>`.
|
|
2463
|
-
* Oncall chats: `发送给: @<last caller>` (falls back to owner if unknown) —
|
|
2464
|
-
* permission is governed by allowedUsers, so there's no per-chat list to cc.
|
|
2465
|
-
*/
|
|
2466
|
-
function buildFooterAddressing(s, oncall) {
|
|
2467
|
-
const owner = s.ownerOpenId;
|
|
2468
|
-
const caller = s.lastCallerOpenId ?? owner;
|
|
2469
|
-
if (!oncall)
|
|
2470
|
-
return { sendTo: owner, cc: [] };
|
|
2471
|
-
return { sendTo: caller, cc: [] };
|
|
2472
|
-
}
|
|
2559
|
+
import { buildCardBodyElements, hasMarkdown, brandFooterSegment } from './im/lark/md-card.js';
|
|
2560
|
+
import { resolveBrandLabel } from './bot-registry.js';
|
|
2561
|
+
import { config } from './config.js';
|
|
2562
|
+
import { resolveQuoteTarget, validateMentionDecision } from './services/send-policy.js';
|
|
2473
2563
|
async function cmdSend(rest) {
|
|
2474
2564
|
// Safety gate: a CLI agent running inside a workflow subagent (Slice F)
|
|
2475
2565
|
// must not chat-post directly — chat-facing side effects are reserved
|
|
@@ -2498,6 +2588,13 @@ async function cmdSend(rest) {
|
|
|
2498
2588
|
// for streaming-card / progress UI.
|
|
2499
2589
|
const sendTopLevel = rest.includes('--top-level');
|
|
2500
2590
|
const overrideChatId = argValue(rest, '--chat-id');
|
|
2591
|
+
// Quote chain (chat scope): --quote <message_id> overrides the auto target,
|
|
2592
|
+
// --no-quote forces a plain (un-quoted) send.
|
|
2593
|
+
const explicitQuote = argValue(rest, '--quote');
|
|
2594
|
+
const noQuote = rest.includes('--no-quote');
|
|
2595
|
+
// @ hard-gate: every reply must explicitly choose one of these.
|
|
2596
|
+
const mentionBack = rest.includes('--mention-back');
|
|
2597
|
+
const noMention = rest.includes('--no-mention');
|
|
2501
2598
|
const sid = sessionIdArg ?? findAncestorSessionId();
|
|
2502
2599
|
if (!sid) {
|
|
2503
2600
|
console.error('无法推断 session-id。请在 Lark 话题内的 CLI 会话中运行,或传 --session-id <id>。');
|
|
@@ -2523,7 +2620,7 @@ async function cmdSend(rest) {
|
|
|
2523
2620
|
content = readFileSync(contentFile, 'utf-8');
|
|
2524
2621
|
}
|
|
2525
2622
|
else {
|
|
2526
|
-
const pos = positionals(rest, ['--card', '--text', '--top-level']);
|
|
2623
|
+
const pos = positionals(rest, ['--card', '--text', '--top-level', '--no-quote', '--mention-back', '--no-mention']);
|
|
2527
2624
|
if (pos.length > 0) {
|
|
2528
2625
|
content = pos.join(' ');
|
|
2529
2626
|
}
|
|
@@ -2549,6 +2646,28 @@ async function cmdSend(rest) {
|
|
|
2549
2646
|
mentions.push({ open_id: m.trim(), name: '' });
|
|
2550
2647
|
}
|
|
2551
2648
|
}
|
|
2649
|
+
// @ hard-gate (config.send.requireMentionDecision, default on): force the
|
|
2650
|
+
// model to make an explicit @ decision before sending. --top-level publish
|
|
2651
|
+
// is exempt. The error text adapts to who is being replied to (人 / bot).
|
|
2652
|
+
const mentionGate = validateMentionDecision({
|
|
2653
|
+
enabled: config.send.requireMentionDecision,
|
|
2654
|
+
sendTopLevel,
|
|
2655
|
+
hasMentionArgs: mentionArgs.length > 0,
|
|
2656
|
+
mentionBack,
|
|
2657
|
+
noMention,
|
|
2658
|
+
hasQuoteTargetSender: !!s.quoteTargetSenderOpenId,
|
|
2659
|
+
});
|
|
2660
|
+
if (!mentionGate.ok) {
|
|
2661
|
+
console.error(mentionGate.error);
|
|
2662
|
+
process.exit(2);
|
|
2663
|
+
}
|
|
2664
|
+
// --mention-back: @ the sender of the message this turn is replying to
|
|
2665
|
+
// (open_id from the session — model needn't know it). Bare-name form so it
|
|
2666
|
+
// renders as a trailing <at>.
|
|
2667
|
+
if (mentionBack && s.quoteTargetSenderOpenId
|
|
2668
|
+
&& !mentions.some(m => m.open_id === s.quoteTargetSenderOpenId)) {
|
|
2669
|
+
mentions.push({ open_id: s.quoteTargetSenderOpenId, name: '' });
|
|
2670
|
+
}
|
|
2552
2671
|
// Validate file paths
|
|
2553
2672
|
for (const p of [...images, ...files]) {
|
|
2554
2673
|
if (!existsSync(p)) {
|
|
@@ -2563,7 +2682,7 @@ async function cmdSend(rest) {
|
|
|
2563
2682
|
registerBot(cfg);
|
|
2564
2683
|
}
|
|
2565
2684
|
catch { /* */ }
|
|
2566
|
-
const { sendMessage, replyMessage, uploadImage, uploadFile } = await import('./im/lark/client.js');
|
|
2685
|
+
const { sendMessage, replyMessage, uploadImage, uploadFile, MessageWithdrawnError } = await import('./im/lark/client.js');
|
|
2567
2686
|
const appId = s.larkAppId;
|
|
2568
2687
|
// Effective target chat for top-level mode (defaults to session's chat)
|
|
2569
2688
|
const targetChatId = overrideChatId ?? s.chatId;
|
|
@@ -2578,10 +2697,36 @@ async function cmdSend(rest) {
|
|
|
2578
2697
|
// addressing to go to the last caller in the shared oncall workspace.
|
|
2579
2698
|
const oncallEntry = !sendTopLevel && !overrideChatId && s.chatId
|
|
2580
2699
|
? findOncallChatForAnyBot(s.chatId) : undefined;
|
|
2581
|
-
// Dispatch helper: top-level / chat-scope send vs reply-in-thread, single
|
|
2700
|
+
// Dispatch helper: top-level / chat-scope send vs reply-in-thread, single
|
|
2701
|
+
// decision point. Used for file attachments (always plain in chat scope).
|
|
2582
2702
|
const dispatch = (content, msgType) => (sendTopLevel || isChatScope)
|
|
2583
2703
|
? sendMessage(appId, targetChatId, content, msgType)
|
|
2584
2704
|
: replyMessage(appId, s.rootMessageId, content, msgType, true);
|
|
2705
|
+
// Quote chain (普通群): the primary message replies to the turn's target so
|
|
2706
|
+
// Lark renders a 引用 chain. --quote overrides, --no-quote opts out. Thread
|
|
2707
|
+
// scope and --top-level never quote. Withdrawn target → fall back to plain.
|
|
2708
|
+
const quoteTargetId = resolveQuoteTarget({
|
|
2709
|
+
isChatScope, sendTopLevel, noQuote, explicitQuote,
|
|
2710
|
+
sessionQuoteTargetId: s.quoteTargetId,
|
|
2711
|
+
});
|
|
2712
|
+
let primaryQuotedId = null;
|
|
2713
|
+
const dispatchPrimary = async (content, msgType) => {
|
|
2714
|
+
if (quoteTargetId) {
|
|
2715
|
+
try {
|
|
2716
|
+
const id = await replyMessage(appId, quoteTargetId, content, msgType, false);
|
|
2717
|
+
primaryQuotedId = quoteTargetId;
|
|
2718
|
+
return id;
|
|
2719
|
+
}
|
|
2720
|
+
catch (err) {
|
|
2721
|
+
if (err instanceof MessageWithdrawnError) {
|
|
2722
|
+
console.error(`引用目标 ${quoteTargetId} 已撤回,改为普通发送`);
|
|
2723
|
+
return sendMessage(appId, targetChatId, content, msgType);
|
|
2724
|
+
}
|
|
2725
|
+
throw err;
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
return dispatch(content, msgType);
|
|
2729
|
+
};
|
|
2585
2730
|
try {
|
|
2586
2731
|
// Upload images in parallel
|
|
2587
2732
|
const imageKeys = [];
|
|
@@ -2616,51 +2761,76 @@ async function cmdSend(rest) {
|
|
|
2616
2761
|
// "获取群组中其他机器人和用户@当前机器人的消息"权限),不再走任何本地
|
|
2617
2762
|
// 转发——botmux 历史上为绕过 Lark 不投递跨 bot 事件搞过 signal-file,
|
|
2618
2763
|
// 那套已经在该权限上线后整体下线。
|
|
2764
|
+
let botEntries = [];
|
|
2765
|
+
let crossRef = {};
|
|
2619
2766
|
try {
|
|
2620
2767
|
const dataDir = resolveDataDir();
|
|
2621
2768
|
const botInfoPath = join(dataDir, 'bots-info.json');
|
|
2622
|
-
|
|
2769
|
+
botEntries = existsSync(botInfoPath) ? JSON.parse(readFileSync(botInfoPath, 'utf-8')) : [];
|
|
2623
2770
|
const crossRefPath = join(dataDir, `bot-openids-${appId}.json`);
|
|
2624
|
-
|
|
2771
|
+
crossRef = existsSync(crossRefPath)
|
|
2625
2772
|
? JSON.parse(readFileSync(crossRefPath, 'utf-8'))
|
|
2626
2773
|
: {};
|
|
2627
|
-
|
|
2628
|
-
//
|
|
2629
|
-
//
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
const
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
const re = new RegExp(`(?<![A-Za-z0-9_])@${escName}(?![\\p{L}\\p{N}_])`, 'iu');
|
|
2644
|
-
if (!re.test(text))
|
|
2774
|
+
// --no-mention 显式不 @ 任何人:跳过正文 @BotName 的自动注入,否则正文里
|
|
2775
|
+
// 出现的 @名字 仍会被注入成 <at>,破坏 --no-mention 语义、还可能误触发对方
|
|
2776
|
+
// bot(正是要避免的循环 @)。botEntries/crossRef 仍需加载供 footer 寻址用。
|
|
2777
|
+
if (!noMention) {
|
|
2778
|
+
const alreadyMentioned = new Set(mentions.map(m => m.open_id));
|
|
2779
|
+
// Sort by name length desc so longer names ("Claude分身") win over their
|
|
2780
|
+
// prefix ("Claude") when both could match — break-on-first-hit otherwise
|
|
2781
|
+
// routes "@Claude分身" to Claude.
|
|
2782
|
+
const sortedEntries = [...botEntries].sort((a, b) => (b.botName?.length ?? 0) - (a.botName?.length ?? 0));
|
|
2783
|
+
const selfAliases = new Set(botEntries
|
|
2784
|
+
.filter(entry => entry.larkAppId === appId)
|
|
2785
|
+
.flatMap(entry => [entry.botName, entry.cliId])
|
|
2786
|
+
.filter((name) => !!name)
|
|
2787
|
+
.map(name => name.toLowerCase()));
|
|
2788
|
+
for (const entry of sortedEntries) {
|
|
2789
|
+
if (!entry.botName || entry.larkAppId === appId)
|
|
2645
2790
|
continue;
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2791
|
+
const names = [entry.botName, entry.cliId]
|
|
2792
|
+
.filter((name) => !!name && !selfAliases.has(name.toLowerCase()));
|
|
2793
|
+
for (const name of names) {
|
|
2794
|
+
const escName = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
2795
|
+
// Boundary: lookbehind blocks only ASCII word chars (so `user@Claude`
|
|
2796
|
+
// is rejected but `看看@CoCo` is accepted — CJK prefix is normal in
|
|
2797
|
+
// Chinese text). Lookahead blocks any Unicode letter/digit so
|
|
2798
|
+
// `@Claude2` doesn't match name "Claude" and `@Claude分身好的` doesn't
|
|
2799
|
+
// either-half-match.
|
|
2800
|
+
const re = new RegExp(`(?<![A-Za-z0-9_])@${escName}(?![\\p{L}\\p{N}_])`, 'iu');
|
|
2801
|
+
if (!re.test(text))
|
|
2802
|
+
continue;
|
|
2803
|
+
// Lark open_id is per-app scoped. Use sender-scoped id from cross-ref
|
|
2804
|
+
// only — falling back to entry.botOpenId would feed Lark a wrong-scope
|
|
2805
|
+
// id (target's self-scoped) and the API would reject it. Skip + warn
|
|
2806
|
+
// so the missing cross-ref is observable instead of silently dropped.
|
|
2807
|
+
const senderScopedId = crossRef[entry.botName];
|
|
2808
|
+
if (!senderScopedId) {
|
|
2809
|
+
console.error(`[botmux send] no cross-ref entry for "${entry.botName}" in app ${appId}, skipping auto-mention (cross-ref populates after the sender app first sees the target bot)`);
|
|
2810
|
+
break;
|
|
2811
|
+
}
|
|
2812
|
+
if (alreadyMentioned.has(senderScopedId))
|
|
2813
|
+
break;
|
|
2814
|
+
mentions.push({ open_id: senderScopedId, name: entry.botName });
|
|
2815
|
+
alreadyMentioned.add(senderScopedId);
|
|
2653
2816
|
break;
|
|
2654
2817
|
}
|
|
2655
|
-
if (alreadyMentioned.has(senderScopedId))
|
|
2656
|
-
break;
|
|
2657
|
-
mentions.push({ open_id: senderScopedId, name: entry.botName });
|
|
2658
|
-
alreadyMentioned.add(senderScopedId);
|
|
2659
|
-
break;
|
|
2660
2818
|
}
|
|
2661
2819
|
}
|
|
2662
2820
|
}
|
|
2663
2821
|
catch { /* best-effort */ }
|
|
2822
|
+
const explicitKnownBotMention = hasKnownBotMention(text, mentions, botEntries, crossRef, appId);
|
|
2823
|
+
const knownBotOpenIds = knownBotOpenIdsFromCrossRef(crossRef, botEntries, appId);
|
|
2824
|
+
// --no-mention 显式不 @ 任何人 → 连 footer 的"发送给/cc"寻址 <at> 也清空,
|
|
2825
|
+
// 否则 footer 仍会 @ 人,与 --no-mention 语义和"未@任何人"输出自相矛盾
|
|
2826
|
+
// (Codex review P2)。--top-level 同样无特定收件人。
|
|
2827
|
+
const footerAddressing = (sendTopLevel || noMention)
|
|
2828
|
+
? { sendTo: undefined, cc: [] }
|
|
2829
|
+
: buildFooterAddressing(s, {
|
|
2830
|
+
isOncall: !!oncallEntry,
|
|
2831
|
+
hasExplicitBotMention: explicitKnownBotMention,
|
|
2832
|
+
knownBotOpenIds,
|
|
2833
|
+
});
|
|
2664
2834
|
// Decide: interactive card (renders markdown) vs. post (plain text).
|
|
2665
2835
|
// Explicit --card / --text wins; otherwise auto-detect markdown syntax.
|
|
2666
2836
|
const useCard = forceCard || (!forceText && hasMarkdown(text));
|
|
@@ -2695,12 +2865,9 @@ async function cmdSend(rest) {
|
|
|
2695
2865
|
return `<at id=${openId}></at>`;
|
|
2696
2866
|
});
|
|
2697
2867
|
}
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
trailingAts.push(`<at id=${m.open_id}></at>`);
|
|
2702
|
-
if (trailingAts.length > 0)
|
|
2703
|
-
md = md ? `${md}\n\n${trailingAts.join(' ')}` : trailingAts.join(' ');
|
|
2868
|
+
// Non-inlined mentions are no longer dangled as a trailing @ block at the
|
|
2869
|
+
// body bottom — they're consolidated onto the footer `发送给:` line below
|
|
2870
|
+
// (human addressee first, then explicit targets). See orderedFooterRecipients.
|
|
2704
2871
|
// Inline images into the markdown via . If caller used an
|
|
2705
2872
|
// `` placeholder, substitute by 0-based index; any remaining
|
|
2706
2873
|
// images get appended at the end so they flow with the text.
|
|
@@ -2724,31 +2891,43 @@ async function cmdSend(rest) {
|
|
|
2724
2891
|
const elements = mdWithImages ? buildCardBodyElements(mdWithImages) : [];
|
|
2725
2892
|
// Footer: de-emphasized markdown (v2 dropped the `note` tag). Use small
|
|
2726
2893
|
// text size + grey font tag so it reads like a footnote below the hr.
|
|
2727
|
-
// Oncall groups
|
|
2728
|
-
//
|
|
2729
|
-
|
|
2730
|
-
//
|
|
2731
|
-
//
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
if (
|
|
2736
|
-
footerParts.push(
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2894
|
+
// Oncall groups usually address whoever triggered this turn (may not be
|
|
2895
|
+
// the session owner). Bot recipients are filtered out so footer chrome
|
|
2896
|
+
// cannot accidentally wake a sibling bot.
|
|
2897
|
+
// Brand segment honours this bot's configured brandLabel (unset →
|
|
2898
|
+
// default botmux, '' → suppressed, else custom). Same resolver/rule as
|
|
2899
|
+
// the daemon's card builders so both send paths render identically.
|
|
2900
|
+
const footerParts = [];
|
|
2901
|
+
const brandSeg = brandFooterSegment(resolveBrandLabel(appId));
|
|
2902
|
+
if (brandSeg)
|
|
2903
|
+
footerParts.push(brandSeg);
|
|
2904
|
+
// All real mentions land on one footer line: human addressee first, then
|
|
2905
|
+
// explicit @ targets (incl. handoff bots), then cc. Ids already inlined in
|
|
2906
|
+
// the body prose are skipped. Top-level publish keeps sendTo empty.
|
|
2907
|
+
const footerRecipients = orderedFooterRecipients({
|
|
2908
|
+
sendTo: footerAddressing.sendTo,
|
|
2909
|
+
mentionIds: mentions.map(m => m.open_id),
|
|
2910
|
+
cc: footerAddressing.cc,
|
|
2911
|
+
inlinedIds: usedIds,
|
|
2745
2912
|
});
|
|
2913
|
+
if (footerRecipients.length > 0) {
|
|
2914
|
+
footerParts.push(`发送给:${footerRecipients.map(id => `<at id=${id}></at>`).join(' ')}`);
|
|
2915
|
+
}
|
|
2916
|
+
// Empty brand + no recipients → no footer at all (skip the orphan HR).
|
|
2917
|
+
if (footerParts.length > 0) {
|
|
2918
|
+
elements.push({ tag: 'hr' });
|
|
2919
|
+
elements.push({
|
|
2920
|
+
tag: 'markdown',
|
|
2921
|
+
text_size: 'notation_small_v2',
|
|
2922
|
+
content: `<font color='grey'>${footerParts.join(' · ')}</font>`,
|
|
2923
|
+
});
|
|
2924
|
+
}
|
|
2746
2925
|
const cardJson = JSON.stringify({
|
|
2747
2926
|
schema: '2.0',
|
|
2748
2927
|
config: { update_multi: true },
|
|
2749
2928
|
body: { direction: 'vertical', elements },
|
|
2750
2929
|
});
|
|
2751
|
-
messageId = await
|
|
2930
|
+
messageId = await dispatchPrimary(cardJson, 'interactive');
|
|
2752
2931
|
}
|
|
2753
2932
|
else {
|
|
2754
2933
|
// Plain-text path: build post content, paragraph per line.
|
|
@@ -2772,38 +2951,31 @@ async function cmdSend(rest) {
|
|
|
2772
2951
|
}) : [];
|
|
2773
2952
|
for (const key of imageKeys)
|
|
2774
2953
|
postContent.push([{ tag: 'img', image_key: key }]);
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
const
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
// publish has no specific recipient — skip addressing entirely.
|
|
2792
|
-
const addressing = sendTopLevel
|
|
2793
|
-
? { sendTo: undefined, cc: [] }
|
|
2794
|
-
: buildFooterAddressing(s, oncallEntry);
|
|
2795
|
-
if (addressing.sendTo || addressing.cc.length > 0) {
|
|
2954
|
+
// Footer: mirror the card layout — all real mentions go on one
|
|
2955
|
+
// `发送给:` line (human addressee first, then explicit targets, then cc),
|
|
2956
|
+
// separated from the body by a blank paragraph. Ids already inlined in the
|
|
2957
|
+
// body prose are skipped. Top-level publish keeps sendTo empty.
|
|
2958
|
+
const inlinedIds = new Set();
|
|
2959
|
+
for (const para of postContent)
|
|
2960
|
+
for (const n of para)
|
|
2961
|
+
if (n.tag === 'at')
|
|
2962
|
+
inlinedIds.add(n.user_id);
|
|
2963
|
+
const footerRecipients = orderedFooterRecipients({
|
|
2964
|
+
sendTo: footerAddressing.sendTo,
|
|
2965
|
+
mentionIds: mentions.map(m => m.open_id),
|
|
2966
|
+
cc: footerAddressing.cc,
|
|
2967
|
+
inlinedIds,
|
|
2968
|
+
});
|
|
2969
|
+
if (footerRecipients.length > 0) {
|
|
2796
2970
|
if (postContent.length > 0)
|
|
2797
2971
|
postContent.push([{ tag: 'text', text: '' }]);
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
postContent.push([{ tag: 'text', text: 'cc:' }, ...addressing.cc.map(id => ({ tag: 'at', user_id: id }))]);
|
|
2803
|
-
}
|
|
2972
|
+
postContent.push([
|
|
2973
|
+
{ tag: 'text', text: '发送给:' },
|
|
2974
|
+
...footerRecipients.map(id => ({ tag: 'at', user_id: id })),
|
|
2975
|
+
]);
|
|
2804
2976
|
}
|
|
2805
2977
|
const postJson = JSON.stringify({ zh_cn: { title: '', content: postContent } });
|
|
2806
|
-
messageId = await
|
|
2978
|
+
messageId = await dispatchPrimary(postJson, 'post');
|
|
2807
2979
|
}
|
|
2808
2980
|
// Bridge fallback marker — append-only jsonl per session. The worker
|
|
2809
2981
|
// gates its non-adopt transcript-driven fallback on whether any send
|
|
@@ -2834,7 +3006,17 @@ async function cmdSend(rest) {
|
|
|
2834
3006
|
// 不需要 botmux 自己再写本地 signal 文件做转发。outgoing 消息里 @BotName /
|
|
2835
3007
|
// --mention 的 open_id 解析(在上方 mentions 数组里完成)仍然必要,它让
|
|
2836
3008
|
// Lark 在消息里渲染真正的 @at 元素,从而触发对方 bot 的 WS 事件投递。
|
|
2837
|
-
|
|
3009
|
+
const atSummary = mentions.length > 0
|
|
3010
|
+
? `@${mentions.map(m => m.name || m.open_id).join(',')}`
|
|
3011
|
+
: '未@任何人';
|
|
3012
|
+
console.error(`✓ 已发送 ${messageId} | ${primaryQuotedId ? `引用 ${primaryQuotedId}` : '未引用'} | ${atSummary}`);
|
|
3013
|
+
console.log(JSON.stringify({
|
|
3014
|
+
success: true,
|
|
3015
|
+
messageId,
|
|
3016
|
+
sessionId: sid,
|
|
3017
|
+
quotedMessageId: primaryQuotedId,
|
|
3018
|
+
mentioned: mentions.map(m => ({ open_id: m.open_id, name: m.name })),
|
|
3019
|
+
}));
|
|
2838
3020
|
}
|
|
2839
3021
|
catch (err) {
|
|
2840
3022
|
console.error(`发送失败: ${err.message}`);
|
|
@@ -3016,6 +3198,307 @@ botmux create-group — 用一组机器人新建飞书群
|
|
|
3016
3198
|
}
|
|
3017
3199
|
}
|
|
3018
3200
|
// ─── Bots subcommand ─────────────────────────────────────────────────────────
|
|
3201
|
+
// ─── botmux ask v0.1.7 ───────────────────────────────────────────────────────
|
|
3202
|
+
//
|
|
3203
|
+
// CLI agent inside a botmux-spawned session calls `botmux ask buttons
|
|
3204
|
+
// --options "..." "<prompt>"`. Daemon sends a Lark card; user clicks; CLI
|
|
3205
|
+
// process unblocks with the selected key (or exit 124 on timeout, exit 3 if
|
|
3206
|
+
// the daemon dies). See /tmp/botmux-ask.md (or design memory).
|
|
3207
|
+
/**
|
|
3208
|
+
* postAsk: 找到 daemon → POST /api/asks → 返回 AskResult。
|
|
3209
|
+
* 连接失败 / HTTP 错误时抛出带 exitCode 属性的 Error:
|
|
3210
|
+
* - exitCode=3:daemon 不可达或 HTTP 非 400
|
|
3211
|
+
* - exitCode=2:400 + no_approvers
|
|
3212
|
+
*/
|
|
3213
|
+
async function postAsk(body) {
|
|
3214
|
+
const larkAppId = body.larkAppId;
|
|
3215
|
+
const daemon = findDaemon(larkAppId);
|
|
3216
|
+
if (!daemon) {
|
|
3217
|
+
const err = new Error(`botmux ask: 找不到 daemon (larkAppId=${larkAppId})。daemon 已停?exit 3.`);
|
|
3218
|
+
err.exitCode = 3;
|
|
3219
|
+
throw err;
|
|
3220
|
+
}
|
|
3221
|
+
let res;
|
|
3222
|
+
try {
|
|
3223
|
+
res = await fetch(`http://127.0.0.1:${daemon.ipcPort}/api/asks`, {
|
|
3224
|
+
method: 'POST',
|
|
3225
|
+
headers: { 'content-type': 'application/json' },
|
|
3226
|
+
body: JSON.stringify(body),
|
|
3227
|
+
// No client-side timeout — broker enforces `timeoutMs` and will respond
|
|
3228
|
+
// with `kind:'timedOut'` so this fetch always settles.
|
|
3229
|
+
});
|
|
3230
|
+
}
|
|
3231
|
+
catch (fetchErr) {
|
|
3232
|
+
const msg = fetchErr instanceof Error ? fetchErr.message : String(fetchErr);
|
|
3233
|
+
const err = new Error(`botmux ask: 无法连接 daemon (port=${daemon.ipcPort}): ${msg}`);
|
|
3234
|
+
err.exitCode = 3;
|
|
3235
|
+
throw err;
|
|
3236
|
+
}
|
|
3237
|
+
if (!res.ok) {
|
|
3238
|
+
let errBody = '';
|
|
3239
|
+
try {
|
|
3240
|
+
errBody = (await res.text()).slice(0, 200);
|
|
3241
|
+
}
|
|
3242
|
+
catch { /* */ }
|
|
3243
|
+
if (res.status === 400 && /no_approvers/.test(errBody)) {
|
|
3244
|
+
const err = new Error('botmux ask: 当前会话没有可批准者(session.owner 不在 bot.allowedUsers 里,且 --approver 未指定)');
|
|
3245
|
+
err.exitCode = 2;
|
|
3246
|
+
throw err;
|
|
3247
|
+
}
|
|
3248
|
+
const err = new Error(`botmux ask: daemon HTTP ${res.status}: ${errBody}`);
|
|
3249
|
+
err.exitCode = 3;
|
|
3250
|
+
throw err;
|
|
3251
|
+
}
|
|
3252
|
+
try {
|
|
3253
|
+
return (await res.json());
|
|
3254
|
+
}
|
|
3255
|
+
catch (jsonErr) {
|
|
3256
|
+
const err = new Error(`botmux ask: daemon 返回非 JSON: ${jsonErr}`);
|
|
3257
|
+
err.exitCode = 3;
|
|
3258
|
+
throw err;
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
async function cmdAsk(sub, rest) {
|
|
3262
|
+
// Workflow-subagent safety gate (same posture as cmdSend): a CLI running
|
|
3263
|
+
// inside a workflow subagent (Slice F) must not surface chat UI. Workflow
|
|
3264
|
+
// approvals belong in humanGate / decision nodes so the choice is part of
|
|
3265
|
+
// the run's event log; an ad-hoc `botmux ask` would bypass that audit
|
|
3266
|
+
// trail entirely.
|
|
3267
|
+
if (process.env.BOTMUX_WORKFLOW === '1') {
|
|
3268
|
+
const runId = process.env.BOTMUX_WORKFLOW_RUN_ID ?? '?';
|
|
3269
|
+
const nodeId = process.env.BOTMUX_WORKFLOW_NODE_ID ?? '?';
|
|
3270
|
+
console.error(`botmux ask refused inside workflow subagent (run=${runId} node=${nodeId}).\n` +
|
|
3271
|
+
`Workflow subagents must surface approvals via humanGate / decision nodes\n` +
|
|
3272
|
+
`so the resolution is recorded in the run's event log; ask would bypass it.`);
|
|
3273
|
+
process.exit(2);
|
|
3274
|
+
}
|
|
3275
|
+
// Only `buttons` shipped in v0.1.7. The bare alias (`botmux ask --options`)
|
|
3276
|
+
// routes here with sub='' — accept it and behave identically. `ask text` /
|
|
3277
|
+
// `ask confirm` are reserved for later versions.
|
|
3278
|
+
if (sub && sub !== 'buttons') {
|
|
3279
|
+
console.error(`botmux ask: 未知 subcommand "${sub}"(v0.1.7 仅支持 \`buttons\` 或省略)`);
|
|
3280
|
+
process.exit(2);
|
|
3281
|
+
}
|
|
3282
|
+
const { findMissingAskEnv, parseAskOptions, parseAskTimeoutSeconds, AskArgsError } = await import('./core/ask-args.js');
|
|
3283
|
+
const { toLegacySelected } = await import('./core/ask-types.js');
|
|
3284
|
+
const missing = findMissingAskEnv(process.env);
|
|
3285
|
+
if (missing) {
|
|
3286
|
+
console.error(`botmux ask: 缺少必需环境变量 ${missing}。` +
|
|
3287
|
+
` 请在 botmux daemon spawn 的 CLI 会话内运行。`);
|
|
3288
|
+
process.exit(2);
|
|
3289
|
+
}
|
|
3290
|
+
const optionsRaw = argValue(rest, '--options');
|
|
3291
|
+
const timeoutRaw = argValue(rest, '--timeout');
|
|
3292
|
+
const useJson = rest.includes('--json');
|
|
3293
|
+
const approverArgs = argValues(rest, '--approver');
|
|
3294
|
+
const positionalArgs = positionals(rest, ['--json']);
|
|
3295
|
+
let options;
|
|
3296
|
+
let timeoutMs;
|
|
3297
|
+
try {
|
|
3298
|
+
options = parseAskOptions(optionsRaw);
|
|
3299
|
+
timeoutMs = parseAskTimeoutSeconds(timeoutRaw);
|
|
3300
|
+
}
|
|
3301
|
+
catch (err) {
|
|
3302
|
+
if (err instanceof AskArgsError) {
|
|
3303
|
+
console.error(`botmux ask: ${err.message}`);
|
|
3304
|
+
process.exit(2);
|
|
3305
|
+
}
|
|
3306
|
+
throw err;
|
|
3307
|
+
}
|
|
3308
|
+
const prompt = positionalArgs.join(' ').trim();
|
|
3309
|
+
if (!prompt) {
|
|
3310
|
+
console.error('botmux ask: 缺少 prompt。用法: botmux ask buttons --options "yes,no" "继续发版吗?"');
|
|
3311
|
+
process.exit(2);
|
|
3312
|
+
}
|
|
3313
|
+
const larkAppId = process.env.BOTMUX_LARK_APP_ID;
|
|
3314
|
+
const body = {
|
|
3315
|
+
sessionId: process.env.BOTMUX_SESSION_ID,
|
|
3316
|
+
chatId: process.env.BOTMUX_CHAT_ID,
|
|
3317
|
+
larkAppId,
|
|
3318
|
+
rootMessageId: process.env.BOTMUX_ROOT_MESSAGE_ID || null,
|
|
3319
|
+
options,
|
|
3320
|
+
prompt,
|
|
3321
|
+
timeoutMs,
|
|
3322
|
+
approvers: approverArgs,
|
|
3323
|
+
};
|
|
3324
|
+
let result;
|
|
3325
|
+
try {
|
|
3326
|
+
result = await postAsk(body);
|
|
3327
|
+
}
|
|
3328
|
+
catch (err) {
|
|
3329
|
+
const code = err.exitCode ?? 3;
|
|
3330
|
+
console.error(err.message);
|
|
3331
|
+
process.exit(code);
|
|
3332
|
+
}
|
|
3333
|
+
// result.kind==='answered' 时用 toLegacySelected 取回旧的 string(单问单选)
|
|
3334
|
+
const selected = toLegacySelected(result);
|
|
3335
|
+
if (useJson) {
|
|
3336
|
+
const out = {
|
|
3337
|
+
selected,
|
|
3338
|
+
answers: result.kind === 'answered' ? result.answers : null,
|
|
3339
|
+
by: result.kind === 'answered' ? result.by : null,
|
|
3340
|
+
comment: null,
|
|
3341
|
+
timedOut: result.kind === 'timedOut',
|
|
3342
|
+
};
|
|
3343
|
+
process.stdout.write(JSON.stringify(out) + '\n');
|
|
3344
|
+
}
|
|
3345
|
+
else if (result.kind === 'answered') {
|
|
3346
|
+
// 非 JSON 模式:输出 selected key(单问单选),多选/多问输出空字符串
|
|
3347
|
+
process.stdout.write((selected ?? '') + '\n');
|
|
3348
|
+
}
|
|
3349
|
+
switch (result.kind) {
|
|
3350
|
+
case 'answered':
|
|
3351
|
+
process.exit(0);
|
|
3352
|
+
case 'timedOut':
|
|
3353
|
+
console.error(`botmux ask: 超时(${timeoutMs / 1000}s),无回复`);
|
|
3354
|
+
process.exit(124);
|
|
3355
|
+
case 'invalidated':
|
|
3356
|
+
console.error(`botmux ask: 已失效 (${result.reason})`);
|
|
3357
|
+
process.exit(3);
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
// ─── botmux hook <cliId> ──────────────────────────────────────────────────────
|
|
3361
|
+
//
|
|
3362
|
+
// hook 模式:各 CLI hook 配置调用 `botmux hook <cliId>`,stdin 注入 hook payload,
|
|
3363
|
+
// 本命令解析问题 → POST /api/asks → 等结果 → 写 directive 到 stdout。
|
|
3364
|
+
// 任何失败(daemon 不可达、env 缺失、解析错误)均输出 passthrough directive 并 exit 0,
|
|
3365
|
+
// 绝不挂死,保证 CLI 可以继续原生终端提问。
|
|
3366
|
+
/**
|
|
3367
|
+
* runHook: hook 命令的纯业务逻辑,接受已解析的 payload/env/postAskFn,
|
|
3368
|
+
* 返回应写到 stdout 的字符串。通过依赖注入使单元测试无需真实 daemon/env。
|
|
3369
|
+
*
|
|
3370
|
+
* @param payload 已经 JSON.parse 的 hook payload 对象
|
|
3371
|
+
* @param env 包含 BOTMUX_* 环境变量的字典
|
|
3372
|
+
* @param postAskFn 替代真实 postAsk 的可注入函数(测试用)
|
|
3373
|
+
* @param cliId CLI 适配器 ID
|
|
3374
|
+
* @param resolveAdoptRouteFn 可选:替代真实 adopt 路由解析的注入函数(测试用);
|
|
3375
|
+
* 缺省时使用真实 resolveAdoptRoute(查祖先 PID → daemon)
|
|
3376
|
+
* @returns { stdout: string } 应写到 stdout 的内容
|
|
3377
|
+
*/
|
|
3378
|
+
export async function runHook(payload, env, postAskFn, cliId, resolveAdoptRouteFn) {
|
|
3379
|
+
const { getHookAdapter } = await import('./core/ask-hook/registry.js');
|
|
3380
|
+
// 未知 cliId → 无 adapter,输出空字符串静默放行
|
|
3381
|
+
const adapter = getHookAdapter(cliId);
|
|
3382
|
+
if (!adapter) {
|
|
3383
|
+
return { stdout: '' };
|
|
3384
|
+
}
|
|
3385
|
+
// Workflow-subagent 安全门:workflow 子 agent 内直接 passthrough
|
|
3386
|
+
if (env.BOTMUX_WORKFLOW === '1') {
|
|
3387
|
+
return { stdout: adapter.passthrough(payload) };
|
|
3388
|
+
}
|
|
3389
|
+
// 解析问题:非 askUserQuestion 类事件 → passthrough 放行
|
|
3390
|
+
const parsed = adapter.parseQuestions(payload);
|
|
3391
|
+
if (!parsed) {
|
|
3392
|
+
return { stdout: adapter.passthrough(payload) };
|
|
3393
|
+
}
|
|
3394
|
+
// 检查必需的 BOTMUX_* env
|
|
3395
|
+
const sessionId = env.BOTMUX_SESSION_ID;
|
|
3396
|
+
const chatId = env.BOTMUX_CHAT_ID;
|
|
3397
|
+
const larkAppId = env.BOTMUX_LARK_APP_ID;
|
|
3398
|
+
// 路由变量:优先用 env,env 缺失时尝试 adopt 路由
|
|
3399
|
+
let routeSessionId = sessionId;
|
|
3400
|
+
let routeChatId = chatId;
|
|
3401
|
+
let routeLarkAppId = larkAppId;
|
|
3402
|
+
let routeRoot = env.BOTMUX_ROOT_MESSAGE_ID || null;
|
|
3403
|
+
if (!sessionId || !chatId || !larkAppId) {
|
|
3404
|
+
// env 缺失 → 尝试通过祖先 PID 匹配在线 adopt 会话
|
|
3405
|
+
const resolver = resolveAdoptRouteFn ?? (() => {
|
|
3406
|
+
// 延迟 import 避免冷启动开销
|
|
3407
|
+
return import('./adapters/adopt-route.js').then(({ resolveAdoptRoute, queryAdoptSession }) => resolveAdoptRoute({
|
|
3408
|
+
startPid: process.pid,
|
|
3409
|
+
listDaemons: listOnlineDaemons,
|
|
3410
|
+
queryDaemon: queryAdoptSession,
|
|
3411
|
+
}));
|
|
3412
|
+
});
|
|
3413
|
+
let adopt = null;
|
|
3414
|
+
try {
|
|
3415
|
+
adopt = await resolver();
|
|
3416
|
+
}
|
|
3417
|
+
catch {
|
|
3418
|
+
// 解析失败 → 视作真非 botmux 会话,passthrough 放行
|
|
3419
|
+
}
|
|
3420
|
+
if (!adopt) {
|
|
3421
|
+
// 真非 botmux 会话 → passthrough 放行
|
|
3422
|
+
return { stdout: adapter.passthrough(payload) };
|
|
3423
|
+
}
|
|
3424
|
+
// adopt 命中 → 使用 adopt 路由信息
|
|
3425
|
+
routeSessionId = adopt.sessionId;
|
|
3426
|
+
routeChatId = adopt.chatId;
|
|
3427
|
+
routeLarkAppId = adopt.larkAppId;
|
|
3428
|
+
routeRoot = adopt.rootMessageId;
|
|
3429
|
+
}
|
|
3430
|
+
// 解析 timeoutMs:默认 1 小时,可由 BOTMUX_ASK_TIMEOUT_MS 覆盖
|
|
3431
|
+
const DEFAULT_TIMEOUT_MS = 3_600_000;
|
|
3432
|
+
let timeoutMs = DEFAULT_TIMEOUT_MS;
|
|
3433
|
+
const timeoutEnv = env.BOTMUX_ASK_TIMEOUT_MS;
|
|
3434
|
+
if (timeoutEnv) {
|
|
3435
|
+
const parsed_timeout = parseInt(timeoutEnv, 10);
|
|
3436
|
+
if (Number.isInteger(parsed_timeout) && parsed_timeout > 0) {
|
|
3437
|
+
timeoutMs = parsed_timeout;
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3440
|
+
const body = {
|
|
3441
|
+
sessionId: routeSessionId,
|
|
3442
|
+
chatId: routeChatId,
|
|
3443
|
+
larkAppId: routeLarkAppId,
|
|
3444
|
+
rootMessageId: routeRoot,
|
|
3445
|
+
questions: parsed.questions,
|
|
3446
|
+
timeoutMs,
|
|
3447
|
+
approvers: [],
|
|
3448
|
+
};
|
|
3449
|
+
let result;
|
|
3450
|
+
try {
|
|
3451
|
+
result = await postAskFn(body);
|
|
3452
|
+
}
|
|
3453
|
+
catch {
|
|
3454
|
+
// 任何失败(daemon 不可达、HTTP 错误等)→ passthrough 放行
|
|
3455
|
+
return { stdout: adapter.passthrough(payload) };
|
|
3456
|
+
}
|
|
3457
|
+
if (result.kind === 'answered') {
|
|
3458
|
+
return { stdout: adapter.formatAnswer(result.answers, parsed) };
|
|
3459
|
+
}
|
|
3460
|
+
// timedOut / invalidated → passthrough 放行
|
|
3461
|
+
return { stdout: adapter.passthrough(payload) };
|
|
3462
|
+
}
|
|
3463
|
+
/**
|
|
3464
|
+
* cmdHook: `botmux hook <cliId>` 入口。
|
|
3465
|
+
* 读取 stdin 全文 → JSON.parse → runHook → 写 stdout,exit 0。
|
|
3466
|
+
*/
|
|
3467
|
+
async function cmdHook(cliId) {
|
|
3468
|
+
// 读取 stdin 全文
|
|
3469
|
+
let stdinText = '';
|
|
3470
|
+
try {
|
|
3471
|
+
const chunks = [];
|
|
3472
|
+
for await (const chunk of process.stdin) {
|
|
3473
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
3474
|
+
}
|
|
3475
|
+
stdinText = Buffer.concat(chunks).toString('utf-8');
|
|
3476
|
+
}
|
|
3477
|
+
catch {
|
|
3478
|
+
// stdin 读取失败 → 无法处理,静默退出
|
|
3479
|
+
process.exit(0);
|
|
3480
|
+
}
|
|
3481
|
+
// JSON.parse 失败 → 输出空并退出(不挂死)
|
|
3482
|
+
let payload;
|
|
3483
|
+
try {
|
|
3484
|
+
payload = JSON.parse(stdinText);
|
|
3485
|
+
}
|
|
3486
|
+
catch {
|
|
3487
|
+
process.exit(0);
|
|
3488
|
+
}
|
|
3489
|
+
const { getHookAdapter } = await import('./core/ask-hook/registry.js');
|
|
3490
|
+
const adapter = getHookAdapter(cliId);
|
|
3491
|
+
// 未知 cliId → 静默放行
|
|
3492
|
+
if (!adapter) {
|
|
3493
|
+
process.exit(0);
|
|
3494
|
+
}
|
|
3495
|
+
const env = process.env;
|
|
3496
|
+
const result = await runHook(payload, env, postAsk, cliId);
|
|
3497
|
+
if (result.stdout) {
|
|
3498
|
+
console.log(result.stdout);
|
|
3499
|
+
}
|
|
3500
|
+
process.exit(0);
|
|
3501
|
+
}
|
|
3019
3502
|
async function cmdBots(sub, rest) {
|
|
3020
3503
|
process.env.SESSION_DATA_DIR ??= resolveDataDir();
|
|
3021
3504
|
if (sub !== 'list' && sub !== 'ls' && sub !== '') {
|
|
@@ -3244,6 +3727,20 @@ switch (command) {
|
|
|
3244
3727
|
case 'schedule':
|
|
3245
3728
|
await cmdSchedule(process.argv[3] ?? '', process.argv.slice(4));
|
|
3246
3729
|
break;
|
|
3730
|
+
case 'ask': {
|
|
3731
|
+
// `botmux ask buttons --options ...` → sub='buttons', rest=['--options', ...]
|
|
3732
|
+
// `botmux ask --options ...` → sub='', rest=['--options', ...] (bare alias)
|
|
3733
|
+
const { normalizeAskDispatch } = await import('./core/ask-args.js');
|
|
3734
|
+
const { sub, rest } = normalizeAskDispatch(process.argv.slice(3));
|
|
3735
|
+
await cmdAsk(sub, rest);
|
|
3736
|
+
break;
|
|
3737
|
+
}
|
|
3738
|
+
case 'hook': {
|
|
3739
|
+
// `botmux hook <cliId>` — hook 客户端,stdin 读 payload,stdout 写 directive
|
|
3740
|
+
const cliId = process.argv[3] ?? '';
|
|
3741
|
+
await cmdHook(cliId);
|
|
3742
|
+
break;
|
|
3743
|
+
}
|
|
3247
3744
|
case 'workflow': {
|
|
3248
3745
|
const { cmdWorkflow } = await import('./cli/workflow.js');
|
|
3249
3746
|
await cmdWorkflow(process.argv[3] ?? '', process.argv.slice(4));
|