botmux 2.107.0 → 2.108.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +17 -14
- package/README.md +19 -14
- package/dist/adapters/backend/sandbox.d.ts +3 -1
- package/dist/adapters/backend/sandbox.d.ts.map +1 -1
- package/dist/adapters/backend/sandbox.js +19 -3
- package/dist/adapters/backend/sandbox.js.map +1 -1
- package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
- package/dist/adapters/backend/tmux-backend.js +16 -1
- package/dist/adapters/backend/tmux-backend.js.map +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.d.ts +24 -15
- package/dist/adapters/backend/tmux-pipe-backend.d.ts.map +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.js +75 -43
- package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
- package/dist/adapters/cli/genius.d.ts.map +1 -1
- package/dist/adapters/cli/genius.js +9 -15
- package/dist/adapters/cli/genius.js.map +1 -1
- package/dist/adapters/cli/grok.d.ts +73 -0
- package/dist/adapters/cli/grok.d.ts.map +1 -0
- package/dist/adapters/cli/grok.js +298 -0
- package/dist/adapters/cli/grok.js.map +1 -0
- package/dist/adapters/cli/oh-my-pi.d.ts.map +1 -1
- package/dist/adapters/cli/oh-my-pi.js +133 -12
- package/dist/adapters/cli/oh-my-pi.js.map +1 -1
- package/dist/adapters/cli/registry.d.ts +2 -1
- package/dist/adapters/cli/registry.d.ts.map +1 -1
- package/dist/adapters/cli/registry.js +3 -1
- package/dist/adapters/cli/registry.js.map +1 -1
- package/dist/adapters/cli/shared-hints.d.ts +8 -4
- package/dist/adapters/cli/shared-hints.d.ts.map +1 -1
- package/dist/adapters/cli/shared-hints.js +8 -4
- package/dist/adapters/cli/shared-hints.js.map +1 -1
- package/dist/adapters/cli/types.d.ts +18 -16
- package/dist/adapters/cli/types.d.ts.map +1 -1
- package/dist/adapters/hook-installer.d.ts +5 -4
- package/dist/adapters/hook-installer.d.ts.map +1 -1
- package/dist/adapters/hook-installer.js +42 -0
- package/dist/adapters/hook-installer.js.map +1 -1
- package/dist/cli/send-dispatch.d.ts +21 -0
- package/dist/cli/send-dispatch.d.ts.map +1 -1
- package/dist/cli/send-dispatch.js +159 -0
- package/dist/cli/send-dispatch.js.map +1 -1
- package/dist/cli/session-list-liveness.d.ts +22 -0
- package/dist/cli/session-list-liveness.d.ts.map +1 -0
- package/dist/cli/session-list-liveness.js +22 -0
- package/dist/cli/session-list-liveness.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +182 -33
- package/dist/cli.js.map +1 -1
- package/dist/core/ask-types.d.ts +4 -0
- package/dist/core/ask-types.d.ts.map +1 -1
- package/dist/core/ask-types.js +10 -0
- package/dist/core/ask-types.js.map +1 -1
- package/dist/core/cost-calculator.d.ts.map +1 -1
- package/dist/core/cost-calculator.js +35 -13
- package/dist/core/cost-calculator.js.map +1 -1
- package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
- package/dist/core/dashboard-ipc-server.js +89 -16
- package/dist/core/dashboard-ipc-server.js.map +1 -1
- package/dist/core/dashboard-rows.d.ts.map +1 -1
- package/dist/core/dashboard-rows.js +6 -2
- package/dist/core/dashboard-rows.js.map +1 -1
- package/dist/core/dashboard-url.d.ts +6 -5
- package/dist/core/dashboard-url.d.ts.map +1 -1
- package/dist/core/dashboard-url.js +11 -8
- package/dist/core/dashboard-url.js.map +1 -1
- package/dist/core/maintenance.d.ts +11 -8
- package/dist/core/maintenance.d.ts.map +1 -1
- package/dist/core/maintenance.js +45 -20
- package/dist/core/maintenance.js.map +1 -1
- package/dist/core/per-bot-env.d.ts.map +1 -1
- package/dist/core/per-bot-env.js +4 -0
- package/dist/core/per-bot-env.js.map +1 -1
- package/dist/core/session-discovery.d.ts.map +1 -1
- package/dist/core/session-discovery.js +4 -0
- package/dist/core/session-discovery.js.map +1 -1
- package/dist/core/skills/cli-admin-command.d.ts.map +1 -1
- package/dist/core/skills/cli-admin-command.js +8 -2
- package/dist/core/skills/cli-admin-command.js.map +1 -1
- package/dist/core/skills/im-command.d.ts.map +1 -1
- package/dist/core/skills/im-command.js +3 -1
- package/dist/core/skills/im-command.js.map +1 -1
- package/dist/core/skills/sources.d.ts +33 -1
- package/dist/core/skills/sources.d.ts.map +1 -1
- package/dist/core/skills/sources.js +97 -0
- package/dist/core/skills/sources.js.map +1 -1
- package/dist/core/skills/types.d.ts +8 -0
- package/dist/core/skills/types.d.ts.map +1 -1
- package/dist/core/terminal-url.d.ts.map +1 -1
- package/dist/core/terminal-url.js +12 -2
- package/dist/core/terminal-url.js.map +1 -1
- package/dist/core/terminal-write-auth.d.ts +34 -0
- package/dist/core/terminal-write-auth.d.ts.map +1 -0
- package/dist/core/terminal-write-auth.js +76 -0
- package/dist/core/terminal-write-auth.js.map +1 -0
- package/dist/core/tmux-recovery.d.ts +11 -0
- package/dist/core/tmux-recovery.d.ts.map +1 -0
- package/dist/core/tmux-recovery.js +19 -0
- package/dist/core/tmux-recovery.js.map +1 -0
- package/dist/core/types.d.ts +10 -4
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/update-check.d.ts +2 -2
- package/dist/core/update-check.js +2 -2
- package/dist/core/worker-pool.d.ts +3 -0
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +70 -3
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +15 -8
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/auth.d.ts +1 -1
- package/dist/dashboard/auth.d.ts.map +1 -1
- package/dist/dashboard/auth.js +7 -2
- package/dist/dashboard/auth.js.map +1 -1
- package/dist/dashboard/bot-payload.d.ts +3 -0
- package/dist/dashboard/bot-payload.d.ts.map +1 -1
- package/dist/dashboard/bot-payload.js +3 -0
- package/dist/dashboard/bot-payload.js.map +1 -1
- package/dist/dashboard/connector-api.d.ts.map +1 -1
- package/dist/dashboard/connector-api.js +39 -13
- package/dist/dashboard/connector-api.js.map +1 -1
- package/dist/dashboard/history-senders.d.ts +37 -0
- package/dist/dashboard/history-senders.d.ts.map +1 -0
- package/dist/dashboard/history-senders.js +60 -0
- package/dist/dashboard/history-senders.js.map +1 -0
- package/dist/dashboard/roles-batch.d.ts +31 -0
- package/dist/dashboard/roles-batch.d.ts.map +1 -0
- package/dist/dashboard/roles-batch.js +87 -0
- package/dist/dashboard/roles-batch.js.map +1 -0
- package/dist/dashboard/settings-card-model.d.ts +4 -2
- package/dist/dashboard/settings-card-model.d.ts.map +1 -1
- package/dist/dashboard/settings-card-model.js +13 -5
- package/dist/dashboard/settings-card-model.js.map +1 -1
- package/dist/dashboard/settings-write-applier.d.ts +4 -1
- package/dist/dashboard/settings-write-applier.d.ts.map +1 -1
- package/dist/dashboard/settings-write-applier.js +6 -1
- package/dist/dashboard/settings-write-applier.js.map +1 -1
- package/dist/dashboard/skill-install-request.d.ts +4 -0
- package/dist/dashboard/skill-install-request.d.ts.map +1 -1
- package/dist/dashboard/skill-install-request.js +11 -1
- package/dist/dashboard/skill-install-request.js.map +1 -1
- package/dist/dashboard/trigger-api.d.ts +9 -1
- package/dist/dashboard/trigger-api.d.ts.map +1 -1
- package/dist/dashboard/trigger-api.js +48 -10
- package/dist/dashboard/trigger-api.js.map +1 -1
- package/dist/dashboard/web/app.d.ts.map +1 -1
- package/dist/dashboard/web/app.js +286 -271
- package/dist/dashboard/web/app.js.map +1 -1
- package/dist/dashboard/web/bot-defaults-page.d.ts +26 -0
- package/dist/dashboard/web/bot-defaults-page.d.ts.map +1 -1
- package/dist/dashboard/web/bot-defaults-page.js +1408 -12
- package/dist/dashboard/web/bot-defaults-page.js.map +1 -1
- package/dist/dashboard/web/bot-defaults.d.ts +91 -4
- package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
- package/dist/dashboard/web/bot-defaults.js +43 -2141
- package/dist/dashboard/web/bot-defaults.js.map +1 -1
- package/dist/dashboard/web/bot-onboarding.d.ts +6 -1
- package/dist/dashboard/web/bot-onboarding.d.ts.map +1 -1
- package/dist/dashboard/web/bot-onboarding.js +261 -306
- package/dist/dashboard/web/bot-onboarding.js.map +1 -1
- package/dist/dashboard/web/connectors-page.d.ts +4 -0
- package/dist/dashboard/web/connectors-page.d.ts.map +1 -1
- package/dist/dashboard/web/connectors-page.js +228 -48
- package/dist/dashboard/web/connectors-page.js.map +1 -1
- package/dist/dashboard/web/dashboard-api.d.ts +9 -0
- package/dist/dashboard/web/dashboard-api.d.ts.map +1 -0
- package/dist/dashboard/web/dashboard-api.js +18 -0
- package/dist/dashboard/web/dashboard-api.js.map +1 -0
- package/dist/dashboard/web/dashboard-components.d.ts +92 -0
- package/dist/dashboard/web/dashboard-components.d.ts.map +1 -0
- package/dist/dashboard/web/dashboard-components.js +250 -0
- package/dist/dashboard/web/dashboard-components.js.map +1 -0
- package/dist/dashboard/web/dashboard-routes.d.ts.map +1 -1
- package/dist/dashboard/web/dashboard-routes.js +30 -183
- package/dist/dashboard/web/dashboard-routes.js.map +1 -1
- package/dist/dashboard/web/floating-scrollbars.d.ts +2 -0
- package/dist/dashboard/web/floating-scrollbars.d.ts.map +1 -0
- package/dist/dashboard/web/floating-scrollbars.js +264 -0
- package/dist/dashboard/web/floating-scrollbars.js.map +1 -0
- package/dist/dashboard/web/groups-api.d.ts +36 -0
- package/dist/dashboard/web/groups-api.d.ts.map +1 -0
- package/dist/dashboard/web/groups-api.js +44 -0
- package/dist/dashboard/web/groups-api.js.map +1 -0
- package/dist/dashboard/web/groups-page.d.ts.map +1 -1
- package/dist/dashboard/web/groups-page.js +746 -15
- package/dist/dashboard/web/groups-page.js.map +1 -1
- package/dist/dashboard/web/groups.d.ts +51 -18
- package/dist/dashboard/web/groups.d.ts.map +1 -1
- package/dist/dashboard/web/groups.js +145 -1096
- package/dist/dashboard/web/groups.js.map +1 -1
- package/dist/dashboard/web/i18n.d.ts.map +1 -1
- package/dist/dashboard/web/i18n.js +545 -255
- package/dist/dashboard/web/i18n.js.map +1 -1
- package/dist/dashboard/web/insights-page.d.ts.map +1 -1
- package/dist/dashboard/web/insights-page.js +1047 -14
- package/dist/dashboard/web/insights-page.js.map +1 -1
- package/dist/dashboard/web/insights.d.ts +177 -7
- package/dist/dashboard/web/insights.d.ts.map +1 -1
- package/dist/dashboard/web/insights.js +187 -1926
- package/dist/dashboard/web/insights.js.map +1 -1
- package/dist/dashboard/web/legacy-workflow-page.d.ts +3 -0
- package/dist/dashboard/web/legacy-workflow-page.d.ts.map +1 -0
- package/dist/dashboard/web/legacy-workflow-page.js +717 -0
- package/dist/dashboard/web/legacy-workflow-page.js.map +1 -0
- package/dist/dashboard/web/monitor-room.d.ts.map +1 -1
- package/dist/dashboard/web/monitor-room.js +10 -8
- package/dist/dashboard/web/monitor-room.js.map +1 -1
- package/dist/dashboard/web/monitoring-page.d.ts.map +1 -1
- package/dist/dashboard/web/monitoring-page.js +188 -14
- package/dist/dashboard/web/monitoring-page.js.map +1 -1
- package/dist/dashboard/web/office-page.d.ts.map +1 -1
- package/dist/dashboard/web/office-page.js +11 -23
- package/dist/dashboard/web/office-page.js.map +1 -1
- package/dist/dashboard/web/overview-page.d.ts.map +1 -1
- package/dist/dashboard/web/overview-page.js +141 -147
- package/dist/dashboard/web/overview-page.js.map +1 -1
- package/dist/dashboard/web/overview.d.ts +0 -1
- package/dist/dashboard/web/overview.d.ts.map +1 -1
- package/dist/dashboard/web/overview.js +3 -275
- package/dist/dashboard/web/overview.js.map +1 -1
- package/dist/dashboard/web/preferences.d.ts +1 -6
- package/dist/dashboard/web/preferences.d.ts.map +1 -1
- package/dist/dashboard/web/preferences.js +0 -21
- package/dist/dashboard/web/preferences.js.map +1 -1
- package/dist/dashboard/web/role-batch.d.ts +9 -0
- package/dist/dashboard/web/role-batch.d.ts.map +1 -0
- package/dist/dashboard/web/role-batch.js +42 -0
- package/dist/dashboard/web/role-batch.js.map +1 -0
- package/dist/dashboard/web/roles-page.d.ts.map +1 -1
- package/dist/dashboard/web/roles-page.js +538 -13
- package/dist/dashboard/web/roles-page.js.map +1 -1
- package/dist/dashboard/web/roles.d.ts +98 -1
- package/dist/dashboard/web/roles.d.ts.map +1 -1
- package/dist/dashboard/web/roles.js +117 -903
- package/dist/dashboard/web/roles.js.map +1 -1
- package/dist/dashboard/web/schedules-page.d.ts.map +1 -1
- package/dist/dashboard/web/schedules-page.js +82 -24
- package/dist/dashboard/web/schedules-page.js.map +1 -1
- package/dist/dashboard/web/sessions-kanban.d.ts +67 -0
- package/dist/dashboard/web/sessions-kanban.d.ts.map +1 -0
- package/dist/dashboard/web/sessions-kanban.js +481 -0
- package/dist/dashboard/web/sessions-kanban.js.map +1 -0
- package/dist/dashboard/web/sessions-page.d.ts +4 -0
- package/dist/dashboard/web/sessions-page.d.ts.map +1 -1
- package/dist/dashboard/web/sessions-page.js +1633 -18
- package/dist/dashboard/web/sessions-page.js.map +1 -1
- package/dist/dashboard/web/sessions.d.ts +45 -4
- package/dist/dashboard/web/sessions.d.ts.map +1 -1
- package/dist/dashboard/web/sessions.js +53 -2535
- package/dist/dashboard/web/sessions.js.map +1 -1
- package/dist/dashboard/web/settings-page.d.ts +0 -2
- package/dist/dashboard/web/settings-page.d.ts.map +1 -1
- package/dist/dashboard/web/settings-page.js +127 -117
- package/dist/dashboard/web/settings-page.js.map +1 -1
- package/dist/dashboard/web/skills-page.d.ts +10 -1
- package/dist/dashboard/web/skills-page.d.ts.map +1 -1
- package/dist/dashboard/web/skills-page.js +235 -93
- package/dist/dashboard/web/skills-page.js.map +1 -1
- package/dist/dashboard/web/team-federation-page.d.ts.map +1 -1
- package/dist/dashboard/web/team-federation-page.js +446 -28
- package/dist/dashboard/web/team-federation-page.js.map +1 -1
- package/dist/dashboard/web/team-federation.d.ts +91 -2
- package/dist/dashboard/web/team-federation.d.ts.map +1 -1
- package/dist/dashboard/web/team-federation.js +87 -474
- package/dist/dashboard/web/team-federation.js.map +1 -1
- package/dist/dashboard/web/theme-menu.d.ts +1 -0
- package/dist/dashboard/web/theme-menu.d.ts.map +1 -1
- package/dist/dashboard/web/theme-menu.js +79 -20
- package/dist/dashboard/web/theme-menu.js.map +1 -1
- package/dist/dashboard/web/ui.d.ts +1 -2
- package/dist/dashboard/web/ui.d.ts.map +1 -1
- package/dist/dashboard/web/ui.js +8 -20
- package/dist/dashboard/web/ui.js.map +1 -1
- package/dist/dashboard/web/v3-api.d.ts +16 -0
- package/dist/dashboard/web/v3-api.d.ts.map +1 -0
- package/dist/dashboard/web/v3-api.js +21 -0
- package/dist/dashboard/web/v3-api.js.map +1 -0
- package/dist/dashboard/web/v3-components.d.ts +2 -0
- package/dist/dashboard/web/v3-components.d.ts.map +1 -0
- package/dist/dashboard/web/v3-components.js +290 -0
- package/dist/dashboard/web/v3-components.js.map +1 -0
- package/dist/dashboard/web/v3-model.d.ts +80 -0
- package/dist/dashboard/web/v3-model.d.ts.map +1 -0
- package/dist/dashboard/web/v3-model.js +240 -0
- package/dist/dashboard/web/v3-model.js.map +1 -0
- package/dist/dashboard/web/v3-page.d.ts.map +1 -1
- package/dist/dashboard/web/v3-page.js +2 -22
- package/dist/dashboard/web/v3-page.js.map +1 -1
- package/dist/dashboard/web/v3-terminal.d.ts +14 -0
- package/dist/dashboard/web/v3-terminal.d.ts.map +1 -1
- package/dist/dashboard/web/v3-terminal.js +31 -19
- package/dist/dashboard/web/v3-terminal.js.map +1 -1
- package/dist/dashboard/web/webhook-logs-page.d.ts +16 -0
- package/dist/dashboard/web/webhook-logs-page.d.ts.map +1 -0
- package/dist/dashboard/web/webhook-logs-page.js +195 -0
- package/dist/dashboard/web/webhook-logs-page.js.map +1 -0
- package/dist/dashboard/web/whiteboards-page.d.ts.map +1 -1
- package/dist/dashboard/web/whiteboards-page.js +18 -23
- package/dist/dashboard/web/whiteboards-page.js.map +1 -1
- package/dist/dashboard/web/workflow-version-switch.d.ts +8 -0
- package/dist/dashboard/web/workflow-version-switch.d.ts.map +1 -0
- package/dist/dashboard/web/workflow-version-switch.js +9 -0
- package/dist/dashboard/web/workflow-version-switch.js.map +1 -0
- package/dist/dashboard/web/workflows.d.ts +272 -1
- package/dist/dashboard/web/workflows.d.ts.map +1 -1
- package/dist/dashboard/web/workflows.js +211 -1397
- package/dist/dashboard/web/workflows.js.map +1 -1
- package/dist/dashboard/webhook-routes.d.ts.map +1 -1
- package/dist/dashboard/webhook-routes.js +92 -39
- package/dist/dashboard/webhook-routes.js.map +1 -1
- package/dist/dashboard-web/app.js +1 -65
- package/dist/dashboard-web/brand-logo.png +0 -0
- package/dist/dashboard-web/chunks/bot-defaults-page-HSQ6HR3R.js +1 -0
- package/dist/dashboard-web/chunks/chunk-6TPODDDC.js +1 -0
- package/dist/dashboard-web/chunks/chunk-73EJP2MO.js +1 -0
- package/dist/dashboard-web/chunks/chunk-BRTZFELS.js +1 -0
- package/dist/dashboard-web/chunks/chunk-EVK77ASG.js +1 -0
- package/dist/dashboard-web/chunks/chunk-R3FXBFQH.js +1 -0
- package/dist/dashboard-web/chunks/chunk-VRLL7SNU.js +1 -0
- package/dist/dashboard-web/chunks/chunk-XCPSHD3G.js +37 -0
- package/dist/dashboard-web/chunks/chunk-XJ7BGPJH.js +1 -0
- package/dist/dashboard-web/chunks/chunk-YVRWYGXR.js +1 -0
- package/dist/dashboard-web/chunks/chunk-ZJING6FY.js +66 -0
- package/dist/dashboard-web/chunks/connectors-page-ODUA5UCW.js +1 -0
- package/dist/dashboard-web/chunks/groups-page-OYX5M6NK.js +8 -0
- package/dist/dashboard-web/chunks/insights-page-YCQO6OCR.js +22 -0
- package/dist/dashboard-web/chunks/legacy-workflow-page-XJAUNONP.js +2 -0
- package/dist/dashboard-web/chunks/{monitor-room-RFX4HPS5.js → monitor-room-WTZNYAFV.js} +17 -15
- package/dist/dashboard-web/chunks/monitoring-page-R6LJUK7G.js +2 -0
- package/dist/dashboard-web/chunks/office-page-4OXK3GNR.js +1 -0
- package/dist/dashboard-web/chunks/overview-page-4MVEJY6U.js +1 -0
- package/dist/dashboard-web/chunks/roles-page-NQDW27VV.js +1 -0
- package/dist/dashboard-web/chunks/schedules-page-JRNKYHOX.js +1 -0
- package/dist/dashboard-web/chunks/sessions-page-LF7UVI7B.js +3 -0
- package/dist/dashboard-web/chunks/settings-page-WGW7KQQS.js +2 -0
- package/dist/dashboard-web/chunks/skills-page-AMD4XP3T.js +3 -0
- package/dist/dashboard-web/chunks/team-federation-page-SYG3K4GG.js +1 -0
- package/dist/dashboard-web/chunks/v3-page-6NFFWNUK.js +94 -0
- package/dist/dashboard-web/chunks/whiteboards-page-FBRUK2AS.js +1 -0
- package/dist/dashboard-web/favicon.png +0 -0
- package/dist/dashboard-web/index.html +5 -69
- package/dist/dashboard-web/style.css +18835 -7211
- package/dist/dashboard.js +150 -32
- package/dist/dashboard.js.map +1 -1
- package/dist/global-config.d.ts +2 -2
- package/dist/global-config.d.ts.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +3 -0
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +3 -0
- package/dist/i18n/zh.js.map +1 -1
- package/dist/im/lark/card-builder.d.ts +7 -5
- package/dist/im/lark/card-builder.d.ts.map +1 -1
- package/dist/im/lark/card-builder.js +10 -7
- 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 +25 -1
- package/dist/im/lark/card-handler.js.map +1 -1
- package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
- package/dist/im/lark/event-dispatcher.js +84 -0
- package/dist/im/lark/event-dispatcher.js.map +1 -1
- package/dist/platform/binding.d.ts +9 -0
- package/dist/platform/binding.d.ts.map +1 -1
- package/dist/platform/binding.js +14 -0
- package/dist/platform/binding.js.map +1 -1
- package/dist/platform/tunnel-client.d.ts.map +1 -1
- package/dist/platform/tunnel-client.js +9 -0
- package/dist/platform/tunnel-client.js.map +1 -1
- package/dist/services/bot-config-store.d.ts +1 -1
- package/dist/services/bot-config-store.d.ts.map +1 -1
- package/dist/services/bot-config-store.js +11 -5
- package/dist/services/bot-config-store.js.map +1 -1
- package/dist/services/file-bridge-path.d.ts +8 -0
- package/dist/services/file-bridge-path.d.ts.map +1 -0
- package/dist/services/file-bridge-path.js +75 -0
- package/dist/services/file-bridge-path.js.map +1 -0
- package/dist/services/grok-paths.d.ts +27 -0
- package/dist/services/grok-paths.d.ts.map +1 -0
- package/dist/services/grok-paths.js +106 -0
- package/dist/services/grok-paths.js.map +1 -0
- package/dist/services/grok-transcript.d.ts +67 -0
- package/dist/services/grok-transcript.d.ts.map +1 -0
- package/dist/services/grok-transcript.js +630 -0
- package/dist/services/grok-transcript.js.map +1 -0
- package/dist/services/hermes-session-filter.d.ts +28 -0
- package/dist/services/hermes-session-filter.d.ts.map +1 -0
- package/dist/services/hermes-session-filter.js +53 -0
- package/dist/services/hermes-session-filter.js.map +1 -0
- package/dist/services/hermes-transcript.d.ts +3 -0
- package/dist/services/hermes-transcript.d.ts.map +1 -1
- package/dist/services/hermes-transcript.js +20 -2
- package/dist/services/hermes-transcript.js.map +1 -1
- package/dist/services/resumable-session-discovery.d.ts +3 -0
- package/dist/services/resumable-session-discovery.d.ts.map +1 -1
- package/dist/services/resumable-session-discovery.js +6 -1
- package/dist/services/resumable-session-discovery.js.map +1 -1
- package/dist/services/skill-registry-store.d.ts +9 -0
- package/dist/services/skill-registry-store.d.ts.map +1 -1
- package/dist/services/skill-registry-store.js +304 -1
- package/dist/services/skill-registry-store.js.map +1 -1
- package/dist/services/structured-bridge-clis.d.ts +29 -0
- package/dist/services/structured-bridge-clis.d.ts.map +1 -0
- package/dist/services/structured-bridge-clis.js +76 -0
- package/dist/services/structured-bridge-clis.js.map +1 -0
- package/dist/services/trigger-log-store.d.ts +59 -0
- package/dist/services/trigger-log-store.d.ts.map +1 -1
- package/dist/services/trigger-log-store.js +81 -10
- package/dist/services/trigger-log-store.js.map +1 -1
- package/dist/services/usage-ledger.d.ts +4 -1
- package/dist/services/usage-ledger.d.ts.map +1 -1
- package/dist/services/usage-ledger.js +48 -10
- package/dist/services/usage-ledger.js.map +1 -1
- package/dist/services/webhook-audit.d.ts +10 -0
- package/dist/services/webhook-audit.d.ts.map +1 -0
- package/dist/services/webhook-audit.js +116 -0
- package/dist/services/webhook-audit.js.map +1 -0
- package/dist/setup/bot-config-editor.d.ts.map +1 -1
- package/dist/setup/bot-config-editor.js +2 -0
- package/dist/setup/bot-config-editor.js.map +1 -1
- package/dist/setup/ensure-tmux.d.ts +26 -12
- package/dist/setup/ensure-tmux.d.ts.map +1 -1
- package/dist/setup/ensure-tmux.js +85 -18
- package/dist/setup/ensure-tmux.js.map +1 -1
- package/dist/skills/definitions.d.ts.map +1 -1
- package/dist/skills/definitions.js +14 -1
- package/dist/skills/definitions.js.map +1 -1
- package/dist/skills/injection-mode.d.ts +16 -0
- package/dist/skills/injection-mode.d.ts.map +1 -1
- package/dist/skills/injection-mode.js +25 -0
- package/dist/skills/injection-mode.js.map +1 -1
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/global-install.d.ts +22 -0
- package/dist/utils/global-install.d.ts.map +1 -0
- package/dist/utils/global-install.js +106 -0
- package/dist/utils/global-install.js.map +1 -0
- package/dist/utils/install-diagnostics.d.ts +1 -1
- package/dist/utils/install-diagnostics.d.ts.map +1 -1
- package/dist/utils/install-diagnostics.js +6 -4
- package/dist/utils/install-diagnostics.js.map +1 -1
- package/dist/utils/install-info.d.ts +2 -0
- package/dist/utils/install-info.d.ts.map +1 -1
- package/dist/utils/install-info.js +10 -4
- package/dist/utils/install-info.js.map +1 -1
- package/dist/worker.js +293 -172
- package/dist/worker.js.map +1 -1
- package/dist/workflows/events/payloads.d.ts +10 -10
- package/dist/workflows/events/schema.d.ts +28 -28
- package/package.json +8 -7
- package/dist/dashboard/web/connectors.d.ts +0 -2
- package/dist/dashboard/web/connectors.d.ts.map +0 -1
- package/dist/dashboard/web/connectors.js +0 -2
- package/dist/dashboard/web/connectors.js.map +0 -1
- package/dist/dashboard/web/office.d.ts +0 -2
- package/dist/dashboard/web/office.d.ts.map +0 -1
- package/dist/dashboard/web/office.js +0 -2
- package/dist/dashboard/web/office.js.map +0 -1
- package/dist/dashboard/web/schedules.d.ts +0 -2
- package/dist/dashboard/web/schedules.d.ts.map +0 -1
- package/dist/dashboard/web/schedules.js +0 -2
- package/dist/dashboard/web/schedules.js.map +0 -1
- package/dist/dashboard/web/settings.d.ts +0 -2
- package/dist/dashboard/web/settings.d.ts.map +0 -1
- package/dist/dashboard/web/settings.js +0 -2
- package/dist/dashboard/web/settings.js.map +0 -1
- package/dist/dashboard/web/skills.d.ts +0 -2
- package/dist/dashboard/web/skills.d.ts.map +0 -1
- package/dist/dashboard/web/skills.js +0 -2
- package/dist/dashboard/web/skills.js.map +0 -1
- package/dist/dashboard/web/skin-intro.d.ts +0 -4
- package/dist/dashboard/web/skin-intro.d.ts.map +0 -1
- package/dist/dashboard/web/skin-intro.js +0 -49
- package/dist/dashboard/web/skin-intro.js.map +0 -1
- package/dist/dashboard/web/v3.d.ts +0 -3
- package/dist/dashboard/web/v3.d.ts.map +0 -1
- package/dist/dashboard/web/v3.js +0 -539
- package/dist/dashboard/web/v3.js.map +0 -1
- package/dist/dashboard/web/whiteboards.d.ts +0 -2
- package/dist/dashboard/web/whiteboards.d.ts.map +0 -1
- package/dist/dashboard/web/whiteboards.js +0 -2
- package/dist/dashboard/web/whiteboards.js.map +0 -1
- package/dist/dashboard-web/chunks/bot-defaults-page-OCIHPGEU.js +0 -438
- package/dist/dashboard-web/chunks/chunk-3N2PPQVD.js +0 -1
- package/dist/dashboard-web/chunks/chunk-3UJYRR2Q.js +0 -37
- package/dist/dashboard-web/chunks/chunk-5E3MJJL4.js +0 -1
- package/dist/dashboard-web/chunks/chunk-7SZRL6FY.js +0 -1
- package/dist/dashboard-web/chunks/chunk-JTSE5VP6.js +0 -54
- package/dist/dashboard-web/chunks/chunk-LXIK7OUJ.js +0 -1
- package/dist/dashboard-web/chunks/chunk-PE6W3C2C.js +0 -1
- package/dist/dashboard-web/chunks/chunk-QW54DWU3.js +0 -325
- package/dist/dashboard-web/chunks/connectors-IGETYWCP.js +0 -1
- package/dist/dashboard-web/chunks/groups-page-MYBS4ZYJ.js +0 -207
- package/dist/dashboard-web/chunks/insights-page-UPA32V2S.js +0 -309
- package/dist/dashboard-web/chunks/monitoring-page-7T3BESZ2.js +0 -1
- package/dist/dashboard-web/chunks/office-page-6DXYOG55.js +0 -1
- package/dist/dashboard-web/chunks/overview-page-LJSBCQW4.js +0 -1
- package/dist/dashboard-web/chunks/roles-page-ESURTVQH.js +0 -98
- package/dist/dashboard-web/chunks/schedules-LML2ISOT.js +0 -1
- package/dist/dashboard-web/chunks/sessions-YB73XOTU.js +0 -1
- package/dist/dashboard-web/chunks/sessions-page-MCO5QR7Z.js +0 -1
- package/dist/dashboard-web/chunks/settings-page-OSDKNVS3.js +0 -2
- package/dist/dashboard-web/chunks/skills-page-PXBZJ6P6.js +0 -2
- package/dist/dashboard-web/chunks/team-federation-page-TTPVHFPX.js +0 -10
- package/dist/dashboard-web/chunks/v3-page-FVPUL665.js +0 -149
- package/dist/dashboard-web/chunks/whiteboards-page-HLCTYSSV.js +0 -1
- package/dist/dashboard-web/chunks/workflows-FCPM4S4D.js +0 -184
- package/dist/dashboard-web/skins/bluearchive-hero.webp +0 -0
- package/dist/dashboard-web/skins/dragonball-goku.webp +0 -0
- package/dist/dashboard-web/skins/dragonball-wukong.webp +0 -0
- package/dist/dashboard-web/skins/genshin-breeze.webp +0 -0
- package/dist/dashboard-web/skins/ikun-hero.webp +0 -0
- package/dist/dashboard-web/skins/prts-priestess.webp +0 -0
- package/dist/dashboard-web/skins/zzz-hero.webp +0 -0
- package/dist/dashboard-web/skins/zzz-pattern.svg +0 -25
|
@@ -1,118 +1,19 @@
|
|
|
1
|
-
// Bot Defaults page: per-bot configuration for "default oncall mode on new
|
|
2
|
-
// chats". Strictly per-bot (no chat × bot matrix here — that lives in the
|
|
3
|
-
// Groups & Bots tab). Saving here only affects NEW group chats first observed
|
|
4
|
-
// after the save; existing chats are left alone, and chats already auto-bound
|
|
5
|
-
// once stay user-controlled.
|
|
6
1
|
import { store } from './store.js';
|
|
7
|
-
|
|
8
|
-
import { parseSubstituteTargets, formatSubstituteTargets } from './substitute-targets.js';
|
|
9
|
-
let cache = { bots: [] };
|
|
10
|
-
let loadError = null;
|
|
11
|
-
let cliOptions = [
|
|
2
|
+
export const fallbackCliOptions = [
|
|
12
3
|
{ id: 'claude-code', label: 'Claude' },
|
|
13
4
|
{ id: 'codex', label: 'Codex' },
|
|
14
5
|
{ id: 'traex', label: 'traex' },
|
|
15
6
|
];
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
7
|
+
export const fallbackCliOptionsState = {
|
|
8
|
+
options: fallbackCliOptions,
|
|
9
|
+
ttadkModelDefault: 'glm-5.1',
|
|
10
|
+
ttadkModelSuggestions: [],
|
|
11
|
+
};
|
|
21
12
|
export function displayCliId(bot, sessionFallback) {
|
|
22
13
|
return typeof bot?.cliId === 'string' && bot.cliId ? bot.cliId : sessionFallback;
|
|
23
14
|
}
|
|
24
|
-
// 飞书 Web 登录态刷新 modal:POST /api/feishu-login/start 起扫码流程,轮询
|
|
25
|
-
// /api/feishu-login/status 展示二维码/进度,扫码成功后回调 onSuccess(重试改名)。
|
|
26
|
-
// 机器级单例流程,同一时刻只需一个 overlay。
|
|
27
|
-
function openFeishuLoginModal(onSuccess) {
|
|
28
|
-
document.querySelector('.feishu-login-overlay')?.remove();
|
|
29
|
-
const overlay = document.createElement('div');
|
|
30
|
-
overlay.className = 'feishu-login-overlay';
|
|
31
|
-
overlay.innerHTML = `
|
|
32
|
-
<div class="feishu-login-modal" role="dialog" aria-modal="true">
|
|
33
|
-
<button type="button" class="feishu-login-close" data-close aria-label="${escapeHtml(t('feishuLogin.close'))}">✕</button>
|
|
34
|
-
<h3 class="feishu-login-title">${escapeHtml(t('feishuLogin.title'))}</h3>
|
|
35
|
-
<p class="feishu-login-hint" data-hint>${escapeHtml(t('feishuLogin.starting'))}</p>
|
|
36
|
-
<div class="feishu-login-qr" data-qr></div>
|
|
37
|
-
<div class="feishu-login-actions">
|
|
38
|
-
<button type="button" class="primary" data-retry hidden>${escapeHtml(t('feishuLogin.retry'))}</button>
|
|
39
|
-
</div>
|
|
40
|
-
</div>`;
|
|
41
|
-
document.body.appendChild(overlay);
|
|
42
|
-
const hintEl = overlay.querySelector('[data-hint]');
|
|
43
|
-
const qrEl = overlay.querySelector('[data-qr]');
|
|
44
|
-
const retryBtn = overlay.querySelector('[data-retry]');
|
|
45
|
-
let timer = null;
|
|
46
|
-
const stopTimer = () => { if (timer !== null) {
|
|
47
|
-
window.clearInterval(timer);
|
|
48
|
-
timer = null;
|
|
49
|
-
} };
|
|
50
|
-
const cleanup = () => { stopTimer(); overlay.remove(); };
|
|
51
|
-
overlay.addEventListener('click', (e) => {
|
|
52
|
-
const el = e.target;
|
|
53
|
-
if (el === overlay || el.closest('[data-close]'))
|
|
54
|
-
cleanup();
|
|
55
|
-
});
|
|
56
|
-
function render(login) {
|
|
57
|
-
if (!login)
|
|
58
|
-
return 'active';
|
|
59
|
-
if (login.status === 'awaiting_scan' && login.qrDataUrl) {
|
|
60
|
-
qrEl.innerHTML = `<img class="qr-image" src="${login.qrDataUrl}" alt="${escapeHtml(t('feishuLogin.qrAlt'))}">`;
|
|
61
|
-
hintEl.textContent = login.message || t('feishuLogin.scanHint');
|
|
62
|
-
retryBtn.hidden = true;
|
|
63
|
-
return 'active';
|
|
64
|
-
}
|
|
65
|
-
if (login.status === 'starting') {
|
|
66
|
-
hintEl.textContent = login.message || t('feishuLogin.starting');
|
|
67
|
-
return 'active';
|
|
68
|
-
}
|
|
69
|
-
if (login.status === 'success') {
|
|
70
|
-
stopTimer();
|
|
71
|
-
qrEl.innerHTML = '';
|
|
72
|
-
hintEl.textContent = t('feishuLogin.success');
|
|
73
|
-
window.setTimeout(() => { cleanup(); onSuccess(); }, 900);
|
|
74
|
-
return 'done';
|
|
75
|
-
}
|
|
76
|
-
// failed
|
|
77
|
-
stopTimer();
|
|
78
|
-
qrEl.innerHTML = '';
|
|
79
|
-
hintEl.textContent = t('feishuLogin.failed', { reason: login.message || login.reason || '' });
|
|
80
|
-
retryBtn.hidden = false;
|
|
81
|
-
return 'done';
|
|
82
|
-
}
|
|
83
|
-
async function poll() {
|
|
84
|
-
try {
|
|
85
|
-
const r = await fetch('/api/feishu-login/status');
|
|
86
|
-
const body = await r.json().catch(() => ({}));
|
|
87
|
-
render(body.login);
|
|
88
|
-
}
|
|
89
|
-
catch { /* transient — keep polling */ }
|
|
90
|
-
}
|
|
91
|
-
async function begin() {
|
|
92
|
-
stopTimer();
|
|
93
|
-
hintEl.textContent = t('feishuLogin.starting');
|
|
94
|
-
qrEl.innerHTML = '';
|
|
95
|
-
retryBtn.hidden = true;
|
|
96
|
-
let phase = 'active';
|
|
97
|
-
try {
|
|
98
|
-
const r = await fetch('/api/feishu-login/start', { method: 'POST' });
|
|
99
|
-
const body = await r.json().catch(() => ({}));
|
|
100
|
-
phase = render(body.login);
|
|
101
|
-
}
|
|
102
|
-
catch (e) {
|
|
103
|
-
hintEl.textContent = t('feishuLogin.failed', { reason: e?.message ?? String(e) });
|
|
104
|
-
retryBtn.hidden = false;
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
if (phase === 'active' && timer === null)
|
|
108
|
-
timer = window.setInterval(() => void poll(), 1500);
|
|
109
|
-
}
|
|
110
|
-
retryBtn.addEventListener('click', () => void begin());
|
|
111
|
-
void begin();
|
|
112
|
-
}
|
|
113
|
-
const botProfileRoleCache = new Map();
|
|
114
15
|
/** Fallback for old /api/bots payloads: infer from the bot's recent sessions. */
|
|
115
|
-
function cliIdOf(appId) {
|
|
16
|
+
export function cliIdOf(appId) {
|
|
116
17
|
let best = null;
|
|
117
18
|
for (const s of store.sessions.values()) {
|
|
118
19
|
if (s.larkAppId !== appId || !s.cliId)
|
|
@@ -122,189 +23,64 @@ function cliIdOf(appId) {
|
|
|
122
23
|
}
|
|
123
24
|
return best?.cliId ?? '';
|
|
124
25
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return;
|
|
128
|
-
cliOptionsLoaded = true;
|
|
129
|
-
try {
|
|
130
|
-
const r = await fetch('/api/cli-options');
|
|
131
|
-
const body = await r.json().catch(() => ({}));
|
|
132
|
-
if (r.ok && Array.isArray(body?.options)) {
|
|
133
|
-
cliOptions = body.options.filter((o) => o && typeof o.id === 'string' && typeof o.label === 'string');
|
|
134
|
-
if (typeof body.ttadkModelDefault === 'string' && body.ttadkModelDefault.trim()) {
|
|
135
|
-
ttadkModelDefault = body.ttadkModelDefault.trim();
|
|
136
|
-
}
|
|
137
|
-
if (Array.isArray(body.ttadkModelSuggestions)) {
|
|
138
|
-
ttadkModelSuggestions = body.ttadkModelSuggestions.filter((s) => typeof s === 'string');
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
catch {
|
|
143
|
-
// Keep the static fallback; saving still works for plain claude-code.
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
function agentSelectionKey(bot, sessionFallback) {
|
|
147
|
-
const explicit = typeof bot?.agentSelectionKey === 'string' && bot.agentSelectionKey ? bot.agentSelectionKey : '';
|
|
26
|
+
export function agentSelectionKey(bot, sessionFallback) {
|
|
27
|
+
const explicit = typeof bot.agentSelectionKey === 'string' && bot.agentSelectionKey ? bot.agentSelectionKey : '';
|
|
148
28
|
if (explicit)
|
|
149
29
|
return explicit;
|
|
150
30
|
const cli = displayCliId(bot, sessionFallback);
|
|
151
31
|
return cli || 'claude-code';
|
|
152
32
|
}
|
|
153
|
-
function selectedCliOption(key) {
|
|
154
|
-
return
|
|
155
|
-
}
|
|
156
|
-
// A single resolved-target chip: ✓ green when resolved to a person, ✗ red when
|
|
157
|
-
// the email / id could not be resolved (out of 通讯录可见范围). Shared by the
|
|
158
|
-
// initial render and the post-save refresh.
|
|
159
|
-
function substituteChipHtml(c) {
|
|
160
|
-
const label = (c.name && c.name.trim()) || c.input || '';
|
|
161
|
-
const color = c.ok ? '#16a34a' : '#dc2626';
|
|
162
|
-
const suffix = c.ok ? '' : ` · ${t('botDefaults.substituteUnresolved')}`;
|
|
163
|
-
return `<span style="display:inline-flex;align-items:center;gap:4px;padding:2px 9px;margin:0 6px 6px 0;`
|
|
164
|
-
+ `border-radius:12px;font-size:12px;line-height:1.6;border:1px solid ${color}40;color:${color};background:${color}14">`
|
|
165
|
-
+ `<span>${c.ok ? '✓' : '✗'}</span><span>${escapeHtml(label)}${escapeHtml(suffix)}</span></span>`;
|
|
33
|
+
export function selectedCliOption(options, key) {
|
|
34
|
+
return options.find(o => o.id === key);
|
|
166
35
|
}
|
|
167
|
-
|
|
168
|
-
function substituteChipsHtml(mode) {
|
|
169
|
-
const targets = Array.isArray(mode?.targets) ? mode.targets : [];
|
|
170
|
-
return targets
|
|
171
|
-
.map((tg) => substituteChipHtml({
|
|
172
|
-
ok: true,
|
|
173
|
-
name: (typeof tg?.name === 'string' && tg.name) || tg?.email || tg?.openId || tg?.unionId || tg?.userId,
|
|
174
|
-
input: tg?.email || tg?.openId,
|
|
175
|
-
}))
|
|
176
|
-
.join('');
|
|
177
|
-
}
|
|
178
|
-
// Chips rendered after a save, from the server's per-entry resolution report.
|
|
179
|
-
function substituteChipsFromResolution(resolution) {
|
|
180
|
-
if (!Array.isArray(resolution) || resolution.length === 0)
|
|
181
|
-
return '';
|
|
182
|
-
return resolution
|
|
183
|
-
.map((r) => substituteChipHtml({ ok: r?.ok === true, name: r?.name, input: r?.input }))
|
|
184
|
-
.join('');
|
|
185
|
-
}
|
|
186
|
-
// Re-derive the editor text from the resolution report so a save keeps every
|
|
187
|
-
// line the user typed — resolved ones gain a `# name` annotation, failed ones
|
|
188
|
-
// stay verbatim to be fixed. Empty report (e.g. the "off" button) clears it.
|
|
189
|
-
function editorTextFromResolution(resolution) {
|
|
190
|
-
if (!Array.isArray(resolution))
|
|
191
|
-
return '';
|
|
192
|
-
return resolution
|
|
193
|
-
.map((r) => {
|
|
194
|
-
const input = String(r?.input ?? '').trim();
|
|
195
|
-
if (!input)
|
|
196
|
-
return '';
|
|
197
|
-
return r?.ok && typeof r?.name === 'string' && r.name.trim() ? `${input} # ${r.name.trim()}` : input;
|
|
198
|
-
})
|
|
199
|
-
.filter(Boolean)
|
|
200
|
-
.join('\n');
|
|
201
|
-
}
|
|
202
|
-
function modelSuggestionsForOption(opt) {
|
|
36
|
+
export function modelSuggestionsForOption(opt, cliState) {
|
|
203
37
|
if (opt?.gateway === 'ttadk' && opt.acceptsModel !== false)
|
|
204
|
-
return ttadkModelSuggestions;
|
|
38
|
+
return cliState.ttadkModelSuggestions;
|
|
205
39
|
return [];
|
|
206
40
|
}
|
|
207
|
-
export function
|
|
208
|
-
const key = agentSelectionKey(b, sessionFallback);
|
|
209
|
-
const optHtml = cliOptions
|
|
210
|
-
.map(o => `<option value="${escapeHtml(o.id)}" ${o.id === key ? 'selected' : ''}>${escapeHtml(o.label)}(${escapeHtml(o.id)})</option>`)
|
|
211
|
-
.join('');
|
|
212
|
-
const model = typeof b?.model === 'string' ? b.model : '';
|
|
213
|
-
const suggestions = modelSuggestionsForOption(selectedCliOption(key));
|
|
214
|
-
const disabled = selectedCliOption(key)?.gateway === 'ttadk' && selectedCliOption(key)?.acceptsModel === false;
|
|
215
|
-
// botmux skills 注入方式. `support` decides how the control renders:
|
|
216
|
-
// - 'dynamic' (claude-family, --plugin-dir): disabled, shows 动态注入 as the
|
|
217
|
-
// fixed mode — not configurable.
|
|
218
|
-
// - 'global' (codex-family, global skills dir): prompt/global/off selectable;
|
|
219
|
-
// 动态注入 shown but disabled (hint: this CLI can't do dynamic injection).
|
|
220
|
-
// - 'none' (no skill dir): the whole row is omitted.
|
|
221
|
-
// The selected value is the RESOLVED mode (per-bot override → machine default),
|
|
222
|
-
// which is `prompt` out of the box — so there is no separate "follow" option.
|
|
223
|
-
const siSupport = b?.skillInjectionSupport === 'dynamic' ? 'dynamic' : b?.skillInjectionSupport === 'global' ? 'global' : 'none';
|
|
224
|
-
const siOverride = (b?.skillInjection === 'global' || b?.skillInjection === 'prompt' || b?.skillInjection === 'off') ? b.skillInjection : '';
|
|
225
|
-
const siDefault = (b?.skillInjectionDefault === 'global' || b?.skillInjectionDefault === 'off') ? b.skillInjectionDefault : 'prompt';
|
|
226
|
-
const siResolved = siOverride || siDefault; // 'prompt' | 'global' | 'off'
|
|
227
|
-
const skillRow = siSupport === 'none' ? '' : siSupport === 'dynamic'
|
|
228
|
-
? `<div class="bd-row">
|
|
229
|
-
<label>
|
|
230
|
-
<span>${t('botDefaults.skillInjection')}</span>
|
|
231
|
-
<select data-input="skillInjection" disabled>
|
|
232
|
-
<option value="dynamic" selected>${escapeHtml(t('botDefaults.skillInjectionDynamic'))}</option>
|
|
233
|
-
</select>
|
|
234
|
-
</label>
|
|
235
|
-
<small class="bd-help">${t('botDefaults.skillInjectionHelpDynamic')}</small>
|
|
236
|
-
</div>`
|
|
237
|
-
: `<div class="bd-row">
|
|
238
|
-
<label>
|
|
239
|
-
<span>${t('botDefaults.skillInjection')}</span>
|
|
240
|
-
<select data-input="skillInjection">
|
|
241
|
-
<option value="dynamic" disabled>${escapeHtml(t('botDefaults.skillInjectionDynamicUnsupported'))}</option>
|
|
242
|
-
<option value="prompt" ${siResolved === 'prompt' ? 'selected' : ''}>${escapeHtml(t('botDefaults.skillInjectionPrompt'))}</option>
|
|
243
|
-
<option value="global" ${siResolved === 'global' ? 'selected' : ''}>${escapeHtml(t('botDefaults.skillInjectionGlobal'))}</option>
|
|
244
|
-
<option value="off" ${siResolved === 'off' ? 'selected' : ''}>${escapeHtml(t('botDefaults.skillInjectionOff'))}</option>
|
|
245
|
-
</select>
|
|
246
|
-
</label>
|
|
247
|
-
<small class="bd-help">${t('botDefaults.skillInjectionHelp')}</small>
|
|
248
|
-
<div class="actions">
|
|
249
|
-
<span class="oncall-status" data-skill-injection-status></span>
|
|
250
|
-
</div>
|
|
251
|
-
</div>`;
|
|
252
|
-
return `<section class="bd-section">
|
|
253
|
-
<h3 class="bd-section-title">${t('botDefaults.sectionAgent')}</h3>
|
|
254
|
-
<div class="bd-row">
|
|
255
|
-
<label>
|
|
256
|
-
<span>${t('botDefaults.agentCli')}</span>
|
|
257
|
-
<select data-input="agentCliId">${optHtml}</select>
|
|
258
|
-
</label>
|
|
259
|
-
</div>
|
|
260
|
-
<div class="bd-row">
|
|
261
|
-
<label>
|
|
262
|
-
<span>${t('botDefaults.agentModel')}</span>
|
|
263
|
-
<input type="text" data-input="agentModel" list="agent-model-suggestions-${escapeHtml(b.larkAppId)}"
|
|
264
|
-
placeholder="${escapeHtml(t('botDefaults.agentModelPlaceholder'))}"
|
|
265
|
-
value="${escapeHtml(model)}" ${disabled ? 'disabled' : ''}>
|
|
266
|
-
<datalist id="agent-model-suggestions-${escapeHtml(b.larkAppId)}">
|
|
267
|
-
${suggestions.map(m => `<option value="${escapeHtml(m)}"></option>`).join('')}
|
|
268
|
-
</datalist>
|
|
269
|
-
</label>
|
|
270
|
-
<small class="bd-help">${t('botDefaults.agentHelp')}</small>
|
|
271
|
-
<div class="actions">
|
|
272
|
-
<button type="button" class="primary" data-action="save-agent">${t('botDefaults.agentSave')}</button>
|
|
273
|
-
<span class="oncall-status" data-agent-status></span>
|
|
274
|
-
</div>
|
|
275
|
-
</div>
|
|
276
|
-
${skillRow}
|
|
277
|
-
</section>`;
|
|
278
|
-
}
|
|
279
|
-
async function loadBots() {
|
|
41
|
+
export async function fetchBotDefaults() {
|
|
280
42
|
try {
|
|
281
43
|
const r = await fetch('/api/bots');
|
|
282
44
|
const body = await r.json().catch(() => ({}));
|
|
283
45
|
if (!r.ok) {
|
|
284
|
-
|
|
285
|
-
// hasn't been restarted, so /api/bots isn't registered yet. Surface
|
|
286
|
-
// that instead of throwing — the empty list area is what the user
|
|
287
|
-
// sees as "blank page".
|
|
288
|
-
loadError = body?.error
|
|
46
|
+
const error = body?.error
|
|
289
47
|
? `HTTP ${r.status}: ${body.error}${body.path ? ` (${body.path})` : ''}`
|
|
290
48
|
: `HTTP ${r.status}`;
|
|
291
|
-
|
|
292
|
-
return;
|
|
49
|
+
return { bots: [], error };
|
|
293
50
|
}
|
|
294
51
|
if (!body || !Array.isArray(body.bots)) {
|
|
295
|
-
|
|
296
|
-
cache = { bots: [] };
|
|
297
|
-
return;
|
|
52
|
+
return { bots: [], error: 'unexpected response shape (no `bots` array)' };
|
|
298
53
|
}
|
|
299
|
-
|
|
300
|
-
cache = body;
|
|
54
|
+
return { bots: body.bots, error: null };
|
|
301
55
|
}
|
|
302
56
|
catch (e) {
|
|
303
|
-
|
|
304
|
-
cache = { bots: [] };
|
|
57
|
+
return { bots: [], error: e?.message ?? String(e) };
|
|
305
58
|
}
|
|
306
59
|
}
|
|
307
|
-
function
|
|
60
|
+
export async function fetchCliOptions() {
|
|
61
|
+
try {
|
|
62
|
+
const r = await fetch('/api/cli-options');
|
|
63
|
+
const body = await r.json().catch(() => ({}));
|
|
64
|
+
if (!r.ok || !Array.isArray(body?.options))
|
|
65
|
+
return fallbackCliOptionsState;
|
|
66
|
+
const options = body.options.filter((o) => o && typeof o.id === 'string' && typeof o.label === 'string');
|
|
67
|
+
const ttadkModelDefault = typeof body.ttadkModelDefault === 'string' && body.ttadkModelDefault.trim()
|
|
68
|
+
? body.ttadkModelDefault.trim()
|
|
69
|
+
: fallbackCliOptionsState.ttadkModelDefault;
|
|
70
|
+
const ttadkModelSuggestions = Array.isArray(body.ttadkModelSuggestions)
|
|
71
|
+
? body.ttadkModelSuggestions.filter((s) => typeof s === 'string')
|
|
72
|
+
: [];
|
|
73
|
+
return {
|
|
74
|
+
options: options.length ? options : fallbackCliOptions,
|
|
75
|
+
ttadkModelDefault,
|
|
76
|
+
ttadkModelSuggestions,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return fallbackCliOptionsState;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export function fmtSince(since) {
|
|
308
84
|
if (!since)
|
|
309
85
|
return '—';
|
|
310
86
|
const d = new Date(since);
|
|
@@ -312,1878 +88,4 @@ function fmtSince(since) {
|
|
|
312
88
|
return '—';
|
|
313
89
|
return d.toLocaleString();
|
|
314
90
|
}
|
|
315
|
-
export function wireBotDefaultsPage(root) {
|
|
316
|
-
const listEl = root.querySelector('#bd-list');
|
|
317
|
-
const rosterEl = root.querySelector('#bd-roster');
|
|
318
|
-
const form = root.querySelector('#bd-filters');
|
|
319
|
-
const refreshBtn = root.querySelector('#bd-refresh');
|
|
320
|
-
let disposed = false;
|
|
321
|
-
let readyToRender = false;
|
|
322
|
-
refreshBtn.onclick = async () => {
|
|
323
|
-
refreshBtn.disabled = true;
|
|
324
|
-
try {
|
|
325
|
-
botProfileRoleCache.clear();
|
|
326
|
-
await Promise.all([loadBots(), loadCliOptions()]);
|
|
327
|
-
safeRerender();
|
|
328
|
-
}
|
|
329
|
-
finally {
|
|
330
|
-
if (!disposed)
|
|
331
|
-
refreshBtn.disabled = false;
|
|
332
|
-
}
|
|
333
|
-
};
|
|
334
|
-
// 帮助文字默认折叠成一行;点说明文字本身展开/收起(preventDefault 拦掉
|
|
335
|
-
// label 默认行为,避免一点说明就把开关也切了)。只绑一次,委托不随 rerender 重建。
|
|
336
|
-
const listClickHandler = (e) => {
|
|
337
|
-
const sm = e.target.closest('.toggle-tx small, small.bd-help');
|
|
338
|
-
if (sm) {
|
|
339
|
-
e.preventDefault();
|
|
340
|
-
sm.classList.toggle('open');
|
|
341
|
-
}
|
|
342
|
-
};
|
|
343
|
-
listEl.addEventListener('click', listClickHandler);
|
|
344
|
-
// /api/bots 要逐 daemon 探活,慢——先亮 loading 占住右侧详情区。
|
|
345
|
-
listEl.innerHTML = loadingHtml();
|
|
346
|
-
void (async () => {
|
|
347
|
-
await Promise.all([loadBots(), loadCliOptions()]);
|
|
348
|
-
readyToRender = true;
|
|
349
|
-
safeRerender();
|
|
350
|
-
await loadNameMaps(); // 头像表就绪后重绘,让 /api/bots 这边也出真实头像
|
|
351
|
-
safeRerender();
|
|
352
|
-
})();
|
|
353
|
-
function rerender() {
|
|
354
|
-
const f = new FormData(form);
|
|
355
|
-
const q = (f.get('q') ?? '').toLowerCase();
|
|
356
|
-
const filtered = cache.bots.filter((b) => !q ||
|
|
357
|
-
(b.botName ?? '').toLowerCase().includes(q) ||
|
|
358
|
-
(b.larkAppId ?? '').toLowerCase().includes(q));
|
|
359
|
-
if (loadError) {
|
|
360
|
-
rosterEl.innerHTML = '';
|
|
361
|
-
listEl.innerHTML = `<p class="hint-warn">无法加载 bot 列表:${escapeHtml(loadError)}<br>` +
|
|
362
|
-
`常见原因:dashboard / daemon 进程还在跑旧代码,执行 <code>botmux restart</code> 后刷新。</p>`;
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
if (filtered.length === 0) {
|
|
366
|
-
rosterEl.innerHTML = '';
|
|
367
|
-
listEl.innerHTML = `<p class="empty">${t('botDefaults.empty')}</p>`;
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
if (!selectedAppId || !filtered.some((b) => b.larkAppId === selectedAppId)) {
|
|
371
|
-
selectedAppId = filtered[0].larkAppId;
|
|
372
|
-
}
|
|
373
|
-
rosterEl.innerHTML = filtered.map(renderRosterItem).join('');
|
|
374
|
-
rosterEl.querySelectorAll('.bd-roster-item').forEach(el => {
|
|
375
|
-
el.onclick = () => {
|
|
376
|
-
selectedAppId = el.dataset.appid;
|
|
377
|
-
safeRerender();
|
|
378
|
-
};
|
|
379
|
-
});
|
|
380
|
-
const sel = filtered.find((b) => b.larkAppId === selectedAppId);
|
|
381
|
-
listEl.innerHTML = renderBotCard(sel);
|
|
382
|
-
wireCardHandlers();
|
|
383
|
-
}
|
|
384
|
-
function renderRosterItem(b) {
|
|
385
|
-
const name = b.botName ?? b.larkAppId;
|
|
386
|
-
const cli = displayCliId(b, cliIdOf(b.larkAppId));
|
|
387
|
-
const flag = b.defaultOncall?.enabled
|
|
388
|
-
? `<span class="bd-roster-flag">oncall</span>`
|
|
389
|
-
: '';
|
|
390
|
-
return `<div class="bd-roster-item${b.larkAppId === selectedAppId ? ' on' : ''}" data-appid="${escapeHtml(b.larkAppId)}" role="button" tabindex="0">
|
|
391
|
-
${botAvatarHtml({ name, larkAppId: b.larkAppId, size: 'sm' })}
|
|
392
|
-
<div class="bd-roster-tx">
|
|
393
|
-
<b>${escapeHtml(name)}</b>
|
|
394
|
-
<span>${escapeHtml(cli || b.larkAppId.slice(0, 14))}</span>
|
|
395
|
-
</div>
|
|
396
|
-
${flag}
|
|
397
|
-
</div>`;
|
|
398
|
-
}
|
|
399
|
-
function renderBotCard(b) {
|
|
400
|
-
if (b.error) {
|
|
401
|
-
return `<article class="bd-card bd-profile" data-appid="${escapeHtml(b.larkAppId)}">
|
|
402
|
-
<header class="bd-profile-head">
|
|
403
|
-
${botAvatarHtml({ name: b.botName ?? b.larkAppId, larkAppId: b.larkAppId })}
|
|
404
|
-
<div class="bd-profile-id"><strong>${escapeHtml(b.botName ?? b.larkAppId)}</strong>
|
|
405
|
-
<code>${escapeHtml(b.larkAppId)}</code></div>
|
|
406
|
-
</header>
|
|
407
|
-
<p class="hint-warn-inline">查询失败:${escapeHtml(b.error)}</p>
|
|
408
|
-
</article>`;
|
|
409
|
-
}
|
|
410
|
-
const def = b.defaultOncall ?? { enabled: false, workingDir: '', since: 0 };
|
|
411
|
-
// 默认工作目录模式(三选一,dashboard 互斥):oncall > default > off。
|
|
412
|
-
const wdMode = def.enabled ? 'oncall' : (b.defaultWorkingDir ? 'default' : 'off');
|
|
413
|
-
// 输入框预填「最可能的目录」:defaultWorkingDir 优先,否则 oncall 目录(disable 后仍保留),
|
|
414
|
-
// 便于来回切换模式不用重输。
|
|
415
|
-
const wdDirValue = b.defaultWorkingDir || def.workingDir || '';
|
|
416
|
-
const name = b.botName ?? b.larkAppId;
|
|
417
|
-
const cli = displayCliId(b, cliIdOf(b.larkAppId));
|
|
418
|
-
return `<article class="bd-card bd-profile" data-appid="${escapeHtml(b.larkAppId)}">
|
|
419
|
-
<header class="bd-profile-head">
|
|
420
|
-
${botAvatarHtml({ name, larkAppId: b.larkAppId, dot: 'ok' })}
|
|
421
|
-
<div class="bd-profile-id">
|
|
422
|
-
<span class="bd-name-row" data-name-row>
|
|
423
|
-
<strong data-bot-name>${escapeHtml(name)}</strong>
|
|
424
|
-
<button type="button" class="bd-name-edit" data-action="edit-bot-name"
|
|
425
|
-
title="${escapeHtml(t('botDefaults.renameTitle'))}" aria-label="${escapeHtml(t('botDefaults.renameTitle'))}">
|
|
426
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3a2.85 2.85 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/></svg>
|
|
427
|
-
</button>
|
|
428
|
-
</span>
|
|
429
|
-
<span class="bd-name-editor" data-name-editor hidden>
|
|
430
|
-
<input type="text" class="bd-name-input" data-input="botRename" maxlength="64" value="${escapeHtml(name)}">
|
|
431
|
-
<button type="button" class="primary" data-action="save-bot-name">${t('botDefaults.renameSave')}</button>
|
|
432
|
-
<button type="button" data-action="cancel-bot-name">${t('botDefaults.renameCancel')}</button>
|
|
433
|
-
</span>
|
|
434
|
-
${cli ? `<span class="mate-role">${escapeHtml(cli)}</span>` : ''}
|
|
435
|
-
<code>${escapeHtml(b.larkAppId)}</code>
|
|
436
|
-
<small class="bd-name-status oncall-status" data-name-status></small>
|
|
437
|
-
<button type="button" class="bd-feishu-login" data-action="feishu-login" hidden>${t('feishuLogin.entry')}</button>
|
|
438
|
-
</div>
|
|
439
|
-
<div class="bd-profile-meta bd-meta">
|
|
440
|
-
<small class="bd-meta-ok">● ${t('botDefaults.metaOnline')}</small>
|
|
441
|
-
<small data-oncall-since>${t('botDefaults.lastEnabled')}: ${escapeHtml(fmtSince(def.since ?? 0))}</small>
|
|
442
|
-
<small>${t('botDefaults.autobound', { count: b.autoboundChatCount ?? 0 })}</small>
|
|
443
|
-
</div>
|
|
444
|
-
</header>
|
|
445
|
-
<div class="bd-body bd-grid">
|
|
446
|
-
<section class="bd-tile">
|
|
447
|
-
${renderBotAgentSection(b, cli)}
|
|
448
|
-
<section class="bd-section">
|
|
449
|
-
<h3 class="bd-section-title">${t('botDefaults.sectionWorkingDir')}</h3>
|
|
450
|
-
<div class="bd-row">
|
|
451
|
-
<label>
|
|
452
|
-
<span>${t('botDefaults.workingDirMode')}</span>
|
|
453
|
-
<select data-input="workingDirMode">
|
|
454
|
-
<option value="off" ${wdMode === 'off' ? 'selected' : ''}>${escapeHtml(t('botDefaults.workingDirModeOff'))}</option>
|
|
455
|
-
<option value="default" ${wdMode === 'default' ? 'selected' : ''}>${escapeHtml(t('botDefaults.workingDirModeDefault'))}</option>
|
|
456
|
-
<option value="oncall" ${wdMode === 'oncall' ? 'selected' : ''}>${escapeHtml(t('botDefaults.workingDirModeOncall'))}</option>
|
|
457
|
-
</select>
|
|
458
|
-
</label>
|
|
459
|
-
<small class="bd-help">${t('botDefaults.workingDirModeHelp')}</small>
|
|
460
|
-
</div>
|
|
461
|
-
<div class="bd-row" data-wd-dir-row ${wdMode === 'off' ? 'hidden' : ''}>
|
|
462
|
-
<label>
|
|
463
|
-
<span>${t('botDefaults.workingDirField')}</span>
|
|
464
|
-
<input type="text" data-input="workingDir" placeholder="e.g. /root/iserver/botmux"
|
|
465
|
-
value="${escapeHtml(wdDirValue)}">
|
|
466
|
-
</label>
|
|
467
|
-
</div>
|
|
468
|
-
<label class="toggle-row" data-wd-worktree-row ${wdMode === 'default' ? '' : 'hidden'}>
|
|
469
|
-
<input type="checkbox" data-input="autoWorktree" ${b.defaultWorkingDirAutoWorktree ? 'checked' : ''}>
|
|
470
|
-
<span class="switch" aria-hidden="true"></span>
|
|
471
|
-
<span class="toggle-tx"><strong>${t('botDefaults.autoWorktree')}</strong>
|
|
472
|
-
<small>${t('botDefaults.autoWorktreeHelp')}</small></span>
|
|
473
|
-
</label>
|
|
474
|
-
<div class="actions">
|
|
475
|
-
<button type="button" class="primary" data-action="save-working-dir">${t('botDefaults.save')}</button>
|
|
476
|
-
<span class="oncall-status" data-status></span>
|
|
477
|
-
</div>
|
|
478
|
-
${renderAutoStartControls(b)}
|
|
479
|
-
</section>
|
|
480
|
-
${renderSandboxSection(b)}
|
|
481
|
-
</section>
|
|
482
|
-
<section class="bd-tile">${renderRoleSection(b)}</section>
|
|
483
|
-
<section class="bd-tile">${renderSessionModeSection(b)}${renderSubstituteModeSection(b)}${renderCrossBotSection(b)}${renderSessionCapSection(b)}${renderStartupCommandsSection(b)}${renderLaunchShellSection(b)}${renderEnvSection(b)}</section>
|
|
484
|
-
<section class="bd-tile">${renderCardBehaviorSection(b)}${renderSummaryTriggerSection(b)}${renderBrandSection(b)}</section>
|
|
485
|
-
<section class="bd-tile">${renderGrantSection(b)}</section>
|
|
486
|
-
</div>
|
|
487
|
-
</article>`;
|
|
488
|
-
}
|
|
489
|
-
// Team-level role editor (one role per bot, cross-chat). This is the
|
|
490
|
-
// canonical place to EDIT the team role — the Team page only shows it
|
|
491
|
-
// read-only. The role isn't part of the /api/bots payload, so it's fetched
|
|
492
|
-
// once per bot via GET /api/team/local-bots/{app}/role and cached onto the
|
|
493
|
-
// bot snapshot (b.teamRole) — the page re-renders on every search keystroke,
|
|
494
|
-
// so caching avoids a fetch-per-keystroke storm. undefined = not loaded yet
|
|
495
|
-
// (render disabled + lazy-load); string = loaded (render it directly).
|
|
496
|
-
function renderRoleSection(b) {
|
|
497
|
-
const loaded = typeof b.teamRole === 'string';
|
|
498
|
-
return `<section class="bd-section">
|
|
499
|
-
<h3 class="bd-section-title">${t('botDefaults.sectionRole')}</h3>
|
|
500
|
-
<p class="bd-section-note">${t('botDefaults.roleHelp')}</p>
|
|
501
|
-
<textarea data-input="teamRole" rows="6"
|
|
502
|
-
placeholder="${escapeHtml(t('botDefaults.rolePlaceholder'))}"
|
|
503
|
-
style="width:100%;box-sizing:border-box;font:13px/1.5 ui-monospace,Menlo,monospace;padding:10px"${loaded ? '' : ' disabled'}>${loaded ? escapeHtml(b.teamRole) : ''}</textarea>
|
|
504
|
-
<div class="actions">
|
|
505
|
-
<button type="button" class="primary" data-action="save-role"${loaded ? '' : ' disabled'}>${t('botDefaults.roleSave')}</button>
|
|
506
|
-
<button type="button" data-action="delete-role"${loaded ? '' : ' disabled'}>${t('botDefaults.roleDelete')}</button>
|
|
507
|
-
<span class="oncall-status" data-role-status></span>
|
|
508
|
-
</div>
|
|
509
|
-
<div class="bd-profile-roles" data-profile-roles>
|
|
510
|
-
<h4 class="bd-subsection-title">${t('botDefaults.profileRoles')}</h4>
|
|
511
|
-
<p class="bd-section-note">${t('botDefaults.profileRolesHelp')}</p>
|
|
512
|
-
<div class="bd-profile-role-list" data-profile-role-list>${renderProfileRoleList(b.larkAppId)}</div>
|
|
513
|
-
</div>
|
|
514
|
-
</section>`;
|
|
515
|
-
}
|
|
516
|
-
function renderProfileRoleList(appId) {
|
|
517
|
-
const state = botProfileRoleCache.get(appId);
|
|
518
|
-
if (!state || state.loading)
|
|
519
|
-
return loadingHtml();
|
|
520
|
-
if (state.error)
|
|
521
|
-
return `<p class="hint-warn-inline">${escapeHtml(t('botDefaults.profileRolesLoadFailed', { error: state.error }))}</p>`;
|
|
522
|
-
if (state.items.length === 0)
|
|
523
|
-
return `<p class="empty">${t('botDefaults.profileRolesEmpty')}</p>`;
|
|
524
|
-
return state.items.map(item => `
|
|
525
|
-
<details class="bd-profile-role-entry" data-profile-id="${escapeHtml(item.profileId)}">
|
|
526
|
-
<summary><code>${escapeHtml(item.profileId)}</code></summary>
|
|
527
|
-
<div class="bd-profile-role-content" data-profile-role-body="${escapeHtml(item.profileId)}">
|
|
528
|
-
${renderProfileRoleContent(item)}
|
|
529
|
-
</div>
|
|
530
|
-
</details>
|
|
531
|
-
`).join('');
|
|
532
|
-
}
|
|
533
|
-
function renderProfileRoleContent(item) {
|
|
534
|
-
if (item.loading)
|
|
535
|
-
return loadingHtml();
|
|
536
|
-
if (item.error)
|
|
537
|
-
return `<p class="hint-warn-inline">${escapeHtml(t('botDefaults.profileRoleDetailLoadFailed', { error: item.error }))}</p>`;
|
|
538
|
-
if (!item.loaded)
|
|
539
|
-
return `<p class="empty">${t('botDefaults.profileRoleClickToLoad')}</p>`;
|
|
540
|
-
return `<pre>${escapeHtml(item.content ?? '')}</pre>`;
|
|
541
|
-
}
|
|
542
|
-
// brandLabel is null when unset (→ default botmux), '' when off, else custom.
|
|
543
|
-
// The input shows the configured string ('' for both unset and off); a small
|
|
544
|
-
// state line disambiguates which of the three the bot is currently in.
|
|
545
|
-
function brandStateLabel(brand) {
|
|
546
|
-
if (brand == null)
|
|
547
|
-
return t('botDefaults.brandStateDefault');
|
|
548
|
-
return brand.trim() === '' ? t('botDefaults.brandStateOff') : t('botDefaults.brandStateCustom');
|
|
549
|
-
}
|
|
550
|
-
function renderBrandSection(b) {
|
|
551
|
-
const brand = b.brandLabel ?? null;
|
|
552
|
-
return `<section class="bd-section">
|
|
553
|
-
<h3 class="bd-section-title">${t('botDefaults.sectionBrand')}</h3>
|
|
554
|
-
<div class="bd-row bd-brand">
|
|
555
|
-
<label>
|
|
556
|
-
<span>${t('botDefaults.brandLabel')}</span>
|
|
557
|
-
<input type="text" data-input="brandLabel"
|
|
558
|
-
placeholder="${escapeHtml(t('botDefaults.brandLabelPlaceholder'))}"
|
|
559
|
-
value="${escapeHtml(brand ?? '')}">
|
|
560
|
-
</label>
|
|
561
|
-
<small data-brand-state>${escapeHtml(brandStateLabel(brand))}</small>
|
|
562
|
-
<small class="bd-help">${t('botDefaults.brandLabelHelp')}</small>
|
|
563
|
-
<div class="actions">
|
|
564
|
-
<button type="button" class="primary" data-action="save-brand">${t('botDefaults.brandSave')}</button>
|
|
565
|
-
<button type="button" data-action="reset-brand">${t('botDefaults.brandReset')}</button>
|
|
566
|
-
<span class="oncall-status" data-brand-status></span>
|
|
567
|
-
</div>
|
|
568
|
-
</div>
|
|
569
|
-
</section>`;
|
|
570
|
-
}
|
|
571
|
-
// Per-bot card-behaviour toggles. Each auto-saves on change (no explicit save
|
|
572
|
-
// button — each checkbox PUTs immediately). Two are gated on the streaming-card
|
|
573
|
-
// state: the writable-link toggle is moot WHILE the card is disabled; the
|
|
574
|
-
// status-reactions toggle only matters WHILE the card is disabled (the ✋→✅
|
|
575
|
-
// reactions only appear in card-off sessions), so it's editable only then.
|
|
576
|
-
function renderCardBehaviorSection(b) {
|
|
577
|
-
const disableStreaming = b.disableStreamingCard === true;
|
|
578
|
-
const silentReactions = b.silentTurnReactions === true;
|
|
579
|
-
const writableLink = b.writableTerminalLinkInCard === true;
|
|
580
|
-
const privateCard = b.privateCard === true;
|
|
581
|
-
return `<section class="bd-section">
|
|
582
|
-
<h3 class="bd-section-title">${t('botDefaults.sectionCard')}</h3>
|
|
583
|
-
<label class="toggle-row">
|
|
584
|
-
<input type="checkbox" data-action="toggle-disable-streaming" ${disableStreaming ? 'checked' : ''}>
|
|
585
|
-
<span class="switch" aria-hidden="true"></span>
|
|
586
|
-
<span class="toggle-tx"><strong>${t('botDefaults.disableStreaming')}</strong>
|
|
587
|
-
<small>${t('botDefaults.disableStreamingHelp')}</small></span>
|
|
588
|
-
</label>
|
|
589
|
-
<label class="toggle-row">
|
|
590
|
-
<input type="checkbox" data-action="toggle-silent-reactions" ${silentReactions ? 'checked' : ''} ${disableStreaming ? '' : 'disabled'}>
|
|
591
|
-
<span class="switch" aria-hidden="true"></span>
|
|
592
|
-
<span class="toggle-tx"><strong>${t('botDefaults.silentTurnReactions')}</strong>
|
|
593
|
-
<small>${t('botDefaults.silentTurnReactionsHelp')}</small></span>
|
|
594
|
-
</label>
|
|
595
|
-
<label class="toggle-row">
|
|
596
|
-
<input type="checkbox" data-action="toggle-writable-link" ${writableLink ? 'checked' : ''} ${disableStreaming ? 'disabled' : ''}>
|
|
597
|
-
<span class="switch" aria-hidden="true"></span>
|
|
598
|
-
<span class="toggle-tx"><strong>${t('botDefaults.writableLink')}</strong>
|
|
599
|
-
<small>${t('botDefaults.writableLinkHelp')}</small></span>
|
|
600
|
-
</label>
|
|
601
|
-
<label class="toggle-row">
|
|
602
|
-
<input type="checkbox" data-action="toggle-private-card" ${privateCard ? 'checked' : ''}>
|
|
603
|
-
<span class="switch" aria-hidden="true"></span>
|
|
604
|
-
<span class="toggle-tx"><strong>${t('botDefaults.privateCard')}</strong>
|
|
605
|
-
<small>${t('botDefaults.privateCardHelp')}</small></span>
|
|
606
|
-
</label>
|
|
607
|
-
<div class="actions">
|
|
608
|
-
<small data-card-pref-moot class="hint-warn-inline" ${disableStreaming ? '' : 'hidden'}>${t('botDefaults.writableLinkMoot')}</small>
|
|
609
|
-
<span class="oncall-status" data-card-pref-status></span>
|
|
610
|
-
</div>
|
|
611
|
-
</section>`;
|
|
612
|
-
}
|
|
613
|
-
// bot@bot 同目录拉起 (cross-bot working-dir inheritance). Default ON. Auto-saves
|
|
614
|
-
// on change via the shared card-prefs PUT.
|
|
615
|
-
function renderCrossBotSection(b) {
|
|
616
|
-
const sameDir = b.botToBotSameDir !== false;
|
|
617
|
-
return `<section class="bd-section">
|
|
618
|
-
<h3 class="bd-section-title">${t('botDefaults.sectionCrossBot')}</h3>
|
|
619
|
-
<label class="toggle-row">
|
|
620
|
-
<input type="checkbox" data-action="toggle-cross-bot-samedir" ${sameDir ? 'checked' : ''}>
|
|
621
|
-
<span class="switch" aria-hidden="true"></span>
|
|
622
|
-
<span class="toggle-tx"><strong>${t('botDefaults.botToBotSameDir')}</strong>
|
|
623
|
-
<small>${t('botDefaults.botToBotSameDirHelp')}</small></span>
|
|
624
|
-
</label>
|
|
625
|
-
<div class="actions"><span class="oncall-status" data-crossbot-status></span></div>
|
|
626
|
-
</section>`;
|
|
627
|
-
}
|
|
628
|
-
function renderSummaryTriggerSection(b) {
|
|
629
|
-
const range = b.summaryRange ?? { limit: 50, sinceHours: 24 };
|
|
630
|
-
const limit = Number.isInteger(range.limit) && range.limit >= 0 ? range.limit : 50;
|
|
631
|
-
const sinceHours = Number.isInteger(range.sinceHours) && range.sinceHours >= 0 ? range.sinceHours : 24;
|
|
632
|
-
return `<section class="bd-section">
|
|
633
|
-
<h3 class="bd-section-title">${t('botDefaults.sectionSummaryTrigger')}</h3>
|
|
634
|
-
<div class="bd-row bd-summary-limits">
|
|
635
|
-
<label>
|
|
636
|
-
<span>${t('botDefaults.summaryLimit')}</span>
|
|
637
|
-
<input type="number" min="0" step="1" data-input="summaryLimit" value="${limit}">
|
|
638
|
-
</label>
|
|
639
|
-
<label>
|
|
640
|
-
<span>${t('botDefaults.summarySinceHours')}</span>
|
|
641
|
-
<input type="number" min="0" step="1" data-input="summarySinceHours" value="${sinceHours}">
|
|
642
|
-
</label>
|
|
643
|
-
</div>
|
|
644
|
-
<small class="bd-help">${t('botDefaults.summaryLimitHelp')}</small>
|
|
645
|
-
<div class="actions">
|
|
646
|
-
<button type="button" class="primary" data-action="save-summary-trigger">${t('botDefaults.summarySave')}</button>
|
|
647
|
-
<span class="oncall-status" data-summary-trigger-status></span>
|
|
648
|
-
</div>
|
|
649
|
-
</section>`;
|
|
650
|
-
}
|
|
651
|
-
// 会话模式:私聊(p2pMode)+ 普通群(regularGroupReplyMode)两个默认会话方式
|
|
652
|
-
// 放在同一板块,各自一个下拉、一改即保存。
|
|
653
|
-
// • p2pMode → PUT /api/bots/:appId/p2p-mode(走 applyConfigField,与 /botconfig 同路径)
|
|
654
|
-
// • 普通群默认模式 mode → PUT /api/bots/:appId/card-prefs 的 regularGroupReplyMode
|
|
655
|
-
// (chat | chat-topic | new-topic | shared,默认 chat)
|
|
656
|
-
// per-chat 的 /reply-mode 可覆盖此 per-bot 默认。
|
|
657
|
-
function renderSessionModeSection(b) {
|
|
658
|
-
const p2p = b.p2pMode === 'chat' ? 'chat' : 'thread';
|
|
659
|
-
const regular = (b.regularGroupReplyMode === 'new-topic' || b.regularGroupReplyMode === 'shared' || b.regularGroupReplyMode === 'chat-topic')
|
|
660
|
-
? b.regularGroupReplyMode : 'chat';
|
|
661
|
-
const mention = (b.regularGroupMentionMode === 'topic' || b.regularGroupMentionMode === 'never' || b.regularGroupMentionMode === 'ambient')
|
|
662
|
-
? b.regularGroupMentionMode : 'always';
|
|
663
|
-
const docMode = b.docSubscribeDefaultMode === 'all' ? 'all' : 'mention-only';
|
|
664
|
-
const opt = (v, label) => `<option value="${v}" ${regular === v ? 'selected' : ''}>${escapeHtml(label)}</option>`;
|
|
665
|
-
const mopt = (v, label) => `<option value="${v}" ${mention === v ? 'selected' : ''}>${escapeHtml(label)}</option>`;
|
|
666
|
-
const dopt = (v, label) => `<option value="${v}" ${docMode === v ? 'selected' : ''}>${escapeHtml(label)}</option>`;
|
|
667
|
-
return `<section class="bd-section">
|
|
668
|
-
<h3 class="bd-section-title">${t('botDefaults.sectionSessionMode')}</h3>
|
|
669
|
-
<div class="bd-row">
|
|
670
|
-
<label>
|
|
671
|
-
<span>${t('botDefaults.p2pMode')}</span>
|
|
672
|
-
<select data-input="p2pMode">
|
|
673
|
-
<option value="thread" ${p2p === 'chat' ? '' : 'selected'}>${escapeHtml(t('botDefaults.p2pThread'))}</option>
|
|
674
|
-
<option value="chat" ${p2p === 'chat' ? 'selected' : ''}>${escapeHtml(t('botDefaults.p2pChat'))}</option>
|
|
675
|
-
</select>
|
|
676
|
-
</label>
|
|
677
|
-
<small class="bd-help">${t('botDefaults.p2pHelp')}</small>
|
|
678
|
-
<div class="actions">
|
|
679
|
-
<span class="oncall-status" data-p2p-status></span>
|
|
680
|
-
</div>
|
|
681
|
-
</div>
|
|
682
|
-
<div class="bd-row">
|
|
683
|
-
<label>
|
|
684
|
-
<span>${t('botDefaults.regularGroupMode')}</span>
|
|
685
|
-
<select data-input="regularGroupMode">
|
|
686
|
-
${opt('chat', t('botDefaults.regularGroupModeChat'))}
|
|
687
|
-
${opt('chat-topic', t('botDefaults.regularGroupModeChatTopic'))}
|
|
688
|
-
${opt('new-topic', t('botDefaults.regularGroupModeNewTopic'))}
|
|
689
|
-
${opt('shared', t('botDefaults.regularGroupModeShared'))}
|
|
690
|
-
</select>
|
|
691
|
-
</label>
|
|
692
|
-
<small class="bd-help">${t('botDefaults.regularGroupModeHelp')}</small>
|
|
693
|
-
<div class="actions">
|
|
694
|
-
<span class="oncall-status" data-regular-group-status></span>
|
|
695
|
-
</div>
|
|
696
|
-
</div>
|
|
697
|
-
<div class="bd-row">
|
|
698
|
-
<label>
|
|
699
|
-
<span>${t('botDefaults.mentionMode')}</span>
|
|
700
|
-
<select data-input="regularGroupMentionMode">
|
|
701
|
-
${mopt('always', t('botDefaults.mentionModeAlways'))}
|
|
702
|
-
${mopt('topic', t('botDefaults.mentionModeTopic'))}
|
|
703
|
-
${mopt('never', t('botDefaults.mentionModeNever'))}
|
|
704
|
-
${mopt('ambient', t('botDefaults.mentionModeAmbient'))}
|
|
705
|
-
</select>
|
|
706
|
-
</label>
|
|
707
|
-
<small class="bd-help">${t('botDefaults.mentionModeHelp')}</small>
|
|
708
|
-
<div class="actions">
|
|
709
|
-
<span class="oncall-status" data-mention-mode-status></span>
|
|
710
|
-
</div>
|
|
711
|
-
</div>
|
|
712
|
-
<div class="bd-row">
|
|
713
|
-
<label>
|
|
714
|
-
<span>${t('botDefaults.docSubscribeMode')}</span>
|
|
715
|
-
<select data-input="docSubscribeDefaultMode">
|
|
716
|
-
${dopt('mention-only', t('botDefaults.docSubscribeModeMention'))}
|
|
717
|
-
${dopt('all', t('botDefaults.docSubscribeModeAll'))}
|
|
718
|
-
</select>
|
|
719
|
-
</label>
|
|
720
|
-
<small class="bd-help">${t('botDefaults.docSubscribeModeHelp')}</small>
|
|
721
|
-
<div class="actions">
|
|
722
|
-
<span class="oncall-status" data-doc-subscribe-mode-status></span>
|
|
723
|
-
</div>
|
|
724
|
-
</div>
|
|
725
|
-
</section>`;
|
|
726
|
-
}
|
|
727
|
-
function renderSubstituteModeSection(b) {
|
|
728
|
-
const mode = b.substituteMode && typeof b.substituteMode === 'object' ? b.substituteMode : null;
|
|
729
|
-
const enabled = mode?.enabled === true;
|
|
730
|
-
const disclosure = mode?.disclosure === 'none' ? 'none' : 'prefix';
|
|
731
|
-
return `<section class="bd-section">
|
|
732
|
-
<h3 class="bd-section-title">${t('botDefaults.sectionSubstitute')}</h3>
|
|
733
|
-
<label class="toggle-row">
|
|
734
|
-
<input type="checkbox" data-action="toggle-substitute-mode" ${enabled ? 'checked' : ''}>
|
|
735
|
-
<span class="switch" aria-hidden="true"></span>
|
|
736
|
-
<span class="toggle-tx"><strong>${t('botDefaults.substituteEnabled')}</strong>
|
|
737
|
-
<small>${t('botDefaults.substituteHelp')}</small></span>
|
|
738
|
-
</label>
|
|
739
|
-
<div class="bd-row">
|
|
740
|
-
<label>
|
|
741
|
-
<span>${t('botDefaults.substituteDisclosure')}</span>
|
|
742
|
-
<select data-input="substituteDisclosure">
|
|
743
|
-
<option value="prefix" ${disclosure === 'prefix' ? 'selected' : ''}>${escapeHtml(t('botDefaults.substituteDisclosurePrefix'))}</option>
|
|
744
|
-
<option value="none" ${disclosure === 'none' ? 'selected' : ''}>${escapeHtml(t('botDefaults.substituteDisclosureNone'))}</option>
|
|
745
|
-
</select>
|
|
746
|
-
</label>
|
|
747
|
-
</div>
|
|
748
|
-
<textarea data-input="substituteTargets" rows="5"
|
|
749
|
-
placeholder="${escapeHtml(t('botDefaults.substituteTargetsPlaceholder'))}"
|
|
750
|
-
style="width:100%;box-sizing:border-box;font:13px/1.5 ui-monospace,Menlo,monospace;padding:10px">${escapeHtml(formatSubstituteTargets(mode))}</textarea>
|
|
751
|
-
<small class="bd-help">${t('botDefaults.substituteTargetsHelp')}</small>
|
|
752
|
-
<div class="substitute-chips" data-substitute-chips style="margin:6px 0 0">${substituteChipsHtml(mode)}</div>
|
|
753
|
-
<div class="actions">
|
|
754
|
-
<button type="button" class="primary" data-action="save-substitute-mode">${t('botDefaults.substituteSave')}</button>
|
|
755
|
-
<button type="button" data-action="off-substitute-mode">${t('botDefaults.substituteOff')}</button>
|
|
756
|
-
<span class="oncall-status" data-substitute-status></span>
|
|
757
|
-
</div>
|
|
758
|
-
</section>`;
|
|
759
|
-
}
|
|
760
|
-
function sessionCapStateLabel(cap) {
|
|
761
|
-
return cap == null
|
|
762
|
-
? t('botDefaults.maxLiveWorkersStateDefault')
|
|
763
|
-
: t('botDefaults.maxLiveWorkersStateOn', { count: cap });
|
|
764
|
-
}
|
|
765
|
-
// 最大同时活跃会话数(maxLiveWorkers):数字输入 + 保存/恢复默认按钮(空=用默认 30)。
|
|
766
|
-
// 超过上限时最久未用的会话自动休眠(worker+CLI 一起杀回收内存),下条消息冷恢复。
|
|
767
|
-
// PUT /api/bots/:appId/max-live-workers 落 bots.json,daemon 每分钟读实时值即时生效。
|
|
768
|
-
function renderSessionCapSection(b) {
|
|
769
|
-
const cap = typeof b.maxLiveWorkers === 'number' ? b.maxLiveWorkers : null;
|
|
770
|
-
return `<div class="bd-subsection">
|
|
771
|
-
<h4 class="bd-subsection-title">${t('botDefaults.sectionSessionCap')}</h4>
|
|
772
|
-
<div class="bd-row bd-quota">
|
|
773
|
-
<label>
|
|
774
|
-
<span>${t('botDefaults.maxLiveWorkers')}</span>
|
|
775
|
-
<input type="number" min="1" step="1" data-input="maxLiveWorkers"
|
|
776
|
-
placeholder="${escapeHtml(t('botDefaults.maxLiveWorkersPlaceholder'))}"
|
|
777
|
-
value="${cap == null ? '' : cap}">
|
|
778
|
-
</label>
|
|
779
|
-
<small data-session-cap-state>${escapeHtml(sessionCapStateLabel(cap))}</small>
|
|
780
|
-
<small class="bd-help">${t('botDefaults.maxLiveWorkersHelp')}</small>
|
|
781
|
-
<div class="actions">
|
|
782
|
-
<button type="button" class="primary" data-action="save-session-cap">${t('botDefaults.maxLiveWorkersSave')}</button>
|
|
783
|
-
<button type="button" data-action="off-session-cap">${t('botDefaults.maxLiveWorkersOff')}</button>
|
|
784
|
-
<span class="oncall-status" data-session-cap-status></span>
|
|
785
|
-
</div>
|
|
786
|
-
</div>
|
|
787
|
-
</div>`;
|
|
788
|
-
}
|
|
789
|
-
// 启动命令 startupCommands:开会话后、首条消息前自动按序发给 CLI 的 slash 命令(可带
|
|
790
|
-
// 参数,如 /effort ultracode)。文本域,逗号/换行分隔,每行一条;空=不发。next-session
|
|
791
|
-
// 生效(含 resume,每次新会话重放)。PUT /api/bots/:appId/startup-commands 落 bots.json。
|
|
792
|
-
function renderStartupCommandsSection(b) {
|
|
793
|
-
const val = typeof b.startupCommands === 'string' ? b.startupCommands : '';
|
|
794
|
-
return `<div class="bd-subsection">
|
|
795
|
-
<h4 class="bd-subsection-title">${t('botDefaults.sectionStartupCommands')}</h4>
|
|
796
|
-
<p class="bd-section-note">${t('botDefaults.startupCommandsHelp')}</p>
|
|
797
|
-
<textarea data-input="startupCommands" rows="3"
|
|
798
|
-
placeholder="${escapeHtml(t('botDefaults.startupCommandsPlaceholder'))}"
|
|
799
|
-
style="width:100%;box-sizing:border-box;font:13px/1.5 ui-monospace,Menlo,monospace;padding:10px">${escapeHtml(val)}</textarea>
|
|
800
|
-
<div class="actions">
|
|
801
|
-
<button type="button" class="primary" data-action="save-startup-commands">${t('botDefaults.startupCommandsSave')}</button>
|
|
802
|
-
<span class="oncall-status" data-startup-commands-status></span>
|
|
803
|
-
</div>
|
|
804
|
-
</div>`;
|
|
805
|
-
}
|
|
806
|
-
// 启动 shell launchShell:启动 CLI 用的 shell(zsh|bash|sh 或绝对路径),覆盖 $SHELL。
|
|
807
|
-
// 用于登录 $SHELL(如 bash)的 rc 文件里 `exec zsh` 跳转、导致 CLI 起不来的场景。
|
|
808
|
-
// next-session 生效。PUT /api/bot-launch-shell 落 bots.json。
|
|
809
|
-
function renderLaunchShellSection(b) {
|
|
810
|
-
const val = typeof b.launchShell === 'string' ? b.launchShell : '';
|
|
811
|
-
return `<div class="bd-subsection">
|
|
812
|
-
<h4 class="bd-subsection-title">${t('botDefaults.sectionLaunchShell')}</h4>
|
|
813
|
-
<p class="bd-section-note">${t('botDefaults.launchShellHelp')}</p>
|
|
814
|
-
<input type="text" data-input="launchShell"
|
|
815
|
-
placeholder="${escapeHtml(t('botDefaults.launchShellPlaceholder'))}"
|
|
816
|
-
value="${escapeHtml(val)}"
|
|
817
|
-
style="width:100%;box-sizing:border-box;font:13px/1.5 ui-monospace,Menlo,monospace;padding:10px">
|
|
818
|
-
<div class="actions">
|
|
819
|
-
<button type="button" class="primary" data-action="save-launch-shell">${t('botDefaults.launchShellSave')}</button>
|
|
820
|
-
<span class="oncall-status" data-launch-shell-status></span>
|
|
821
|
-
</div>
|
|
822
|
-
</div>`;
|
|
823
|
-
}
|
|
824
|
-
// 环境变量 env:注入到本 bot CLI 进程的环境变量(JSON 对象),如让某个 bot 走 GLM/
|
|
825
|
-
// 第三方服务商(ANTHROPIC_BASE_URL+ANTHROPIC_AUTH_TOKEN)或设 HTTPS_PROXY。next-session
|
|
826
|
-
// 生效(下个新会话起注入)。PUT /api/bots/:appId/env 落 bots.json,跨后端按会话注入
|
|
827
|
-
// (不进共享 tmux server 全局 env,不会串到别的 bot)。
|
|
828
|
-
function renderEnvSection(b) {
|
|
829
|
-
const val = typeof b.env === 'string' ? b.env : '';
|
|
830
|
-
return `<div class="bd-subsection">
|
|
831
|
-
<h4 class="bd-subsection-title">${t('botDefaults.sectionEnv')}</h4>
|
|
832
|
-
<p class="bd-section-note">${t('botDefaults.envHelp')}</p>
|
|
833
|
-
<textarea data-input="env" rows="5"
|
|
834
|
-
placeholder="${escapeHtml(t('botDefaults.envPlaceholder'))}"
|
|
835
|
-
style="width:100%;box-sizing:border-box;font:13px/1.5 ui-monospace,Menlo,monospace;padding:10px">${escapeHtml(val)}</textarea>
|
|
836
|
-
<div class="actions">
|
|
837
|
-
<button type="button" class="primary" data-action="save-env">${t('botDefaults.envSave')}</button>
|
|
838
|
-
<span class="oncall-status" data-env-status></span>
|
|
839
|
-
</div>
|
|
840
|
-
</div>`;
|
|
841
|
-
}
|
|
842
|
-
// File sandbox (oncall): a per-bot toggle. ON → this bot's sessions run inside
|
|
843
|
-
// a per-session bwrap file sandbox (Linux). Auto-saves on change.
|
|
844
|
-
function renderSandboxSection(b) {
|
|
845
|
-
const on = b.sandbox === true;
|
|
846
|
-
return `<section class="bd-section">
|
|
847
|
-
<h3 class="bd-section-title">${t('botDefaults.sectionSandbox')}</h3>
|
|
848
|
-
<label class="toggle-row">
|
|
849
|
-
<input type="checkbox" data-action="toggle-sandbox" ${on ? 'checked' : ''}>
|
|
850
|
-
<span class="switch" aria-hidden="true"></span>
|
|
851
|
-
<span class="toggle-tx"><strong>${t('botDefaults.sandboxToggle')}</strong>
|
|
852
|
-
<small>${t('botDefaults.sandboxHelp')}</small></span>
|
|
853
|
-
</label>
|
|
854
|
-
<div class="actions">
|
|
855
|
-
<span class="oncall-status" data-sandbox-status></span>
|
|
856
|
-
</div>
|
|
857
|
-
</section>`;
|
|
858
|
-
}
|
|
859
|
-
function quotaStateLabel(quota) {
|
|
860
|
-
return quota == null
|
|
861
|
-
? t('botDefaults.quotaStateOff')
|
|
862
|
-
: t('botDefaults.quotaStateOn', { count: quota });
|
|
863
|
-
}
|
|
864
|
-
// 授权(/grant)相关:自动申请卡、命令限制开关 + 默认消息额度。都通过
|
|
865
|
-
// PUT /api/bots/:appId/grant-prefs 落到 bots.json,daemon 内存同步即时生效。
|
|
866
|
-
function renderGrantSection(b) {
|
|
867
|
-
const restrict = b.restrictGrantCommands === true;
|
|
868
|
-
const autoCard = b.autoGrantRequestCards !== false;
|
|
869
|
-
const quota = typeof b.messageQuotaDefaultLimit === 'number' ? b.messageQuotaDefaultLimit : null;
|
|
870
|
-
return `<section class="bd-section">
|
|
871
|
-
<h3 class="bd-section-title">${t('botDefaults.sectionGrant')}</h3>
|
|
872
|
-
<label class="toggle-row">
|
|
873
|
-
<input type="checkbox" data-action="toggle-auto-grant-card" ${autoCard ? 'checked' : ''}>
|
|
874
|
-
<span class="switch" aria-hidden="true"></span>
|
|
875
|
-
<span class="toggle-tx"><strong>${t('botDefaults.autoGrantCard')}</strong>
|
|
876
|
-
<small>${t('botDefaults.autoGrantCardHelp')}</small></span>
|
|
877
|
-
</label>
|
|
878
|
-
<label class="toggle-row">
|
|
879
|
-
<input type="checkbox" data-action="toggle-restrict-grant" ${restrict ? 'checked' : ''}>
|
|
880
|
-
<span class="switch" aria-hidden="true"></span>
|
|
881
|
-
<span class="toggle-tx"><strong>${t('botDefaults.restrictGrant')}</strong>
|
|
882
|
-
<small>${t('botDefaults.restrictGrantHelp')}</small></span>
|
|
883
|
-
</label>
|
|
884
|
-
<div class="bd-row bd-quota">
|
|
885
|
-
<label>
|
|
886
|
-
<span>${t('botDefaults.quotaDefault')}</span>
|
|
887
|
-
<input type="number" min="1" step="1" data-input="quotaLimit"
|
|
888
|
-
placeholder="${escapeHtml(t('botDefaults.quotaPlaceholder'))}"
|
|
889
|
-
value="${quota == null ? '' : quota}">
|
|
890
|
-
</label>
|
|
891
|
-
<small data-quota-state>${escapeHtml(quotaStateLabel(quota))}</small>
|
|
892
|
-
<small class="bd-help">${t('botDefaults.quotaHelp')}</small>
|
|
893
|
-
<div class="actions">
|
|
894
|
-
<button type="button" class="primary" data-action="save-quota">${t('botDefaults.quotaSave')}</button>
|
|
895
|
-
<button type="button" data-action="off-quota">${t('botDefaults.quotaOff')}</button>
|
|
896
|
-
<span class="oncall-status" data-grant-status></span>
|
|
897
|
-
</div>
|
|
898
|
-
</div>
|
|
899
|
-
</section>`;
|
|
900
|
-
}
|
|
901
|
-
// 主动开工 — rendered as a sub-block INSIDE the 新群 Oncall section (it's part
|
|
902
|
-
// of the same "proactively engage" config family). The two checkboxes auto-save
|
|
903
|
-
// on change; the 场景① prompt has its own save button (a textarea shouldn't PUT
|
|
904
|
-
// per keystroke). Data-action hooks are unchanged → wireCardHandlers still finds them.
|
|
905
|
-
function renderAutoStartControls(b) {
|
|
906
|
-
const onJoin = b.autoStartOnGroupJoin === true;
|
|
907
|
-
const onTopic = b.autoStartOnNewTopic === true;
|
|
908
|
-
const joinPrompt = typeof b.autoStartOnGroupJoinPrompt === 'string' ? b.autoStartOnGroupJoinPrompt : '';
|
|
909
|
-
return `<div class="bd-subsection">
|
|
910
|
-
<h4 class="bd-subsection-title">${t('botDefaults.sectionAutoStart')}</h4>
|
|
911
|
-
<label class="toggle-row">
|
|
912
|
-
<input type="checkbox" data-action="toggle-auto-join" ${onJoin ? 'checked' : ''}>
|
|
913
|
-
<span class="switch" aria-hidden="true"></span>
|
|
914
|
-
<span class="toggle-tx"><strong>${t('botDefaults.autoStartJoin')}</strong>
|
|
915
|
-
<small>${t('botDefaults.autoStartJoinHelp')}</small></span>
|
|
916
|
-
</label>
|
|
917
|
-
<div class="bd-row">
|
|
918
|
-
<label>
|
|
919
|
-
<span>${t('botDefaults.autoStartJoinPrompt')}</span>
|
|
920
|
-
<textarea data-input="autoJoinPrompt" rows="3"
|
|
921
|
-
placeholder="${escapeHtml(t('botDefaults.autoStartJoinPromptPlaceholder'))}">${escapeHtml(joinPrompt)}</textarea>
|
|
922
|
-
</label>
|
|
923
|
-
<div class="actions">
|
|
924
|
-
<button type="button" class="primary" data-action="save-auto-join-prompt">${t('botDefaults.autoStartJoinPromptSave')}</button>
|
|
925
|
-
</div>
|
|
926
|
-
</div>
|
|
927
|
-
<label class="toggle-row">
|
|
928
|
-
<input type="checkbox" data-action="toggle-auto-topic" ${onTopic ? 'checked' : ''}>
|
|
929
|
-
<span class="switch" aria-hidden="true"></span>
|
|
930
|
-
<span class="toggle-tx"><strong>${t('botDefaults.autoStartTopic')}</strong>
|
|
931
|
-
<small>${t('botDefaults.autoStartTopicHelp')}</small></span>
|
|
932
|
-
</label>
|
|
933
|
-
<div class="actions">
|
|
934
|
-
<span class="oncall-status" data-auto-start-status></span>
|
|
935
|
-
</div>
|
|
936
|
-
</div>`;
|
|
937
|
-
}
|
|
938
|
-
function liveCard(appId) {
|
|
939
|
-
return listEl.querySelector(`.bd-card[data-appid="${CSS.escape(appId)}"]`);
|
|
940
|
-
}
|
|
941
|
-
function renderProfileRolesInto(card, appId) {
|
|
942
|
-
const target = card?.querySelector('[data-profile-role-list]');
|
|
943
|
-
if (!target)
|
|
944
|
-
return;
|
|
945
|
-
target.innerHTML = renderProfileRoleList(appId);
|
|
946
|
-
wireProfileRoleDetails(card, appId);
|
|
947
|
-
}
|
|
948
|
-
async function ensureProfileRolesLoaded(appId, card) {
|
|
949
|
-
let state = botProfileRoleCache.get(appId);
|
|
950
|
-
if (state?.loaded || state?.loading) {
|
|
951
|
-
renderProfileRolesInto(card, appId);
|
|
952
|
-
return;
|
|
953
|
-
}
|
|
954
|
-
state = { loaded: false, loading: true, items: [] };
|
|
955
|
-
botProfileRoleCache.set(appId, state);
|
|
956
|
-
renderProfileRolesInto(card, appId);
|
|
957
|
-
try {
|
|
958
|
-
const r = await fetch('/api/role-profiles');
|
|
959
|
-
const body = await r.json().catch(() => ({}));
|
|
960
|
-
if (!r.ok)
|
|
961
|
-
throw new Error(body?.error ?? String(r.status));
|
|
962
|
-
const profiles = Array.isArray(body.profiles) ? body.profiles : [];
|
|
963
|
-
state.items = profiles
|
|
964
|
-
.filter((profile) => (profile.botEntries ?? []).some((entry) => entry?.larkAppId === appId && entry?.hasEntry))
|
|
965
|
-
.map((profile) => ({ profileId: String(profile.profileId) }));
|
|
966
|
-
state.loaded = true;
|
|
967
|
-
}
|
|
968
|
-
catch (e) {
|
|
969
|
-
state.error = e?.message ?? String(e);
|
|
970
|
-
state.loaded = true;
|
|
971
|
-
}
|
|
972
|
-
finally {
|
|
973
|
-
state.loading = false;
|
|
974
|
-
renderProfileRolesInto(liveCard(appId), appId);
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
function wireProfileRoleDetails(card, appId) {
|
|
978
|
-
card.querySelectorAll('details.bd-profile-role-entry[data-profile-id]').forEach(detail => {
|
|
979
|
-
detail.addEventListener('toggle', () => {
|
|
980
|
-
if (!detail.open)
|
|
981
|
-
return;
|
|
982
|
-
const profileId = detail.dataset.profileId;
|
|
983
|
-
if (profileId)
|
|
984
|
-
void ensureProfileRoleDetailLoaded(appId, profileId);
|
|
985
|
-
});
|
|
986
|
-
});
|
|
987
|
-
}
|
|
988
|
-
async function ensureProfileRoleDetailLoaded(appId, profileId) {
|
|
989
|
-
const state = botProfileRoleCache.get(appId);
|
|
990
|
-
const item = state?.items.find(i => i.profileId === profileId);
|
|
991
|
-
if (!item || item.loaded || item.loading)
|
|
992
|
-
return;
|
|
993
|
-
item.loading = true;
|
|
994
|
-
renderProfileRoleBody(appId, profileId);
|
|
995
|
-
try {
|
|
996
|
-
const r = await fetch(`/api/role-profiles/${encodeURIComponent(profileId)}/${encodeURIComponent(appId)}`);
|
|
997
|
-
const body = await r.json().catch(() => ({}));
|
|
998
|
-
if (!r.ok)
|
|
999
|
-
throw new Error(body?.error ?? String(r.status));
|
|
1000
|
-
item.content = body?.hasEntry ? String(body.content ?? '') : '';
|
|
1001
|
-
item.loaded = true;
|
|
1002
|
-
}
|
|
1003
|
-
catch (e) {
|
|
1004
|
-
item.error = e?.message ?? String(e);
|
|
1005
|
-
}
|
|
1006
|
-
finally {
|
|
1007
|
-
item.loading = false;
|
|
1008
|
-
renderProfileRoleBody(appId, profileId);
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
function renderProfileRoleBody(appId, profileId) {
|
|
1012
|
-
const state = botProfileRoleCache.get(appId);
|
|
1013
|
-
const item = state?.items.find(i => i.profileId === profileId);
|
|
1014
|
-
if (!item)
|
|
1015
|
-
return;
|
|
1016
|
-
const body = liveCard(appId)?.querySelector(`[data-profile-role-body="${CSS.escape(profileId)}"]`);
|
|
1017
|
-
if (body)
|
|
1018
|
-
body.innerHTML = renderProfileRoleContent(item);
|
|
1019
|
-
}
|
|
1020
|
-
function wireCardHandlers() {
|
|
1021
|
-
listEl.querySelectorAll('.bd-card').forEach(card => {
|
|
1022
|
-
const appId = card.dataset.appid;
|
|
1023
|
-
void ensureProfileRolesLoaded(appId, card);
|
|
1024
|
-
// ── 默认工作目录模式(三选一互斥:off / default / oncall)─────────────────
|
|
1025
|
-
const wdModeSel = card.querySelector('select[data-input=workingDirMode]');
|
|
1026
|
-
const input = card.querySelector('input[data-input=workingDir]');
|
|
1027
|
-
const wdDirRow = card.querySelector('[data-wd-dir-row]');
|
|
1028
|
-
const wdWorktreeRow = card.querySelector('[data-wd-worktree-row]');
|
|
1029
|
-
const autoWorktreeInput = card.querySelector('input[data-input=autoWorktree]');
|
|
1030
|
-
const saveBtn = card.querySelector('button[data-action=save-working-dir]');
|
|
1031
|
-
const statusEl = card.querySelector('[data-status]');
|
|
1032
|
-
if (!wdModeSel || !input || !saveBtn || !statusEl)
|
|
1033
|
-
return; // error card
|
|
1034
|
-
// ── Agent CLI / model (next-session) ─────────────────────────────────
|
|
1035
|
-
const agentCliSel = card.querySelector('select[data-input=agentCliId]');
|
|
1036
|
-
const agentModelInput = card.querySelector('input[data-input=agentModel]');
|
|
1037
|
-
const agentSaveBtn = card.querySelector('button[data-action=save-agent]');
|
|
1038
|
-
const agentStatusEl = card.querySelector('[data-agent-status]');
|
|
1039
|
-
function syncAgentModelField() {
|
|
1040
|
-
if (!agentCliSel || !agentModelInput)
|
|
1041
|
-
return;
|
|
1042
|
-
const list = card.querySelector(`#agent-model-suggestions-${CSS.escape(appId)}`);
|
|
1043
|
-
const opt = selectedCliOption(agentCliSel.value);
|
|
1044
|
-
const isTtadk = opt?.gateway === 'ttadk';
|
|
1045
|
-
const acceptsModel = isTtadk && opt.acceptsModel !== false;
|
|
1046
|
-
if (isTtadk && !acceptsModel) {
|
|
1047
|
-
if (list)
|
|
1048
|
-
list.innerHTML = '';
|
|
1049
|
-
agentModelInput.value = '';
|
|
1050
|
-
agentModelInput.disabled = true;
|
|
1051
|
-
agentModelInput.placeholder = t('botOnboarding.modelTtadkCocoPlaceholder');
|
|
1052
|
-
return;
|
|
1053
|
-
}
|
|
1054
|
-
agentModelInput.disabled = false;
|
|
1055
|
-
if (acceptsModel) {
|
|
1056
|
-
if (list)
|
|
1057
|
-
list.innerHTML = ttadkModelSuggestions.map(m => `<option value="${escapeHtml(m)}"></option>`).join('');
|
|
1058
|
-
agentModelInput.placeholder = t('botOnboarding.modelTtadkPlaceholder').replace('{model}', ttadkModelDefault);
|
|
1059
|
-
if (!agentModelInput.value.trim())
|
|
1060
|
-
agentModelInput.value = ttadkModelDefault;
|
|
1061
|
-
}
|
|
1062
|
-
else {
|
|
1063
|
-
if (list)
|
|
1064
|
-
list.innerHTML = '';
|
|
1065
|
-
agentModelInput.placeholder = t('botDefaults.agentModelPlaceholder');
|
|
1066
|
-
if (agentModelInput.value.trim() === ttadkModelDefault)
|
|
1067
|
-
agentModelInput.value = '';
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
if (agentCliSel && agentModelInput && agentSaveBtn && agentStatusEl) {
|
|
1071
|
-
agentCliSel.addEventListener('change', syncAgentModelField);
|
|
1072
|
-
agentSaveBtn.addEventListener('click', async () => {
|
|
1073
|
-
agentStatusEl.textContent = '';
|
|
1074
|
-
agentStatusEl.className = 'oncall-status';
|
|
1075
|
-
agentSaveBtn.disabled = true;
|
|
1076
|
-
agentCliSel.disabled = true;
|
|
1077
|
-
agentModelInput.disabled = true;
|
|
1078
|
-
try {
|
|
1079
|
-
const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/agent`, {
|
|
1080
|
-
method: 'PUT',
|
|
1081
|
-
headers: { 'content-type': 'application/json' },
|
|
1082
|
-
body: JSON.stringify({ cliId: agentCliSel.value, model: agentModelInput.value }),
|
|
1083
|
-
});
|
|
1084
|
-
const body = await r.json().catch(() => ({}));
|
|
1085
|
-
if (r.ok && body.ok) {
|
|
1086
|
-
agentStatusEl.textContent = `✓ ${t('botDefaults.agentSaved')}`;
|
|
1087
|
-
agentStatusEl.classList.add('hint-ok');
|
|
1088
|
-
const cached = cache.bots.find((bb) => bb.larkAppId === appId);
|
|
1089
|
-
if (cached) {
|
|
1090
|
-
cached.cliId = body.cliId;
|
|
1091
|
-
cached.wrapperCli = body.wrapperCli ?? null;
|
|
1092
|
-
cached.model = body.model ?? '';
|
|
1093
|
-
cached.agentSelectionKey = body.selectionKey ?? agentCliSel.value;
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
else {
|
|
1097
|
-
agentStatusEl.textContent = `✗ ${body.error ?? r.status}`;
|
|
1098
|
-
agentStatusEl.classList.add('hint-warn-inline');
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1101
|
-
catch (e) {
|
|
1102
|
-
agentStatusEl.textContent = `✗ ${e?.message ?? e}`;
|
|
1103
|
-
agentStatusEl.classList.add('hint-warn-inline');
|
|
1104
|
-
}
|
|
1105
|
-
finally {
|
|
1106
|
-
agentSaveBtn.disabled = false;
|
|
1107
|
-
agentCliSel.disabled = false;
|
|
1108
|
-
agentModelInput.disabled = false;
|
|
1109
|
-
syncAgentModelField();
|
|
1110
|
-
}
|
|
1111
|
-
});
|
|
1112
|
-
}
|
|
1113
|
-
// 选「关闭」隐藏目录输入框;选其它则显示并聚焦(off 不需要目录)。
|
|
1114
|
-
// 「自动创建 worktree」开关仅「仅默认目录」模式可见(脱离该模式无意义)。
|
|
1115
|
-
wdModeSel.addEventListener('change', () => {
|
|
1116
|
-
const off = wdModeSel.value === 'off';
|
|
1117
|
-
if (wdDirRow)
|
|
1118
|
-
wdDirRow.hidden = off;
|
|
1119
|
-
if (wdWorktreeRow)
|
|
1120
|
-
wdWorktreeRow.hidden = wdModeSel.value !== 'default';
|
|
1121
|
-
if (!off)
|
|
1122
|
-
input.focus();
|
|
1123
|
-
});
|
|
1124
|
-
saveBtn.addEventListener('click', async () => {
|
|
1125
|
-
statusEl.textContent = '';
|
|
1126
|
-
statusEl.className = 'oncall-status';
|
|
1127
|
-
const mode = wdModeSel.value;
|
|
1128
|
-
const workingDir = input.value.trim();
|
|
1129
|
-
if (mode !== 'off' && !workingDir) {
|
|
1130
|
-
statusEl.textContent = t('botDefaults.required');
|
|
1131
|
-
statusEl.classList.add('hint-warn-inline');
|
|
1132
|
-
return;
|
|
1133
|
-
}
|
|
1134
|
-
// 「自动创建 worktree」仅「仅默认目录」模式有效;其它模式一律传 false(后端也会强制清)。
|
|
1135
|
-
const autoWorktree = mode === 'default' && !!autoWorktreeInput?.checked;
|
|
1136
|
-
saveBtn.disabled = true;
|
|
1137
|
-
try {
|
|
1138
|
-
const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/working-dir-mode`, {
|
|
1139
|
-
method: 'PUT',
|
|
1140
|
-
headers: { 'content-type': 'application/json' },
|
|
1141
|
-
body: JSON.stringify({ mode, workingDir, autoWorktree }),
|
|
1142
|
-
});
|
|
1143
|
-
const body = await r.json().catch(() => ({}));
|
|
1144
|
-
if (r.ok && body.ok) {
|
|
1145
|
-
const resolvedNote = body.resolvedPath ? ` → ${body.resolvedPath}` : '';
|
|
1146
|
-
statusEl.textContent = `✓ ${t('botDefaults.workingDirSaved')}${resolvedNote}`;
|
|
1147
|
-
statusEl.classList.add('hint-ok');
|
|
1148
|
-
// Patch in-cache snapshot so the next manual Refresh / filter
|
|
1149
|
-
// rerender reflects the new mode. We deliberately don't call
|
|
1150
|
-
// rerender() here — that would rebuild the card and wipe the toast.
|
|
1151
|
-
const cached = cache.bots.find((b) => b.larkAppId === appId);
|
|
1152
|
-
if (cached) {
|
|
1153
|
-
if (body.defaultOncall)
|
|
1154
|
-
cached.defaultOncall = body.defaultOncall;
|
|
1155
|
-
cached.defaultWorkingDir = body.defaultWorkingDir ?? null;
|
|
1156
|
-
cached.defaultWorkingDirAutoWorktree = body.defaultWorkingDirAutoWorktree === true;
|
|
1157
|
-
}
|
|
1158
|
-
// Oncall 模式 re-stamps `since` → reflect it in the meta line.
|
|
1159
|
-
const metaEl = card.querySelector('[data-oncall-since]');
|
|
1160
|
-
if (metaEl && body.defaultOncall?.since != null) {
|
|
1161
|
-
metaEl.textContent = `${t('botDefaults.lastEnabled')}: ${fmtSince(body.defaultOncall.since)}`;
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
else {
|
|
1165
|
-
statusEl.textContent = `✗ ${body.error ?? r.status}`;
|
|
1166
|
-
statusEl.classList.add('hint-warn-inline');
|
|
1167
|
-
}
|
|
1168
|
-
}
|
|
1169
|
-
catch (e) {
|
|
1170
|
-
statusEl.textContent = `✗ ${e?.message ?? e}`;
|
|
1171
|
-
statusEl.classList.add('hint-warn-inline');
|
|
1172
|
-
}
|
|
1173
|
-
finally {
|
|
1174
|
-
saveBtn.disabled = false;
|
|
1175
|
-
}
|
|
1176
|
-
});
|
|
1177
|
-
// ── Brand label (independent of oncall save) ──────────────────────────
|
|
1178
|
-
const brandInput = card.querySelector('input[data-input=brandLabel]');
|
|
1179
|
-
const brandSaveBtn = card.querySelector('button[data-action=save-brand]');
|
|
1180
|
-
const brandResetBtn = card.querySelector('button[data-action=reset-brand]');
|
|
1181
|
-
const brandStatusEl = card.querySelector('[data-brand-status]');
|
|
1182
|
-
const brandStateEl = card.querySelector('[data-brand-state]');
|
|
1183
|
-
// PUT the given brandLabel (string '' = off, null = revert to default),
|
|
1184
|
-
// then reflect the new state inline without a full rerender.
|
|
1185
|
-
async function putBrand(brandLabel, btn) {
|
|
1186
|
-
if (!brandStatusEl)
|
|
1187
|
-
return;
|
|
1188
|
-
brandStatusEl.textContent = '';
|
|
1189
|
-
brandStatusEl.className = 'oncall-status';
|
|
1190
|
-
btn.disabled = true;
|
|
1191
|
-
try {
|
|
1192
|
-
const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/brand-label`, {
|
|
1193
|
-
method: 'PUT',
|
|
1194
|
-
headers: { 'content-type': 'application/json' },
|
|
1195
|
-
body: JSON.stringify({ brandLabel }),
|
|
1196
|
-
});
|
|
1197
|
-
const body = await r.json().catch(() => ({}));
|
|
1198
|
-
if (r.ok && body.ok) {
|
|
1199
|
-
const next = body.brandLabel ?? null;
|
|
1200
|
-
brandStatusEl.textContent = '✓';
|
|
1201
|
-
brandStatusEl.classList.add('hint-ok');
|
|
1202
|
-
if (brandInput)
|
|
1203
|
-
brandInput.value = next ?? '';
|
|
1204
|
-
if (brandStateEl)
|
|
1205
|
-
brandStateEl.textContent = brandStateLabel(next);
|
|
1206
|
-
const cached = cache.bots.find((b) => b.larkAppId === appId);
|
|
1207
|
-
if (cached)
|
|
1208
|
-
cached.brandLabel = next;
|
|
1209
|
-
}
|
|
1210
|
-
else {
|
|
1211
|
-
brandStatusEl.textContent = `✗ ${body.error ?? r.status}`;
|
|
1212
|
-
brandStatusEl.classList.add('hint-warn-inline');
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
catch (e) {
|
|
1216
|
-
brandStatusEl.textContent = `✗ ${e?.message ?? e}`;
|
|
1217
|
-
brandStatusEl.classList.add('hint-warn-inline');
|
|
1218
|
-
}
|
|
1219
|
-
finally {
|
|
1220
|
-
btn.disabled = false;
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
if (brandInput && brandSaveBtn) {
|
|
1224
|
-
// Empty input saved as '' = brand off (per "配置为空就可以关").
|
|
1225
|
-
brandSaveBtn.addEventListener('click', () => putBrand(brandInput.value, brandSaveBtn));
|
|
1226
|
-
}
|
|
1227
|
-
if (brandResetBtn) {
|
|
1228
|
-
brandResetBtn.addEventListener('click', () => putBrand(null, brandResetBtn));
|
|
1229
|
-
}
|
|
1230
|
-
// ── 机器人改名(档案头 ✎ → 行内输入框)────────────────────────────────
|
|
1231
|
-
// 主路径走飞书开放平台真改应用名(daemon 侧自动建版发布,约 5-10 秒);
|
|
1232
|
-
// 失败自动降级为仅改 dashboard 展示名并明示原因。成功后就地更新卡片标题、
|
|
1233
|
-
// 左侧名册与缓存,不整卡重绘(避免吹掉状态提示)。
|
|
1234
|
-
const nameRowEl = card.querySelector('[data-name-row]');
|
|
1235
|
-
const nameEditorEl = card.querySelector('[data-name-editor]');
|
|
1236
|
-
const nameStrongEl = card.querySelector('[data-bot-name]');
|
|
1237
|
-
const nameInputEl = card.querySelector('input[data-input=botRename]');
|
|
1238
|
-
const nameEditBtn = card.querySelector('button[data-action=edit-bot-name]');
|
|
1239
|
-
const nameSaveBtn = card.querySelector('button[data-action=save-bot-name]');
|
|
1240
|
-
const nameCancelBtn = card.querySelector('button[data-action=cancel-bot-name]');
|
|
1241
|
-
const nameStatusEl = card.querySelector('[data-name-status]');
|
|
1242
|
-
const feishuLoginBtn = card.querySelector('button[data-action=feishu-login]');
|
|
1243
|
-
function setNameEditMode(on) {
|
|
1244
|
-
if (!nameRowEl || !nameEditorEl)
|
|
1245
|
-
return;
|
|
1246
|
-
nameRowEl.hidden = on;
|
|
1247
|
-
nameEditorEl.hidden = !on;
|
|
1248
|
-
if (on && nameInputEl) {
|
|
1249
|
-
nameInputEl.value = nameStrongEl?.textContent ?? '';
|
|
1250
|
-
nameInputEl.focus();
|
|
1251
|
-
nameInputEl.select();
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
function renameWarningText(warning, message) {
|
|
1255
|
-
const known = ['no_session', 'session_expired', 'no_access', 'unsupported_brand'];
|
|
1256
|
-
const detail = known.includes(warning)
|
|
1257
|
-
? t(`botDefaults.renameWarn.${warning}`)
|
|
1258
|
-
: (message || warning);
|
|
1259
|
-
return t('botDefaults.renameLocalOnly', { reason: detail });
|
|
1260
|
-
}
|
|
1261
|
-
async function submitRename() {
|
|
1262
|
-
if (!nameInputEl || !nameStatusEl)
|
|
1263
|
-
return;
|
|
1264
|
-
const name = nameInputEl.value.trim();
|
|
1265
|
-
if (!name) {
|
|
1266
|
-
nameStatusEl.textContent = `✗ ${t('botDefaults.renameEmpty')}`;
|
|
1267
|
-
nameStatusEl.className = 'bd-name-status oncall-status hint-warn-inline';
|
|
1268
|
-
return;
|
|
1269
|
-
}
|
|
1270
|
-
nameInputEl.disabled = true;
|
|
1271
|
-
if (nameSaveBtn)
|
|
1272
|
-
nameSaveBtn.disabled = true;
|
|
1273
|
-
if (nameCancelBtn)
|
|
1274
|
-
nameCancelBtn.disabled = true;
|
|
1275
|
-
nameStatusEl.className = 'bd-name-status oncall-status';
|
|
1276
|
-
nameStatusEl.textContent = `⏳ ${t('botDefaults.renaming')}`;
|
|
1277
|
-
try {
|
|
1278
|
-
const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/rename`, {
|
|
1279
|
-
method: 'PUT',
|
|
1280
|
-
headers: { 'content-type': 'application/json' },
|
|
1281
|
-
body: JSON.stringify({ name }),
|
|
1282
|
-
});
|
|
1283
|
-
const body = await r.json().catch(() => ({}));
|
|
1284
|
-
if (r.ok && body.ok) {
|
|
1285
|
-
const effective = (typeof body.botName === 'string' && body.botName) ? body.botName : name;
|
|
1286
|
-
const cached = cache.bots.find((bb) => bb.larkAppId === appId);
|
|
1287
|
-
if (cached) {
|
|
1288
|
-
cached.botName = effective;
|
|
1289
|
-
if (body.mode === 'feishu') {
|
|
1290
|
-
cached.larkBotName = name;
|
|
1291
|
-
cached.displayName = null;
|
|
1292
|
-
}
|
|
1293
|
-
else
|
|
1294
|
-
cached.displayName = name;
|
|
1295
|
-
}
|
|
1296
|
-
if (nameStrongEl)
|
|
1297
|
-
nameStrongEl.textContent = effective;
|
|
1298
|
-
const rosterName = rosterEl.querySelector(`.bd-roster-item[data-appid="${CSS.escape(appId)}"] .bd-roster-tx b`);
|
|
1299
|
-
if (rosterName)
|
|
1300
|
-
rosterName.textContent = effective;
|
|
1301
|
-
setNameEditMode(false);
|
|
1302
|
-
if (body.mode === 'feishu') {
|
|
1303
|
-
nameStatusEl.textContent = `✓ ${t('botDefaults.renameOkFeishu')}`;
|
|
1304
|
-
nameStatusEl.classList.add('hint-ok');
|
|
1305
|
-
if (feishuLoginBtn)
|
|
1306
|
-
feishuLoginBtn.hidden = true;
|
|
1307
|
-
}
|
|
1308
|
-
else {
|
|
1309
|
-
nameStatusEl.textContent = `⚠ ${renameWarningText(String(body.warning ?? ''), body.message)}`;
|
|
1310
|
-
nameStatusEl.classList.add('hint-warn-inline');
|
|
1311
|
-
if (typeof body.message === 'string' && body.message)
|
|
1312
|
-
nameStatusEl.title = body.message;
|
|
1313
|
-
// 登录态缺失/过期 → 亮出「扫码登录」,让用户当场刷登录态后重试真改名。
|
|
1314
|
-
const needsLogin = body.warning === 'no_session' || body.warning === 'session_expired';
|
|
1315
|
-
if (feishuLoginBtn)
|
|
1316
|
-
feishuLoginBtn.hidden = !needsLogin;
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
else {
|
|
1320
|
-
nameStatusEl.textContent = `✗ ${t('botDefaults.renameFailed', { error: String(body.error ?? r.status) })}`;
|
|
1321
|
-
nameStatusEl.classList.add('hint-warn-inline');
|
|
1322
|
-
}
|
|
1323
|
-
}
|
|
1324
|
-
catch (e) {
|
|
1325
|
-
nameStatusEl.textContent = `✗ ${t('botDefaults.renameFailed', { error: e?.message ?? String(e) })}`;
|
|
1326
|
-
nameStatusEl.classList.add('hint-warn-inline');
|
|
1327
|
-
}
|
|
1328
|
-
finally {
|
|
1329
|
-
nameInputEl.disabled = false;
|
|
1330
|
-
if (nameSaveBtn)
|
|
1331
|
-
nameSaveBtn.disabled = false;
|
|
1332
|
-
if (nameCancelBtn)
|
|
1333
|
-
nameCancelBtn.disabled = false;
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
if (nameEditBtn) {
|
|
1337
|
-
nameEditBtn.addEventListener('click', () => {
|
|
1338
|
-
if (nameStatusEl) {
|
|
1339
|
-
nameStatusEl.textContent = '';
|
|
1340
|
-
nameStatusEl.className = 'bd-name-status oncall-status';
|
|
1341
|
-
}
|
|
1342
|
-
if (feishuLoginBtn)
|
|
1343
|
-
feishuLoginBtn.hidden = true;
|
|
1344
|
-
setNameEditMode(true);
|
|
1345
|
-
});
|
|
1346
|
-
}
|
|
1347
|
-
// 「扫码登录飞书」→ 弹二维码 modal,扫码成功后自动重试真改名。
|
|
1348
|
-
if (feishuLoginBtn) {
|
|
1349
|
-
feishuLoginBtn.addEventListener('click', () => {
|
|
1350
|
-
openFeishuLoginModal(() => {
|
|
1351
|
-
feishuLoginBtn.hidden = true;
|
|
1352
|
-
void submitRename();
|
|
1353
|
-
});
|
|
1354
|
-
});
|
|
1355
|
-
}
|
|
1356
|
-
if (nameCancelBtn)
|
|
1357
|
-
nameCancelBtn.addEventListener('click', () => setNameEditMode(false));
|
|
1358
|
-
if (nameSaveBtn)
|
|
1359
|
-
nameSaveBtn.addEventListener('click', () => void submitRename());
|
|
1360
|
-
if (nameInputEl) {
|
|
1361
|
-
nameInputEl.addEventListener('keydown', (e) => {
|
|
1362
|
-
if (e.key === 'Enter') {
|
|
1363
|
-
e.preventDefault();
|
|
1364
|
-
void submitRename();
|
|
1365
|
-
}
|
|
1366
|
-
else if (e.key === 'Escape')
|
|
1367
|
-
setNameEditMode(false);
|
|
1368
|
-
});
|
|
1369
|
-
}
|
|
1370
|
-
// ── Card behaviour toggles (auto-save on change) ──────────────────────
|
|
1371
|
-
const disableStreamingCb = card.querySelector('input[data-action=toggle-disable-streaming]');
|
|
1372
|
-
const silentReactionsCb = card.querySelector('input[data-action=toggle-silent-reactions]');
|
|
1373
|
-
const writableLinkCb = card.querySelector('input[data-action=toggle-writable-link]');
|
|
1374
|
-
const privateCardCb = card.querySelector('input[data-action=toggle-private-card]');
|
|
1375
|
-
const cardPrefStatusEl = card.querySelector('[data-card-pref-status]');
|
|
1376
|
-
const cardPrefMootEl = card.querySelector('[data-card-pref-moot]');
|
|
1377
|
-
// PUT a partial card-prefs patch (booleans and/or the auto-start prompt
|
|
1378
|
-
// string). `selfEl` is the control that triggered it (disabled during the
|
|
1379
|
-
// request to block double-submit); `statusEl` is where the result toast
|
|
1380
|
-
// lands (defaults to the card-behaviour status line).
|
|
1381
|
-
async function putCardPref(patch, selfEl, statusEl = cardPrefStatusEl) {
|
|
1382
|
-
if (!statusEl)
|
|
1383
|
-
return;
|
|
1384
|
-
statusEl.textContent = '';
|
|
1385
|
-
statusEl.className = 'oncall-status';
|
|
1386
|
-
selfEl.disabled = true;
|
|
1387
|
-
try {
|
|
1388
|
-
const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/card-prefs`, {
|
|
1389
|
-
method: 'PUT',
|
|
1390
|
-
headers: { 'content-type': 'application/json' },
|
|
1391
|
-
body: JSON.stringify(patch),
|
|
1392
|
-
});
|
|
1393
|
-
const body = await r.json().catch(() => ({}));
|
|
1394
|
-
if (r.ok && body.ok) {
|
|
1395
|
-
statusEl.textContent = `✓ ${t('botDefaults.cardPrefSaved')}`;
|
|
1396
|
-
statusEl.classList.add('hint-ok');
|
|
1397
|
-
const cached = cache.bots.find((bb) => bb.larkAppId === appId);
|
|
1398
|
-
if (cached) {
|
|
1399
|
-
cached.disableStreamingCard = body.disableStreamingCard;
|
|
1400
|
-
cached.silentTurnReactions = body.silentTurnReactions;
|
|
1401
|
-
cached.writableTerminalLinkInCard = body.writableTerminalLinkInCard;
|
|
1402
|
-
cached.privateCard = body.privateCard;
|
|
1403
|
-
cached.botToBotSameDir = body.botToBotSameDir;
|
|
1404
|
-
cached.autoStartOnGroupJoin = body.autoStartOnGroupJoin;
|
|
1405
|
-
cached.autoStartOnGroupJoinPrompt = body.autoStartOnGroupJoinPrompt;
|
|
1406
|
-
cached.autoStartOnNewTopic = body.autoStartOnNewTopic;
|
|
1407
|
-
cached.regularGroupReplyMode = body.regularGroupReplyMode;
|
|
1408
|
-
cached.regularGroupMentionMode = body.regularGroupMentionMode;
|
|
1409
|
-
cached.docSubscribeDefaultMode = body.docSubscribeDefaultMode;
|
|
1410
|
-
}
|
|
1411
|
-
}
|
|
1412
|
-
else {
|
|
1413
|
-
statusEl.textContent = `✗ ${body.error ?? r.status}`;
|
|
1414
|
-
statusEl.classList.add('hint-warn-inline');
|
|
1415
|
-
}
|
|
1416
|
-
}
|
|
1417
|
-
catch (e) {
|
|
1418
|
-
statusEl.textContent = `✗ ${e?.message ?? e}`;
|
|
1419
|
-
statusEl.classList.add('hint-warn-inline');
|
|
1420
|
-
}
|
|
1421
|
-
finally {
|
|
1422
|
-
// The writable-link checkbox stays disabled while streaming is off.
|
|
1423
|
-
if (selfEl === writableLinkCb)
|
|
1424
|
-
selfEl.disabled = !!disableStreamingCb?.checked;
|
|
1425
|
-
// The status-reactions checkbox is only editable while streaming is off.
|
|
1426
|
-
else if (selfEl === silentReactionsCb)
|
|
1427
|
-
selfEl.disabled = !disableStreamingCb?.checked;
|
|
1428
|
-
else
|
|
1429
|
-
selfEl.disabled = false;
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
if (disableStreamingCb) {
|
|
1433
|
-
disableStreamingCb.addEventListener('change', () => {
|
|
1434
|
-
const off = disableStreamingCb.checked;
|
|
1435
|
-
// Streaming off → the writable-link toggle has nothing to attach to.
|
|
1436
|
-
if (writableLinkCb)
|
|
1437
|
-
writableLinkCb.disabled = off;
|
|
1438
|
-
// Status reactions only exist in card-off sessions, so this toggle is
|
|
1439
|
-
// editable only while streaming is off.
|
|
1440
|
-
if (silentReactionsCb)
|
|
1441
|
-
silentReactionsCb.disabled = !off;
|
|
1442
|
-
if (cardPrefMootEl)
|
|
1443
|
-
cardPrefMootEl.hidden = !off;
|
|
1444
|
-
putCardPref({ disableStreamingCard: off }, disableStreamingCb);
|
|
1445
|
-
});
|
|
1446
|
-
}
|
|
1447
|
-
if (silentReactionsCb) {
|
|
1448
|
-
silentReactionsCb.addEventListener('change', () => {
|
|
1449
|
-
putCardPref({ silentTurnReactions: silentReactionsCb.checked }, silentReactionsCb);
|
|
1450
|
-
});
|
|
1451
|
-
}
|
|
1452
|
-
if (writableLinkCb) {
|
|
1453
|
-
writableLinkCb.addEventListener('change', () => {
|
|
1454
|
-
putCardPref({ writableTerminalLinkInCard: writableLinkCb.checked }, writableLinkCb);
|
|
1455
|
-
});
|
|
1456
|
-
}
|
|
1457
|
-
if (privateCardCb) {
|
|
1458
|
-
privateCardCb.addEventListener('change', () => {
|
|
1459
|
-
putCardPref({ privateCard: privateCardCb.checked }, privateCardCb);
|
|
1460
|
-
});
|
|
1461
|
-
}
|
|
1462
|
-
const crossBotCb = card.querySelector('input[data-action=toggle-cross-bot-samedir]');
|
|
1463
|
-
const crossBotStatusEl = card.querySelector('[data-crossbot-status]');
|
|
1464
|
-
if (crossBotCb) {
|
|
1465
|
-
crossBotCb.addEventListener('change', () => {
|
|
1466
|
-
putCardPref({ botToBotSameDir: crossBotCb.checked }, crossBotCb, crossBotStatusEl);
|
|
1467
|
-
});
|
|
1468
|
-
}
|
|
1469
|
-
// ── /summary 总结范围 ───────────────────────────────────────────────
|
|
1470
|
-
const summaryLimitInput = card.querySelector('input[data-input=summaryLimit]');
|
|
1471
|
-
const summarySinceInput = card.querySelector('input[data-input=summarySinceHours]');
|
|
1472
|
-
const summarySaveBtn = card.querySelector('button[data-action=save-summary-trigger]');
|
|
1473
|
-
const summaryStatusEl = card.querySelector('[data-summary-trigger-status]');
|
|
1474
|
-
function readNonNegativeInt(input, fallback) {
|
|
1475
|
-
const raw = input.value.trim();
|
|
1476
|
-
if (raw === '')
|
|
1477
|
-
return fallback;
|
|
1478
|
-
if (!/^(0|[1-9]\d*)$/.test(raw))
|
|
1479
|
-
return null;
|
|
1480
|
-
return Number(raw);
|
|
1481
|
-
}
|
|
1482
|
-
if (summaryLimitInput && summarySinceInput && summarySaveBtn) {
|
|
1483
|
-
summarySaveBtn.addEventListener('click', async () => {
|
|
1484
|
-
if (!summaryStatusEl)
|
|
1485
|
-
return;
|
|
1486
|
-
summaryStatusEl.textContent = '';
|
|
1487
|
-
summaryStatusEl.className = 'oncall-status';
|
|
1488
|
-
const limit = readNonNegativeInt(summaryLimitInput, 50);
|
|
1489
|
-
const sinceHours = readNonNegativeInt(summarySinceInput, 24);
|
|
1490
|
-
if (limit == null || sinceHours == null) {
|
|
1491
|
-
summaryStatusEl.textContent = `✗ ${t('botDefaults.summaryNumberInvalid')}`;
|
|
1492
|
-
summaryStatusEl.classList.add('hint-warn-inline');
|
|
1493
|
-
return;
|
|
1494
|
-
}
|
|
1495
|
-
summarySaveBtn.disabled = true;
|
|
1496
|
-
try {
|
|
1497
|
-
const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/summary-range`, {
|
|
1498
|
-
method: 'PUT',
|
|
1499
|
-
headers: { 'content-type': 'application/json' },
|
|
1500
|
-
body: JSON.stringify({
|
|
1501
|
-
limit,
|
|
1502
|
-
sinceHours,
|
|
1503
|
-
}),
|
|
1504
|
-
});
|
|
1505
|
-
const body = await r.json().catch(() => ({}));
|
|
1506
|
-
if (r.ok && body.ok) {
|
|
1507
|
-
summaryStatusEl.textContent = `✓ ${t('botDefaults.cardPrefSaved')}`;
|
|
1508
|
-
summaryStatusEl.classList.add('hint-ok');
|
|
1509
|
-
const next = body.summaryRange ?? { limit, sinceHours };
|
|
1510
|
-
summaryLimitInput.value = String(Number.isInteger(next.limit) && next.limit >= 0 ? next.limit : limit);
|
|
1511
|
-
summarySinceInput.value = String(Number.isInteger(next.sinceHours) && next.sinceHours >= 0 ? next.sinceHours : sinceHours);
|
|
1512
|
-
const cached = cache.bots.find((bb) => bb.larkAppId === appId);
|
|
1513
|
-
if (cached)
|
|
1514
|
-
cached.summaryRange = next;
|
|
1515
|
-
}
|
|
1516
|
-
else {
|
|
1517
|
-
summaryStatusEl.textContent = `✗ ${body.error ?? r.status}`;
|
|
1518
|
-
summaryStatusEl.classList.add('hint-warn-inline');
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1521
|
-
catch (e) {
|
|
1522
|
-
summaryStatusEl.textContent = `✗ ${e?.message ?? e}`;
|
|
1523
|
-
summaryStatusEl.classList.add('hint-warn-inline');
|
|
1524
|
-
}
|
|
1525
|
-
finally {
|
|
1526
|
-
summarySaveBtn.disabled = false;
|
|
1527
|
-
}
|
|
1528
|
-
});
|
|
1529
|
-
}
|
|
1530
|
-
// ── File sandbox toggle (auto-save on change) ─────────────────────────
|
|
1531
|
-
const sandboxCb = card.querySelector('input[data-action=toggle-sandbox]');
|
|
1532
|
-
const sandboxStatusEl = card.querySelector('[data-sandbox-status]');
|
|
1533
|
-
if (sandboxCb) {
|
|
1534
|
-
sandboxCb.addEventListener('change', async () => {
|
|
1535
|
-
const enabled = sandboxCb.checked;
|
|
1536
|
-
if (sandboxStatusEl) {
|
|
1537
|
-
sandboxStatusEl.textContent = '';
|
|
1538
|
-
sandboxStatusEl.className = 'oncall-status';
|
|
1539
|
-
}
|
|
1540
|
-
sandboxCb.disabled = true;
|
|
1541
|
-
try {
|
|
1542
|
-
const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/sandbox`, {
|
|
1543
|
-
method: 'PUT',
|
|
1544
|
-
headers: { 'content-type': 'application/json' },
|
|
1545
|
-
body: JSON.stringify({ enabled }),
|
|
1546
|
-
});
|
|
1547
|
-
const body = await r.json().catch(() => ({}));
|
|
1548
|
-
if (r.ok && body.ok) {
|
|
1549
|
-
if (sandboxStatusEl) {
|
|
1550
|
-
sandboxStatusEl.textContent = `✓ ${t('botDefaults.sandboxSaved')}`;
|
|
1551
|
-
sandboxStatusEl.classList.add('hint-ok');
|
|
1552
|
-
}
|
|
1553
|
-
const cached = cache.bots.find((bb) => bb.larkAppId === appId);
|
|
1554
|
-
if (cached)
|
|
1555
|
-
cached.sandbox = body.sandbox === true;
|
|
1556
|
-
}
|
|
1557
|
-
else {
|
|
1558
|
-
if (sandboxStatusEl) {
|
|
1559
|
-
sandboxStatusEl.textContent = `✗ ${body.error ?? r.status}`;
|
|
1560
|
-
sandboxStatusEl.classList.add('hint-warn-inline');
|
|
1561
|
-
}
|
|
1562
|
-
sandboxCb.checked = !enabled; // revert on failure
|
|
1563
|
-
}
|
|
1564
|
-
}
|
|
1565
|
-
catch (e) {
|
|
1566
|
-
if (sandboxStatusEl) {
|
|
1567
|
-
sandboxStatusEl.textContent = `✗ ${e?.message ?? e}`;
|
|
1568
|
-
sandboxStatusEl.classList.add('hint-warn-inline');
|
|
1569
|
-
}
|
|
1570
|
-
sandboxCb.checked = !enabled;
|
|
1571
|
-
}
|
|
1572
|
-
finally {
|
|
1573
|
-
sandboxCb.disabled = false;
|
|
1574
|
-
}
|
|
1575
|
-
});
|
|
1576
|
-
}
|
|
1577
|
-
// ── 主动开工 toggles + 场景① prompt ───────────────────────────────────
|
|
1578
|
-
const autoJoinCb = card.querySelector('input[data-action=toggle-auto-join]');
|
|
1579
|
-
const autoTopicCb = card.querySelector('input[data-action=toggle-auto-topic]');
|
|
1580
|
-
const autoJoinPromptEl = card.querySelector('textarea[data-input=autoJoinPrompt]');
|
|
1581
|
-
const autoJoinPromptSaveBtn = card.querySelector('button[data-action=save-auto-join-prompt]');
|
|
1582
|
-
const autoStartStatusEl = card.querySelector('[data-auto-start-status]');
|
|
1583
|
-
if (autoJoinCb) {
|
|
1584
|
-
autoJoinCb.addEventListener('change', () => {
|
|
1585
|
-
putCardPref({ autoStartOnGroupJoin: autoJoinCb.checked }, autoJoinCb, autoStartStatusEl);
|
|
1586
|
-
});
|
|
1587
|
-
}
|
|
1588
|
-
if (autoTopicCb) {
|
|
1589
|
-
autoTopicCb.addEventListener('change', () => {
|
|
1590
|
-
putCardPref({ autoStartOnNewTopic: autoTopicCb.checked }, autoTopicCb, autoStartStatusEl);
|
|
1591
|
-
});
|
|
1592
|
-
}
|
|
1593
|
-
if (autoJoinPromptEl && autoJoinPromptSaveBtn) {
|
|
1594
|
-
autoJoinPromptSaveBtn.addEventListener('click', () => {
|
|
1595
|
-
putCardPref({ autoStartOnGroupJoinPrompt: autoJoinPromptEl.value }, autoJoinPromptSaveBtn, autoStartStatusEl);
|
|
1596
|
-
});
|
|
1597
|
-
}
|
|
1598
|
-
// ── 私聊单聊模式 p2pMode select ───────────────────────────────────────
|
|
1599
|
-
const p2pModeSel = card.querySelector('select[data-input=p2pMode]');
|
|
1600
|
-
const p2pStatusEl = card.querySelector('[data-p2p-status]');
|
|
1601
|
-
if (p2pModeSel && p2pStatusEl) {
|
|
1602
|
-
p2pModeSel.addEventListener('change', async () => {
|
|
1603
|
-
const mode = p2pModeSel.value === 'chat' ? 'chat' : 'thread';
|
|
1604
|
-
p2pStatusEl.textContent = '';
|
|
1605
|
-
p2pStatusEl.className = 'oncall-status';
|
|
1606
|
-
p2pModeSel.disabled = true;
|
|
1607
|
-
try {
|
|
1608
|
-
const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/p2p-mode`, {
|
|
1609
|
-
method: 'PUT',
|
|
1610
|
-
headers: { 'content-type': 'application/json' },
|
|
1611
|
-
body: JSON.stringify({ p2pMode: mode }),
|
|
1612
|
-
});
|
|
1613
|
-
const body = await r.json().catch(() => ({}));
|
|
1614
|
-
if (r.ok && body.ok) {
|
|
1615
|
-
p2pStatusEl.textContent = `✓ ${t('botDefaults.cardPrefSaved')}`;
|
|
1616
|
-
p2pStatusEl.classList.add('hint-ok');
|
|
1617
|
-
const cached = cache.bots.find((bb) => bb.larkAppId === appId);
|
|
1618
|
-
if (cached)
|
|
1619
|
-
cached.p2pMode = body.p2pMode === 'chat' ? 'chat' : 'thread';
|
|
1620
|
-
}
|
|
1621
|
-
else {
|
|
1622
|
-
p2pStatusEl.textContent = `✗ ${body.error ?? r.status}`;
|
|
1623
|
-
p2pStatusEl.classList.add('hint-warn-inline');
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
catch (e) {
|
|
1627
|
-
p2pStatusEl.textContent = `✗ ${e?.message ?? e}`;
|
|
1628
|
-
p2pStatusEl.classList.add('hint-warn-inline');
|
|
1629
|
-
}
|
|
1630
|
-
finally {
|
|
1631
|
-
p2pModeSel.disabled = false;
|
|
1632
|
-
}
|
|
1633
|
-
});
|
|
1634
|
-
}
|
|
1635
|
-
// ── 内置技能注入模式 skillInjection select ────────────────────────────
|
|
1636
|
-
// '' = 清回机器级默认(botmux skills injection);global|prompt|off 显式覆盖。
|
|
1637
|
-
// 走 /api/bots/:appId/skill-injection → applyConfigField(与 /config 同路径)。
|
|
1638
|
-
const skillInjSel = card.querySelector('select[data-input=skillInjection]');
|
|
1639
|
-
const skillInjStatusEl = card.querySelector('[data-skill-injection-status]');
|
|
1640
|
-
if (skillInjSel && skillInjStatusEl) {
|
|
1641
|
-
skillInjSel.addEventListener('change', async () => {
|
|
1642
|
-
const mode = skillInjSel.value; // '' | 'global' | 'prompt' | 'off'
|
|
1643
|
-
skillInjStatusEl.textContent = '';
|
|
1644
|
-
skillInjStatusEl.className = 'oncall-status';
|
|
1645
|
-
skillInjSel.disabled = true;
|
|
1646
|
-
try {
|
|
1647
|
-
const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/skill-injection`, {
|
|
1648
|
-
method: 'PUT',
|
|
1649
|
-
headers: { 'content-type': 'application/json' },
|
|
1650
|
-
body: JSON.stringify({ skillInjection: mode }),
|
|
1651
|
-
});
|
|
1652
|
-
const body = await r.json().catch(() => ({}));
|
|
1653
|
-
if (r.ok && body.ok) {
|
|
1654
|
-
skillInjStatusEl.textContent = `✓ ${t('botDefaults.cardPrefSaved')}`;
|
|
1655
|
-
skillInjStatusEl.classList.add('hint-ok');
|
|
1656
|
-
const cached = cache.bots.find((bb) => bb.larkAppId === appId);
|
|
1657
|
-
if (cached)
|
|
1658
|
-
cached.skillInjection = body.skillInjection ?? null;
|
|
1659
|
-
}
|
|
1660
|
-
else {
|
|
1661
|
-
skillInjStatusEl.textContent = `✗ ${body.error ?? r.status}`;
|
|
1662
|
-
skillInjStatusEl.classList.add('hint-warn-inline');
|
|
1663
|
-
}
|
|
1664
|
-
}
|
|
1665
|
-
catch (e) {
|
|
1666
|
-
skillInjStatusEl.textContent = `✗ ${e?.message ?? e}`;
|
|
1667
|
-
skillInjStatusEl.classList.add('hint-warn-inline');
|
|
1668
|
-
}
|
|
1669
|
-
finally {
|
|
1670
|
-
skillInjSel.disabled = false;
|
|
1671
|
-
}
|
|
1672
|
-
});
|
|
1673
|
-
}
|
|
1674
|
-
// ── 普通群默认会话模式 regularGroupReplyMode select ─────────────────────
|
|
1675
|
-
// chat = 整群一个连续会话(默认);new-topic = 每条顶层 @ 开独立话题;
|
|
1676
|
-
// shared = 话题模式但复用同一个 session。走 card-prefs 路径。
|
|
1677
|
-
const regularGroupModeSel = card.querySelector('select[data-input=regularGroupMode]');
|
|
1678
|
-
const regularGroupStatusEl = card.querySelector('[data-regular-group-status]');
|
|
1679
|
-
if (regularGroupModeSel) {
|
|
1680
|
-
regularGroupModeSel.addEventListener('change', () => {
|
|
1681
|
-
putCardPref({ regularGroupReplyMode: regularGroupModeSel.value }, regularGroupModeSel, regularGroupStatusEl);
|
|
1682
|
-
});
|
|
1683
|
-
}
|
|
1684
|
-
// ── 群聊 @ 策略三档(bot-global)──────────────────────────────────────
|
|
1685
|
-
// always = 都需要 @(默认);topic = 仅 shared 话题内免 @;never = 都不需要 @。
|
|
1686
|
-
const mentionModeSel = card.querySelector('select[data-input=regularGroupMentionMode]');
|
|
1687
|
-
const mentionModeStatusEl = card.querySelector('[data-mention-mode-status]');
|
|
1688
|
-
if (mentionModeSel) {
|
|
1689
|
-
mentionModeSel.addEventListener('change', () => {
|
|
1690
|
-
putCardPref({ regularGroupMentionMode: mentionModeSel.value }, mentionModeSel, mentionModeStatusEl);
|
|
1691
|
-
});
|
|
1692
|
-
}
|
|
1693
|
-
// ── 文档订阅默认触发范围(bot-global)─────────────────────────────────
|
|
1694
|
-
// mention-only = 仅评论 @ 我才触发(默认);all = 所有新评论都触发。
|
|
1695
|
-
const docModeSel = card.querySelector('select[data-input=docSubscribeDefaultMode]');
|
|
1696
|
-
const docModeStatusEl = card.querySelector('[data-doc-subscribe-mode-status]');
|
|
1697
|
-
if (docModeSel) {
|
|
1698
|
-
docModeSel.addEventListener('change', () => {
|
|
1699
|
-
putCardPref({ docSubscribeDefaultMode: docModeSel.value }, docModeSel, docModeStatusEl);
|
|
1700
|
-
});
|
|
1701
|
-
}
|
|
1702
|
-
// ── 替身模式 substituteMode ─────────────────────────────────────────
|
|
1703
|
-
const substituteEnabledCb = card.querySelector('input[data-action=toggle-substitute-mode]');
|
|
1704
|
-
const substituteDisclosureSel = card.querySelector('select[data-input=substituteDisclosure]');
|
|
1705
|
-
const substituteTargetsTa = card.querySelector('textarea[data-input=substituteTargets]');
|
|
1706
|
-
const substituteSaveBtn = card.querySelector('button[data-action=save-substitute-mode]');
|
|
1707
|
-
const substituteOffBtn = card.querySelector('button[data-action=off-substitute-mode]');
|
|
1708
|
-
const substituteStatusEl = card.querySelector('[data-substitute-status]');
|
|
1709
|
-
const substituteChipsEl = card.querySelector('[data-substitute-chips]');
|
|
1710
|
-
async function putSubstituteMode(body, btn) {
|
|
1711
|
-
if (!substituteStatusEl)
|
|
1712
|
-
return;
|
|
1713
|
-
substituteStatusEl.textContent = t('botDefaults.substituteResolving');
|
|
1714
|
-
substituteStatusEl.className = 'oncall-status';
|
|
1715
|
-
btn.disabled = true;
|
|
1716
|
-
try {
|
|
1717
|
-
const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/substitute-mode`, {
|
|
1718
|
-
method: 'PUT',
|
|
1719
|
-
headers: { 'content-type': 'application/json' },
|
|
1720
|
-
body: JSON.stringify(body),
|
|
1721
|
-
});
|
|
1722
|
-
const resp = await r.json().catch(() => ({}));
|
|
1723
|
-
// Resolution chips render on success AND failure so the user can always
|
|
1724
|
-
// see which entries resolved to a person and which didn't.
|
|
1725
|
-
if (substituteChipsEl)
|
|
1726
|
-
substituteChipsEl.innerHTML = substituteChipsFromResolution(resp.resolution);
|
|
1727
|
-
if (r.ok && resp.ok) {
|
|
1728
|
-
substituteStatusEl.textContent = `✓ ${t('botDefaults.cardPrefSaved')}`;
|
|
1729
|
-
substituteStatusEl.classList.add('hint-ok');
|
|
1730
|
-
const cached = cache.bots.find((bb) => bb.larkAppId === appId);
|
|
1731
|
-
if (cached)
|
|
1732
|
-
cached.substituteMode = resp.substituteMode ?? null;
|
|
1733
|
-
if (substituteEnabledCb)
|
|
1734
|
-
substituteEnabledCb.checked = !!resp.substituteMode?.enabled;
|
|
1735
|
-
if (substituteDisclosureSel)
|
|
1736
|
-
substituteDisclosureSel.value = resp.substituteMode?.disclosure === 'none' ? 'none' : 'prefix';
|
|
1737
|
-
// Re-render the editor from the resolution report (not the stored
|
|
1738
|
-
// config): a disabled save keeps the list, and failed entries stay
|
|
1739
|
-
// visible so they can be corrected instead of silently vanishing.
|
|
1740
|
-
if (substituteTargetsTa)
|
|
1741
|
-
substituteTargetsTa.value = editorTextFromResolution(resp.resolution);
|
|
1742
|
-
}
|
|
1743
|
-
else {
|
|
1744
|
-
const reason = resp.error === 'targets_required'
|
|
1745
|
-
? t('botDefaults.substituteTargetsRequired')
|
|
1746
|
-
: (resp.error ?? r.status);
|
|
1747
|
-
substituteStatusEl.textContent = `✗ ${reason}`;
|
|
1748
|
-
substituteStatusEl.classList.add('hint-warn-inline');
|
|
1749
|
-
}
|
|
1750
|
-
}
|
|
1751
|
-
catch (e) {
|
|
1752
|
-
substituteStatusEl.textContent = `✗ ${e?.message ?? e}`;
|
|
1753
|
-
substituteStatusEl.classList.add('hint-warn-inline');
|
|
1754
|
-
}
|
|
1755
|
-
finally {
|
|
1756
|
-
btn.disabled = false;
|
|
1757
|
-
}
|
|
1758
|
-
}
|
|
1759
|
-
if (substituteSaveBtn && substituteTargetsTa && substituteDisclosureSel && substituteEnabledCb) {
|
|
1760
|
-
substituteSaveBtn.addEventListener('click', () => {
|
|
1761
|
-
if (!substituteStatusEl)
|
|
1762
|
-
return;
|
|
1763
|
-
const { targets, invalid } = parseSubstituteTargets(substituteTargetsTa.value);
|
|
1764
|
-
if (invalid.length) {
|
|
1765
|
-
substituteStatusEl.textContent = `✗ ${t('botDefaults.substituteTargetsInvalid')}: ${invalid.join('; ')}`;
|
|
1766
|
-
substituteStatusEl.className = 'oncall-status hint-warn-inline';
|
|
1767
|
-
return;
|
|
1768
|
-
}
|
|
1769
|
-
putSubstituteMode({
|
|
1770
|
-
enabled: substituteEnabledCb.checked,
|
|
1771
|
-
targets,
|
|
1772
|
-
disclosure: substituteDisclosureSel.value === 'none' ? 'none' : 'prefix',
|
|
1773
|
-
}, substituteSaveBtn);
|
|
1774
|
-
});
|
|
1775
|
-
}
|
|
1776
|
-
if (substituteOffBtn) {
|
|
1777
|
-
substituteOffBtn.addEventListener('click', () => {
|
|
1778
|
-
putSubstituteMode({ enabled: false, targets: [] }, substituteOffBtn);
|
|
1779
|
-
});
|
|
1780
|
-
}
|
|
1781
|
-
// ── Team role (one role per bot, cross-chat) ──────────────────────────
|
|
1782
|
-
const roleTextarea = card.querySelector('textarea[data-input=teamRole]');
|
|
1783
|
-
const roleSaveBtn = card.querySelector('button[data-action=save-role]');
|
|
1784
|
-
const roleDeleteBtn = card.querySelector('button[data-action=delete-role]');
|
|
1785
|
-
const roleStatusEl = card.querySelector('[data-role-status]');
|
|
1786
|
-
if (roleTextarea && roleSaveBtn && roleDeleteBtn && roleStatusEl) {
|
|
1787
|
-
const roleUrl = `/api/team/local-bots/${encodeURIComponent(appId)}/role`;
|
|
1788
|
-
const cached = cache.bots.find((bb) => bb.larkAppId === appId);
|
|
1789
|
-
// Until the role is loaded, the textarea AND both buttons render
|
|
1790
|
-
// disabled. This is load-bearing: an empty not-yet-loaded textarea
|
|
1791
|
-
// saved as "" is treated as a DELETE by the server (federation-spoke-api
|
|
1792
|
-
// role PUT), so a mis-click during a slow load would silently wipe an
|
|
1793
|
-
// existing role. We only enable the editor once GET has returned.
|
|
1794
|
-
function enableLiveEditor(value) {
|
|
1795
|
-
const live = listEl.querySelector(`.bd-card[data-appid="${CSS.escape(appId)}"]`);
|
|
1796
|
-
if (!live)
|
|
1797
|
-
return; // filtered out by search — next render draws it enabled from cache
|
|
1798
|
-
const ta = live.querySelector('textarea[data-input=teamRole]');
|
|
1799
|
-
const sv = live.querySelector('button[data-action=save-role]');
|
|
1800
|
-
const dl = live.querySelector('button[data-action=delete-role]');
|
|
1801
|
-
if (ta) {
|
|
1802
|
-
ta.value = value;
|
|
1803
|
-
ta.disabled = false;
|
|
1804
|
-
}
|
|
1805
|
-
if (sv)
|
|
1806
|
-
sv.disabled = false;
|
|
1807
|
-
if (dl)
|
|
1808
|
-
dl.disabled = false;
|
|
1809
|
-
}
|
|
1810
|
-
// Lazily load the role ONCE per bot, then stash it onto the snapshot
|
|
1811
|
-
// (cached.teamRole) so later re-renders — one per search keystroke —
|
|
1812
|
-
// render from cache instead of re-fetching. The teamRoleLoading sentinel
|
|
1813
|
-
// guards against a re-render firing a second concurrent GET while the
|
|
1814
|
-
// first is still in flight. enableLiveEditor re-queries the *current*
|
|
1815
|
-
// DOM so a mid-load re-render doesn't leave a stale (detached) textarea
|
|
1816
|
-
// stuck disabled.
|
|
1817
|
-
if (cached && typeof cached.teamRole !== 'string' && !cached.teamRoleLoading) {
|
|
1818
|
-
cached.teamRoleLoading = true;
|
|
1819
|
-
(async () => {
|
|
1820
|
-
try {
|
|
1821
|
-
const r = await fetch(roleUrl);
|
|
1822
|
-
const body = await r.json().catch(() => ({}));
|
|
1823
|
-
if (r.ok && body.ok) {
|
|
1824
|
-
cached.teamRole = body.role ?? '';
|
|
1825
|
-
enableLiveEditor(cached.teamRole);
|
|
1826
|
-
}
|
|
1827
|
-
else {
|
|
1828
|
-
roleStatusEl.textContent = `✗ ${t('botDefaults.roleLoadErr')}: ${body.error ?? r.status}`;
|
|
1829
|
-
roleStatusEl.classList.add('hint-warn-inline');
|
|
1830
|
-
}
|
|
1831
|
-
}
|
|
1832
|
-
catch (e) {
|
|
1833
|
-
roleStatusEl.textContent = `✗ ${t('botDefaults.roleLoadErr')}: ${e?.message ?? e}`;
|
|
1834
|
-
roleStatusEl.classList.add('hint-warn-inline');
|
|
1835
|
-
}
|
|
1836
|
-
finally {
|
|
1837
|
-
cached.teamRoleLoading = false;
|
|
1838
|
-
}
|
|
1839
|
-
})();
|
|
1840
|
-
}
|
|
1841
|
-
// PUT the role ('' = delete on the server). `deleted` picks the success
|
|
1842
|
-
// toast; both buttons share this path. Server trims + deletes on empty,
|
|
1843
|
-
// so we mirror the stored value into the cache for consistent re-renders.
|
|
1844
|
-
async function putRole(role, btn, deleted) {
|
|
1845
|
-
if (!roleStatusEl)
|
|
1846
|
-
return;
|
|
1847
|
-
// Defense-in-depth: never PUT before the role is loaded (would risk a
|
|
1848
|
-
// ""-as-delete). The buttons render disabled until then, but guard the
|
|
1849
|
-
// entry too in case of a stale handler firing.
|
|
1850
|
-
if (!cached || typeof cached.teamRole !== 'string')
|
|
1851
|
-
return;
|
|
1852
|
-
roleStatusEl.textContent = '';
|
|
1853
|
-
roleStatusEl.className = 'oncall-status';
|
|
1854
|
-
roleSaveBtn.disabled = true;
|
|
1855
|
-
roleDeleteBtn.disabled = true;
|
|
1856
|
-
try {
|
|
1857
|
-
const r = await fetch(roleUrl, {
|
|
1858
|
-
method: 'PUT',
|
|
1859
|
-
headers: { 'content-type': 'application/json' },
|
|
1860
|
-
body: JSON.stringify({ role }),
|
|
1861
|
-
});
|
|
1862
|
-
const body = await r.json().catch(() => ({}));
|
|
1863
|
-
if (r.ok && body.ok) {
|
|
1864
|
-
if (cached)
|
|
1865
|
-
cached.teamRole = role.trim();
|
|
1866
|
-
roleStatusEl.textContent = `✓ ${deleted ? t('botDefaults.roleDeleted') : t('botDefaults.roleSaved')}`;
|
|
1867
|
-
roleStatusEl.classList.add('hint-ok');
|
|
1868
|
-
}
|
|
1869
|
-
else {
|
|
1870
|
-
roleStatusEl.textContent = `✗ ${body.error ?? r.status}`;
|
|
1871
|
-
roleStatusEl.classList.add('hint-warn-inline');
|
|
1872
|
-
}
|
|
1873
|
-
}
|
|
1874
|
-
catch (e) {
|
|
1875
|
-
roleStatusEl.textContent = `✗ ${e?.message ?? e}`;
|
|
1876
|
-
roleStatusEl.classList.add('hint-warn-inline');
|
|
1877
|
-
}
|
|
1878
|
-
finally {
|
|
1879
|
-
roleSaveBtn.disabled = false;
|
|
1880
|
-
roleDeleteBtn.disabled = false;
|
|
1881
|
-
}
|
|
1882
|
-
}
|
|
1883
|
-
roleSaveBtn.addEventListener('click', () => putRole(roleTextarea.value, roleSaveBtn, false));
|
|
1884
|
-
roleDeleteBtn.addEventListener('click', () => {
|
|
1885
|
-
roleTextarea.value = '';
|
|
1886
|
-
putRole('', roleDeleteBtn, true);
|
|
1887
|
-
});
|
|
1888
|
-
}
|
|
1889
|
-
// ── 授权偏好:自动申请卡 + 命令限制开关 + 默认消息额度 ──────────────
|
|
1890
|
-
const autoGrantCardCb = card.querySelector('input[data-action=toggle-auto-grant-card]');
|
|
1891
|
-
const restrictCb = card.querySelector('input[data-action=toggle-restrict-grant]');
|
|
1892
|
-
const quotaInput = card.querySelector('input[data-input=quotaLimit]');
|
|
1893
|
-
const quotaSaveBtn = card.querySelector('button[data-action=save-quota]');
|
|
1894
|
-
const quotaOffBtn = card.querySelector('button[data-action=off-quota]');
|
|
1895
|
-
const grantStatusEl = card.querySelector('[data-grant-status]');
|
|
1896
|
-
const quotaStateEl = card.querySelector('[data-quota-state]');
|
|
1897
|
-
// PUT a partial grant-prefs patch ({ autoGrantRequestCards? },
|
|
1898
|
-
// { restrictGrantCommands? } and/or { messageQuotaDefaultLimit: number|null }).
|
|
1899
|
-
// Mirrors putCardPref.
|
|
1900
|
-
async function putGrantPref(patch, selfEl) {
|
|
1901
|
-
if (!grantStatusEl)
|
|
1902
|
-
return;
|
|
1903
|
-
grantStatusEl.textContent = '';
|
|
1904
|
-
grantStatusEl.className = 'oncall-status';
|
|
1905
|
-
selfEl.disabled = true;
|
|
1906
|
-
try {
|
|
1907
|
-
const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/grant-prefs`, {
|
|
1908
|
-
method: 'PUT',
|
|
1909
|
-
headers: { 'content-type': 'application/json' },
|
|
1910
|
-
body: JSON.stringify(patch),
|
|
1911
|
-
});
|
|
1912
|
-
const body = await r.json().catch(() => ({}));
|
|
1913
|
-
if (r.ok && body.ok) {
|
|
1914
|
-
grantStatusEl.textContent = `✓ ${t('botDefaults.cardPrefSaved')}`;
|
|
1915
|
-
grantStatusEl.classList.add('hint-ok');
|
|
1916
|
-
const next = typeof body.messageQuotaDefaultLimit === 'number' ? body.messageQuotaDefaultLimit : null;
|
|
1917
|
-
const cached = cache.bots.find((bb) => bb.larkAppId === appId);
|
|
1918
|
-
if (cached) {
|
|
1919
|
-
cached.autoGrantRequestCards = body.autoGrantRequestCards !== false;
|
|
1920
|
-
cached.restrictGrantCommands = body.restrictGrantCommands === true;
|
|
1921
|
-
cached.messageQuotaDefaultLimit = next;
|
|
1922
|
-
}
|
|
1923
|
-
if (quotaStateEl)
|
|
1924
|
-
quotaStateEl.textContent = quotaStateLabel(next);
|
|
1925
|
-
if (quotaInput && 'messageQuotaDefaultLimit' in patch) {
|
|
1926
|
-
quotaInput.value = next == null ? '' : String(next);
|
|
1927
|
-
}
|
|
1928
|
-
}
|
|
1929
|
-
else {
|
|
1930
|
-
grantStatusEl.textContent = `✗ ${body.error ?? r.status}`;
|
|
1931
|
-
grantStatusEl.classList.add('hint-warn-inline');
|
|
1932
|
-
}
|
|
1933
|
-
}
|
|
1934
|
-
catch (e) {
|
|
1935
|
-
grantStatusEl.textContent = `✗ ${e?.message ?? e}`;
|
|
1936
|
-
grantStatusEl.classList.add('hint-warn-inline');
|
|
1937
|
-
}
|
|
1938
|
-
finally {
|
|
1939
|
-
selfEl.disabled = false;
|
|
1940
|
-
}
|
|
1941
|
-
}
|
|
1942
|
-
if (autoGrantCardCb) {
|
|
1943
|
-
autoGrantCardCb.addEventListener('change', () => {
|
|
1944
|
-
putGrantPref({ autoGrantRequestCards: autoGrantCardCb.checked }, autoGrantCardCb);
|
|
1945
|
-
});
|
|
1946
|
-
}
|
|
1947
|
-
if (restrictCb) {
|
|
1948
|
-
restrictCb.addEventListener('change', () => {
|
|
1949
|
-
putGrantPref({ restrictGrantCommands: restrictCb.checked }, restrictCb);
|
|
1950
|
-
});
|
|
1951
|
-
}
|
|
1952
|
-
if (quotaInput && quotaSaveBtn) {
|
|
1953
|
-
quotaSaveBtn.addEventListener('click', () => {
|
|
1954
|
-
const raw = quotaInput.value.trim();
|
|
1955
|
-
if (raw === '') {
|
|
1956
|
-
putGrantPref({ messageQuotaDefaultLimit: null }, quotaSaveBtn);
|
|
1957
|
-
return;
|
|
1958
|
-
} // 空=关闭
|
|
1959
|
-
// 只认纯正整数 token(拒 1e2 / 1.0 / 01),与 /grant @x N 的数字语义一致。
|
|
1960
|
-
if (!/^[1-9]\d*$/.test(raw)) {
|
|
1961
|
-
if (grantStatusEl) {
|
|
1962
|
-
grantStatusEl.textContent = `✗ ${t('botDefaults.quotaInvalid')}`;
|
|
1963
|
-
grantStatusEl.className = 'oncall-status hint-warn-inline';
|
|
1964
|
-
}
|
|
1965
|
-
return;
|
|
1966
|
-
}
|
|
1967
|
-
putGrantPref({ messageQuotaDefaultLimit: Number(raw) }, quotaSaveBtn);
|
|
1968
|
-
});
|
|
1969
|
-
}
|
|
1970
|
-
if (quotaInput && quotaOffBtn) {
|
|
1971
|
-
quotaOffBtn.addEventListener('click', () => {
|
|
1972
|
-
quotaInput.value = '';
|
|
1973
|
-
putGrantPref({ messageQuotaDefaultLimit: null }, quotaOffBtn);
|
|
1974
|
-
});
|
|
1975
|
-
}
|
|
1976
|
-
// ── 最大同时活跃会话数 maxLiveWorkers(空=回落默认 30) ──────────────────
|
|
1977
|
-
const capInput = card.querySelector('input[data-input=maxLiveWorkers]');
|
|
1978
|
-
const capSaveBtn = card.querySelector('button[data-action=save-session-cap]');
|
|
1979
|
-
const capOffBtn = card.querySelector('button[data-action=off-session-cap]');
|
|
1980
|
-
const capStatusEl = card.querySelector('[data-session-cap-status]');
|
|
1981
|
-
const capStateEl = card.querySelector('[data-session-cap-state]');
|
|
1982
|
-
// PUT { maxLiveWorkers: number | null } to the bot's daemon (via the
|
|
1983
|
-
// dashboard proxy). null = unlimited. Mirrors putGrantPref.
|
|
1984
|
-
async function putMaxLiveWorkers(value, selfEl) {
|
|
1985
|
-
if (!capStatusEl)
|
|
1986
|
-
return;
|
|
1987
|
-
capStatusEl.textContent = '';
|
|
1988
|
-
capStatusEl.className = 'oncall-status';
|
|
1989
|
-
selfEl.disabled = true;
|
|
1990
|
-
try {
|
|
1991
|
-
const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/max-live-workers`, {
|
|
1992
|
-
method: 'PUT',
|
|
1993
|
-
headers: { 'content-type': 'application/json' },
|
|
1994
|
-
body: JSON.stringify({ maxLiveWorkers: value }),
|
|
1995
|
-
});
|
|
1996
|
-
const body = await r.json().catch(() => ({}));
|
|
1997
|
-
if (r.ok && body.ok) {
|
|
1998
|
-
capStatusEl.textContent = `✓ ${t('botDefaults.cardPrefSaved')}`;
|
|
1999
|
-
capStatusEl.classList.add('hint-ok');
|
|
2000
|
-
const next = typeof body.maxLiveWorkers === 'number' ? body.maxLiveWorkers : null;
|
|
2001
|
-
const cached = cache.bots.find((bb) => bb.larkAppId === appId);
|
|
2002
|
-
if (cached)
|
|
2003
|
-
cached.maxLiveWorkers = next;
|
|
2004
|
-
if (capStateEl)
|
|
2005
|
-
capStateEl.textContent = sessionCapStateLabel(next);
|
|
2006
|
-
if (capInput)
|
|
2007
|
-
capInput.value = next == null ? '' : String(next);
|
|
2008
|
-
}
|
|
2009
|
-
else {
|
|
2010
|
-
capStatusEl.textContent = `✗ ${body.error ?? r.status}`;
|
|
2011
|
-
capStatusEl.classList.add('hint-warn-inline');
|
|
2012
|
-
}
|
|
2013
|
-
}
|
|
2014
|
-
catch (e) {
|
|
2015
|
-
capStatusEl.textContent = `✗ ${e?.message ?? e}`;
|
|
2016
|
-
capStatusEl.classList.add('hint-warn-inline');
|
|
2017
|
-
}
|
|
2018
|
-
finally {
|
|
2019
|
-
selfEl.disabled = false;
|
|
2020
|
-
}
|
|
2021
|
-
}
|
|
2022
|
-
if (capInput && capSaveBtn) {
|
|
2023
|
-
capSaveBtn.addEventListener('click', () => {
|
|
2024
|
-
const raw = capInput.value.trim();
|
|
2025
|
-
if (raw === '') {
|
|
2026
|
-
putMaxLiveWorkers(null, capSaveBtn);
|
|
2027
|
-
return;
|
|
2028
|
-
} // 空=清回默认 30
|
|
2029
|
-
// 只认纯正整数 token(拒 1e2 / 1.0 / 01),与额度输入同口径。
|
|
2030
|
-
if (!/^[1-9]\d*$/.test(raw)) {
|
|
2031
|
-
if (capStatusEl) {
|
|
2032
|
-
capStatusEl.textContent = `✗ ${t('botDefaults.maxLiveWorkersInvalid')}`;
|
|
2033
|
-
capStatusEl.className = 'oncall-status hint-warn-inline';
|
|
2034
|
-
}
|
|
2035
|
-
return;
|
|
2036
|
-
}
|
|
2037
|
-
putMaxLiveWorkers(Number(raw), capSaveBtn);
|
|
2038
|
-
});
|
|
2039
|
-
}
|
|
2040
|
-
if (capInput && capOffBtn) {
|
|
2041
|
-
capOffBtn.addEventListener('click', () => {
|
|
2042
|
-
capInput.value = '';
|
|
2043
|
-
putMaxLiveWorkers(null, capOffBtn);
|
|
2044
|
-
});
|
|
2045
|
-
}
|
|
2046
|
-
// ── 启动命令 startupCommands(逗号/换行分隔;空=清除,不发任何命令) ──────────
|
|
2047
|
-
const startupEl = card.querySelector('textarea[data-input=startupCommands]');
|
|
2048
|
-
const startupSaveBtn = card.querySelector('button[data-action=save-startup-commands]');
|
|
2049
|
-
const startupStatusEl = card.querySelector('[data-startup-commands-status]');
|
|
2050
|
-
if (startupEl && startupSaveBtn) {
|
|
2051
|
-
startupSaveBtn.addEventListener('click', async () => {
|
|
2052
|
-
if (!startupStatusEl)
|
|
2053
|
-
return;
|
|
2054
|
-
startupStatusEl.textContent = '';
|
|
2055
|
-
startupStatusEl.className = 'oncall-status';
|
|
2056
|
-
startupSaveBtn.disabled = true;
|
|
2057
|
-
try {
|
|
2058
|
-
const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/startup-commands`, {
|
|
2059
|
-
method: 'PUT',
|
|
2060
|
-
headers: { 'content-type': 'application/json' },
|
|
2061
|
-
body: JSON.stringify({ startupCommands: startupEl.value }),
|
|
2062
|
-
});
|
|
2063
|
-
const body = await r.json().catch(() => ({}));
|
|
2064
|
-
if (r.ok && body.ok) {
|
|
2065
|
-
startupStatusEl.textContent = `✓ ${t('botDefaults.cardPrefSaved')}`;
|
|
2066
|
-
startupStatusEl.classList.add('hint-ok');
|
|
2067
|
-
// Server returns the normalized, newline-joined list — reflect it
|
|
2068
|
-
// back so the textarea shows exactly what was persisted.
|
|
2069
|
-
const next = typeof body.startupCommands === 'string' ? body.startupCommands : '';
|
|
2070
|
-
startupEl.value = next;
|
|
2071
|
-
const cached = cache.bots.find((bb) => bb.larkAppId === appId);
|
|
2072
|
-
if (cached)
|
|
2073
|
-
cached.startupCommands = next;
|
|
2074
|
-
}
|
|
2075
|
-
else {
|
|
2076
|
-
startupStatusEl.textContent = `✗ ${body.error ?? r.status}`;
|
|
2077
|
-
startupStatusEl.classList.add('hint-warn-inline');
|
|
2078
|
-
}
|
|
2079
|
-
}
|
|
2080
|
-
catch (e) {
|
|
2081
|
-
startupStatusEl.textContent = `✗ ${e?.message ?? e}`;
|
|
2082
|
-
startupStatusEl.classList.add('hint-warn-inline');
|
|
2083
|
-
}
|
|
2084
|
-
finally {
|
|
2085
|
-
startupSaveBtn.disabled = false;
|
|
2086
|
-
}
|
|
2087
|
-
});
|
|
2088
|
-
}
|
|
2089
|
-
// ── 启动 shell launchShell(shell 名或绝对路径;空=清除→回 $SHELL) ──────
|
|
2090
|
-
const launchShellEl = card.querySelector('input[data-input=launchShell]');
|
|
2091
|
-
const launchShellSaveBtn = card.querySelector('button[data-action=save-launch-shell]');
|
|
2092
|
-
const launchShellStatusEl = card.querySelector('[data-launch-shell-status]');
|
|
2093
|
-
if (launchShellEl && launchShellSaveBtn) {
|
|
2094
|
-
launchShellSaveBtn.addEventListener('click', async () => {
|
|
2095
|
-
if (!launchShellStatusEl)
|
|
2096
|
-
return;
|
|
2097
|
-
launchShellStatusEl.textContent = '';
|
|
2098
|
-
launchShellStatusEl.className = 'oncall-status';
|
|
2099
|
-
launchShellSaveBtn.disabled = true;
|
|
2100
|
-
try {
|
|
2101
|
-
const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/launch-shell`, {
|
|
2102
|
-
method: 'PUT',
|
|
2103
|
-
headers: { 'content-type': 'application/json' },
|
|
2104
|
-
body: JSON.stringify({ launchShell: launchShellEl.value }),
|
|
2105
|
-
});
|
|
2106
|
-
const body = await r.json().catch(() => ({}));
|
|
2107
|
-
if (r.ok && body.ok) {
|
|
2108
|
-
launchShellStatusEl.textContent = `✓ ${t('botDefaults.cardPrefSaved')}`;
|
|
2109
|
-
launchShellStatusEl.classList.add('hint-ok');
|
|
2110
|
-
const next = typeof body.launchShell === 'string' ? body.launchShell : '';
|
|
2111
|
-
launchShellEl.value = next;
|
|
2112
|
-
const cached = cache.bots.find((bb) => bb.larkAppId === appId);
|
|
2113
|
-
if (cached)
|
|
2114
|
-
cached.launchShell = next;
|
|
2115
|
-
}
|
|
2116
|
-
else {
|
|
2117
|
-
launchShellStatusEl.textContent = `✗ ${body.error ?? r.status}`;
|
|
2118
|
-
launchShellStatusEl.classList.add('hint-warn-inline');
|
|
2119
|
-
}
|
|
2120
|
-
}
|
|
2121
|
-
catch (e) {
|
|
2122
|
-
launchShellStatusEl.textContent = `✗ ${e?.message ?? e}`;
|
|
2123
|
-
launchShellStatusEl.classList.add('hint-warn-inline');
|
|
2124
|
-
}
|
|
2125
|
-
finally {
|
|
2126
|
-
launchShellSaveBtn.disabled = false;
|
|
2127
|
-
}
|
|
2128
|
-
});
|
|
2129
|
-
}
|
|
2130
|
-
// ── 环境变量 env(JSON 对象;空=清除) ──────────────────────────────
|
|
2131
|
-
const envEl = card.querySelector('textarea[data-input=env]');
|
|
2132
|
-
const envSaveBtn = card.querySelector('button[data-action=save-env]');
|
|
2133
|
-
const envStatusEl = card.querySelector('[data-env-status]');
|
|
2134
|
-
if (envEl && envSaveBtn) {
|
|
2135
|
-
envSaveBtn.addEventListener('click', async () => {
|
|
2136
|
-
if (!envStatusEl)
|
|
2137
|
-
return;
|
|
2138
|
-
envStatusEl.textContent = '';
|
|
2139
|
-
envStatusEl.className = 'oncall-status';
|
|
2140
|
-
envSaveBtn.disabled = true;
|
|
2141
|
-
try {
|
|
2142
|
-
const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/env`, {
|
|
2143
|
-
method: 'PUT',
|
|
2144
|
-
headers: { 'content-type': 'application/json' },
|
|
2145
|
-
body: JSON.stringify({ env: envEl.value }),
|
|
2146
|
-
});
|
|
2147
|
-
const body = await r.json().catch(() => ({}));
|
|
2148
|
-
if (r.ok && body.ok) {
|
|
2149
|
-
envStatusEl.textContent = `✓ ${t('botDefaults.cardPrefSaved')}`;
|
|
2150
|
-
envStatusEl.classList.add('hint-ok');
|
|
2151
|
-
// Server returns the sanitized, pretty-printed JSON — reflect it
|
|
2152
|
-
// back so the textarea shows exactly what was persisted.
|
|
2153
|
-
const next = typeof body.env === 'string' ? body.env : '';
|
|
2154
|
-
envEl.value = next;
|
|
2155
|
-
const cached = cache.bots.find((bb) => bb.larkAppId === appId);
|
|
2156
|
-
if (cached)
|
|
2157
|
-
cached.env = next;
|
|
2158
|
-
}
|
|
2159
|
-
else {
|
|
2160
|
-
envStatusEl.textContent = `✗ ${body.error ?? r.status}`;
|
|
2161
|
-
envStatusEl.classList.add('hint-warn-inline');
|
|
2162
|
-
}
|
|
2163
|
-
}
|
|
2164
|
-
catch (e) {
|
|
2165
|
-
envStatusEl.textContent = `✗ ${e?.message ?? e}`;
|
|
2166
|
-
envStatusEl.classList.add('hint-warn-inline');
|
|
2167
|
-
}
|
|
2168
|
-
finally {
|
|
2169
|
-
envSaveBtn.disabled = false;
|
|
2170
|
-
}
|
|
2171
|
-
});
|
|
2172
|
-
}
|
|
2173
|
-
});
|
|
2174
|
-
}
|
|
2175
|
-
function safeRerender() {
|
|
2176
|
-
if (!disposed && readyToRender)
|
|
2177
|
-
rerender();
|
|
2178
|
-
}
|
|
2179
|
-
form.addEventListener('input', safeRerender);
|
|
2180
|
-
return () => {
|
|
2181
|
-
disposed = true;
|
|
2182
|
-
refreshBtn.onclick = null;
|
|
2183
|
-
form.removeEventListener('input', safeRerender);
|
|
2184
|
-
listEl.removeEventListener('click', listClickHandler);
|
|
2185
|
-
rosterEl.replaceChildren();
|
|
2186
|
-
listEl.replaceChildren();
|
|
2187
|
-
};
|
|
2188
|
-
}
|
|
2189
91
|
//# sourceMappingURL=bot-defaults.js.map
|