@xmanrui/dsh-im 4.22.0 → 4.24.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 +5 -4
- package/README.md +5 -4
- package/lib/client.js +2584 -1153
- package/lib/index.js +290 -298
- package/package.json +14 -2
- package/plugin-src/client/channel-card-meta.js +2 -0
- package/plugin-src/client/channel-logos.js +11 -0
- package/plugin-src/client/channels/dingtalk/api.js +5 -1
- package/plugin-src/client/channels/dingtalk/index.js +15 -13
- package/plugin-src/client/channels/email/api.js +52 -0
- package/plugin-src/client/channels/email/index.js +728 -0
- package/plugin-src/client/channels/email/styles.js +51 -0
- package/plugin-src/client/channels/feishu/api.js +4 -2
- package/plugin-src/client/channels/feishu/index.js +23 -7
- package/plugin-src/client/channels/imessage/index.js +2 -1
- package/plugin-src/client/channels/office/api.js +2 -0
- package/plugin-src/client/channels/office/index.js +6 -5
- package/plugin-src/client/channels/qq/api.js +7 -0
- package/plugin-src/client/channels/qq/index.js +16 -3
- package/plugin-src/client/channels/shared/token-api.js +8 -1
- package/plugin-src/client/channels/shared/token-channel.js +28 -3
- package/plugin-src/client/channels/slack/index.js +2 -1
- package/plugin-src/client/channels/telegram/index.js +3 -0
- package/plugin-src/client/channels/telegram/styles.js +12 -0
- package/plugin-src/client/channels/telegram/thinking-traces.js +25 -0
- package/plugin-src/client/channels/wecom/api.js +7 -1
- package/plugin-src/client/channels/wecom/index.js +16 -3
- package/plugin-src/client/channels/wecom-app/api.js +6 -1
- package/plugin-src/client/channels/wecom-app/index.js +16 -3
- package/plugin-src/client/channels/weixin/api.js +2 -1
- package/plugin-src/client/channels/weixin/connection-error.js +1 -71
- package/plugin-src/client/channels/whatsapp/api.js +5 -0
- package/plugin-src/client/channels/whatsapp/index.js +16 -3
- package/plugin-src/client/connection-error.js +87 -0
- package/plugin-src/client/global-settings.js +81 -1
- package/plugin-src/client/i18n.js +69 -1
- package/plugin-src/client/index.js +40 -4
- package/plugin-src/client/last-message-error.js +2 -1
- package/plugin-src/client/styles.js +8 -2
- package/plugin-src/host/build.mjs +4 -0
- package/plugin-src/host/channels/dingtalk/production.mjs +2 -0
- package/plugin-src/host/channels/dingtalk/rpc.mjs +7 -4
- package/plugin-src/host/channels/email/availability.mjs +60 -0
- package/plugin-src/host/channels/email/index.mjs +36 -0
- package/plugin-src/host/channels/email/production.mjs +58 -0
- package/plugin-src/host/channels/email/rpc.mjs +163 -0
- package/plugin-src/host/channels/feishu/production.mjs +3 -0
- package/plugin-src/host/channels/feishu/rpc.mjs +7 -4
- package/plugin-src/host/channels/imessage/rpc.mjs +13 -6
- package/plugin-src/host/channels/office/rpc.mjs +3 -1
- package/plugin-src/host/channels/qq/production.mjs +2 -0
- package/plugin-src/host/channels/qq/rpc.mjs +5 -3
- package/plugin-src/host/channels/shared/production.mjs +52 -5
- package/plugin-src/host/channels/shared/rpc.mjs +14 -3
- package/plugin-src/host/channels/shared/startup-error.mjs +4 -3
- package/plugin-src/host/channels/shared/startup.mjs +11 -6
- package/plugin-src/host/channels/shared/thinking-traces-rpc.mjs +11 -0
- package/plugin-src/host/channels/slack/production.mjs +2 -0
- package/plugin-src/host/channels/slack/rpc.mjs +5 -3
- package/plugin-src/host/channels/wecom/production.mjs +2 -0
- package/plugin-src/host/channels/wecom/rpc.mjs +5 -3
- package/plugin-src/host/channels/wecom-app/production.mjs +2 -0
- package/plugin-src/host/channels/wecom-app/rpc.mjs +5 -3
- package/plugin-src/host/channels/weixin/production.mjs +2 -0
- package/plugin-src/host/channels/whatsapp/production.mjs +2 -0
- package/plugin-src/host/channels/whatsapp/rpc.mjs +5 -3
- package/plugin-src/host/delivery-adapter.mjs +11 -0
- package/plugin-src/host/image-input-rpc.mjs +24 -0
- package/plugin-src/host/inbound-ttl-rpc.mjs +6 -1
- package/plugin-src/host/index.mjs +3 -0
- package/plugin-src/host/modern-harness-api.mjs +7 -2
- package/plugin-src/management-rpc.mjs +12 -2
- package/scripts/verify-package.mjs +13 -5
- package/src/channels/dingtalk/connection-error.mjs +5 -12
- package/src/channels/dingtalk/device-auth.mjs +4 -1
- package/src/channels/dingtalk/dingtalk-bridge.mjs +6 -4
- package/src/channels/dingtalk/dingtalk-controller.mjs +60 -45
- package/src/channels/dingtalk/dingtalk-runtime.mjs +20 -4
- package/src/channels/discord/discord-api.mjs +3 -3
- package/src/channels/discord/discord-runtime.mjs +18 -12
- package/src/channels/email/config-store.mjs +194 -0
- package/src/channels/email/email-api.mjs +20 -0
- package/src/channels/email/email-bridge.mjs +17 -0
- package/src/channels/email/email-controller.mjs +824 -0
- package/src/channels/email/email-runtime.mjs +699 -0
- package/src/channels/email/harness-client.mjs +7 -0
- package/src/channels/email/mail-format.mjs +91 -0
- package/src/channels/email/state-store.mjs +176 -0
- package/src/channels/email/transport.mjs +64 -0
- package/src/channels/email/transports/agent-mail.mjs +604 -0
- package/src/channels/email/transports/agently-cli.mjs +288 -0
- package/src/channels/email/transports/imap-smtp.mjs +165 -0
- package/src/channels/feishu/bridge.mjs +39 -11
- package/src/channels/feishu/feishu-channel.mjs +35 -0
- package/src/channels/feishu/feishu-runtime.mjs +15 -8
- package/src/channels/feishu/group-message-permission-manager.mjs +2 -1
- package/src/channels/feishu/live-cot.mjs +260 -0
- package/src/channels/feishu/message-utils.mjs +3 -2
- package/src/channels/feishu/multi-bot-controller.mjs +56 -47
- package/src/channels/feishu/registration-manager.mjs +8 -4
- package/src/channels/feishu/repair-manager.mjs +2 -1
- package/src/channels/feishu/slash-command-registry.mjs +17 -0
- package/src/channels/feishu/step-push-mode.mjs +10 -4
- package/src/channels/imessage/runtime.mjs +9 -5
- package/src/channels/office/office-controller.mjs +23 -12
- package/src/channels/office/office-runtime.mjs +6 -3
- package/src/channels/office/office-transport.mjs +1 -0
- package/src/channels/qq/qq-bridge.mjs +7 -5
- package/src/channels/qq/qq-controller.mjs +56 -42
- package/src/channels/qq/qq-runtime.mjs +16 -11
- package/src/channels/shared/bot-workspace-store.mjs +29 -10
- package/src/channels/shared/connection-error.mjs +191 -0
- package/src/channels/shared/connection-test.mjs +3 -1
- package/src/channels/shared/conversation-state-store.mjs +46 -1
- package/src/channels/shared/diagnostic-details.mjs +95 -0
- package/src/channels/shared/harness-client.mjs +287 -57
- package/src/channels/shared/i18n-en/diagnostics.mjs +55 -0
- package/src/channels/shared/i18n-en/email.mjs +25 -0
- package/src/channels/shared/i18n-en/image-input.mjs +17 -0
- package/src/channels/shared/i18n-en/shared-a.mjs +1 -0
- package/src/channels/shared/i18n-en/shared-c.mjs +1 -1
- package/src/channels/shared/i18n-en.mjs +6 -0
- package/src/channels/shared/image-input-policy.mjs +35 -0
- package/src/channels/shared/image-input-settings-store.mjs +60 -0
- package/src/channels/shared/image-input.mjs +124 -0
- package/src/channels/shared/image-prompt.mjs +15 -7
- package/src/channels/shared/message-failure.mjs +12 -0
- package/src/channels/shared/semantic/reply-reference.mjs +3 -3
- package/src/channels/shared/text-harness-bridge.mjs +150 -25
- package/src/channels/shared/token-bot-controller.mjs +52 -47
- package/src/channels/slack/slack-api.mjs +3 -3
- package/src/channels/slack/slack-controller.mjs +59 -51
- package/src/channels/slack/slack-runtime.mjs +18 -12
- package/src/channels/telegram/config-store.mjs +4 -1
- package/src/channels/telegram/telegram-api.mjs +2 -2
- package/src/channels/telegram/telegram-controller.mjs +13 -1
- package/src/channels/telegram/telegram-runtime.mjs +215 -14
- package/src/channels/wecom/wecom-bridge.mjs +15 -6
- package/src/channels/wecom/wecom-controller.mjs +51 -37
- package/src/channels/wecom/wecom-runtime.mjs +10 -6
- package/src/channels/wecom-app/wecom-app-bridge.mjs +7 -5
- package/src/channels/wecom-app/wecom-app-controller.mjs +50 -37
- package/src/channels/wecom-app/wecom-app-runtime.mjs +2 -1
- package/src/channels/weixin/connection-error.mjs +26 -92
- package/src/channels/weixin/diagnostic-details.mjs +1 -63
- package/src/channels/weixin/weixin-bridge.mjs +7 -5
- package/src/channels/whatsapp/whatsapp-controller.mjs +34 -17
- package/src/channels/whatsapp/whatsapp-runtime.mjs +35 -8
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export const EMAIL_STYLE_ID = 'xmanrui-dsh-im-email-settings';
|
|
2
|
+
const CSS = String.raw`
|
|
3
|
+
.dim-pageEmail { --ddt-accent: #2f6fd0; --ddt-accent-deep: #24559f; --ddt-accent-wash: #eef4fd; }
|
|
4
|
+
.dim-avatarEmail { color: #fff; background: linear-gradient(180deg, #6ea8f5 0%, #3d7fe0 50%, #24559f 100%); box-shadow: inset 0 1px 1px rgb(255 255 255 / 45%), 0 1px 3px rgb(31 35 41 / 12%); }
|
|
5
|
+
.dim-avatarEmail svg { display: block; }
|
|
6
|
+
.dim-emailPanel { padding: 20px; }
|
|
7
|
+
.dim-emailPanel p { color: var(--dsw-alias-label-secondary, #646a73); }
|
|
8
|
+
.dim-emailFields { display: grid; gap: 12px; margin: 16px 0; }
|
|
9
|
+
.dim-emailField { display: grid; gap: 6px; }
|
|
10
|
+
.dim-emailField > span { font-size: 13px; color: var(--dsw-alias-label-secondary, #646a73); }
|
|
11
|
+
.dim-emailField input, .dim-emailField select, .dim-emailField textarea {
|
|
12
|
+
padding: 8px 10px; border: 1px solid var(--dsw-alias-border, #dee0e3); border-radius: 6px;
|
|
13
|
+
background: var(--dsw-alias-bg-base, #fff); color: inherit; font: inherit;
|
|
14
|
+
}
|
|
15
|
+
.dim-emailField textarea { min-height: 76px; resize: vertical; }
|
|
16
|
+
.dim-emailHint { font-size: 12px; color: var(--dsw-alias-label-tertiary, #8f959e); }
|
|
17
|
+
.dim-emailGrid { display: grid; grid-template-columns: 1fr 120px; gap: 12px; }
|
|
18
|
+
.dim-emailBinding { margin-top: 20px; border-top: 1px solid var(--dsw-alias-border, #dee0e3); padding-top: 16px; }
|
|
19
|
+
.dim-emailBinding h4 { margin: 0 0 4px; font-size: 14px; }
|
|
20
|
+
.dim-emailBindingHead { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
|
21
|
+
/* The sender address wraps above its picker instead of squeezing the select
|
|
22
|
+
into a narrow column, so a long session title stays fully readable. */
|
|
23
|
+
.dim-emailBindingRow {
|
|
24
|
+
display: grid;
|
|
25
|
+
grid-template-columns: minmax(0, 1fr);
|
|
26
|
+
gap: 4px;
|
|
27
|
+
align-items: center;
|
|
28
|
+
padding: 8px 0;
|
|
29
|
+
border-top: 1px dashed var(--dsw-alias-border, #eef0f2);
|
|
30
|
+
}
|
|
31
|
+
.dim-emailBindingRow:first-of-type { border-top: 0; }
|
|
32
|
+
.dim-emailBindingSender { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; }
|
|
33
|
+
.dim-emailBindingRow select { width: 100%; }
|
|
34
|
+
/* Selects clip their own text; a minimum width keeps the chosen title legible. */
|
|
35
|
+
.dim-emailBinding select { min-width: 0; }
|
|
36
|
+
/* QR authorization block for the Agent mailbox. */
|
|
37
|
+
.dim-emailAuth { display: grid; gap: 8px; padding: 12px; border: 1px solid var(--dsw-alias-border, #dee0e3); border-radius: 8px; }
|
|
38
|
+
.dim-emailAuthPanel { display: grid; gap: 6px; }
|
|
39
|
+
.dim-emailAuthRow { display: grid; grid-template-columns: 72px minmax(0, 1fr); gap: 8px; align-items: center; font-size: 13px; }
|
|
40
|
+
.dim-emailAuthLink { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
41
|
+
/* The full title of the current choice, wrapping instead of clipping. */
|
|
42
|
+
.dim-emailBindingSelected { display: block; white-space: normal; overflow-wrap: anywhere; }
|
|
43
|
+
`;
|
|
44
|
+
export function installEmailStyles() {
|
|
45
|
+
if (typeof document === 'undefined') return () => {};
|
|
46
|
+
const existing = document.querySelector(`style[data-plugin-css="${EMAIL_STYLE_ID}"]`);
|
|
47
|
+
if (existing) return () => {};
|
|
48
|
+
const style = document.createElement('style'); style.dataset.plugin = '@xmanrui/dsh-im';
|
|
49
|
+
style.dataset.pluginCss = EMAIL_STYLE_ID; style.textContent = CSS; document.head.appendChild(style);
|
|
50
|
+
return () => style.remove();
|
|
51
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { diagnosticFields } from '../../../../src/channels/shared/diagnostic-details.mjs';
|
|
1
2
|
import { normalizeBotAlias } from '../../../../src/channels/shared/bot-alias.mjs';
|
|
2
3
|
/**
|
|
3
4
|
* Browser-safe contract for the Feishu Host plugin.
|
|
@@ -119,6 +120,7 @@ export function unwrapRpcResult(result) {
|
|
|
119
120
|
const message = optionalString(result.error?.message) ?? "飞书服务请求失败";
|
|
120
121
|
const error = new Error(message);
|
|
121
122
|
error.code = optionalString(result.error?.code) ?? "FEISHU_RPC_ERROR";
|
|
123
|
+
Object.assign(error, diagnosticFields(result.error));
|
|
122
124
|
throw error;
|
|
123
125
|
}
|
|
124
126
|
return result.value;
|
|
@@ -190,7 +192,7 @@ function normalizeError(value) {
|
|
|
190
192
|
if (!isRecord(value)) return undefined;
|
|
191
193
|
const message = optionalString(value.message);
|
|
192
194
|
if (!message) return undefined;
|
|
193
|
-
return { message, code: optionalString(value.code) };
|
|
195
|
+
return { ...diagnosticFields(value), message, code: optionalString(value.code) };
|
|
194
196
|
}
|
|
195
197
|
|
|
196
198
|
function authoritativeState(value, connected) {
|
|
@@ -386,7 +388,7 @@ export function presentError(error) {
|
|
|
386
388
|
const message = raw
|
|
387
389
|
.replace(/(client[_-]?secret|app[_-]?secret|secret|token)\s*[:=]\s*[^\s,;]+/gi, "$1=••••••")
|
|
388
390
|
.slice(0, 240);
|
|
389
|
-
return { message, code: optionalString(error?.code) };
|
|
391
|
+
return { message, code: optionalString(error?.code), ...diagnosticFields(error) };
|
|
390
392
|
}
|
|
391
393
|
|
|
392
394
|
export function formatRemaining(milliseconds) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ConnectionError, normalizeConnectionError } from '../../connection-error.js';
|
|
1
2
|
import { BotName } from '../../bot-alias.js';
|
|
2
3
|
import * as React from "react";
|
|
3
4
|
|
|
@@ -396,7 +397,7 @@ function ProvisionError({ error, provision, onRetry, onCancel, busy }) {
|
|
|
396
397
|
h("h3", null, repairing
|
|
397
398
|
? "权限与回调没有补全完成"
|
|
398
399
|
: grantingGroupMessages ? "群消息权限没有开通完成" : "新机器人没有添加完成"),
|
|
399
|
-
h(
|
|
400
|
+
h(ConnectionError, { error: error }),
|
|
400
401
|
error.code ? h("span", { className: "bxf-errorCode" }, error.code) : null,
|
|
401
402
|
h("div", { className: "bxf-actions dim-viewActions" },
|
|
402
403
|
h(Button, { kind: "primary", onClick: onRetry, disabled: busy },
|
|
@@ -465,7 +466,7 @@ function RemoveConfirmation({ bot, busy, onConfirm, onCancel }) {
|
|
|
465
466
|
);
|
|
466
467
|
}
|
|
467
468
|
|
|
468
|
-
/** One select for the step-push presentation
|
|
469
|
+
/** One select for the step-push presentation. */
|
|
469
470
|
function StepPushEditor({ value = false, mode = "post", disabled = false, onSave, onModeSave }) {
|
|
470
471
|
const titleId = React.useId();
|
|
471
472
|
const helpId = `${titleId}-help`;
|
|
@@ -495,7 +496,7 @@ function StepPushEditor({ value = false, mode = "post", disabled = false, onSave
|
|
|
495
496
|
if (value === true) await onSave?.(false);
|
|
496
497
|
return;
|
|
497
498
|
}
|
|
498
|
-
const nextMode =
|
|
499
|
+
const nextMode = ["streaming_card", "live_cot"].includes(next) ? next : "post";
|
|
499
500
|
// Enabling (or switching presentation) may need both writes; the flag
|
|
500
501
|
// must land before the mode so the runtime never sees a mode without
|
|
501
502
|
// step push enabled.
|
|
@@ -506,7 +507,9 @@ function StepPushEditor({ value = false, mode = "post", disabled = false, onSave
|
|
|
506
507
|
|
|
507
508
|
const helpText = current === "off"
|
|
508
509
|
? "适合日常问答:执行过程中不显示工具调用等中间步骤,只回复最终结果"
|
|
509
|
-
: current === "
|
|
510
|
+
: current === "live_cot"
|
|
511
|
+
? "使用飞书原生思考过程展示推理、工具调用与结果,最终答案单独发送"
|
|
512
|
+
: current === "streaming_card"
|
|
510
513
|
? "推荐长任务使用:过程与最终答案都在同一张卡片里实时更新,不刷屏"
|
|
511
514
|
: "每一步都单独发一条消息(含工具调用和过程说明);注意长任务会连续发送较多消息";
|
|
512
515
|
|
|
@@ -528,7 +531,7 @@ function StepPushEditor({ value = false, mode = "post", disabled = false, onSave
|
|
|
528
531
|
id: helpId,
|
|
529
532
|
className: "dim-presetTooltip",
|
|
530
533
|
role: "tooltip",
|
|
531
|
-
}, "
|
|
534
|
+
}, "设置任务执行过程的呈现方式:不显示、原生直播、实时卡片或逐步消息"))),
|
|
532
535
|
saving
|
|
533
536
|
? h("span", { className: "dim-feishuGroupControlStatus", role: "status" }, "保存中…")
|
|
534
537
|
: null),
|
|
@@ -540,6 +543,7 @@ function StepPushEditor({ value = false, mode = "post", disabled = false, onSave
|
|
|
540
543
|
onChange: change,
|
|
541
544
|
},
|
|
542
545
|
h("option", { value: "off" }, "不显示过程(只发送最终答案)"),
|
|
546
|
+
h("option", { value: "live_cot" }, "实时直播(飞书原生思考过程)"),
|
|
543
547
|
h("option", { value: "streaming_card" }, "实时过程卡(全程一张卡片动态更新)"),
|
|
544
548
|
h("option", { value: "post" }, "逐步直播(每一步单独发一条消息)")),
|
|
545
549
|
h("p", { className: "dim-feishuGroupHelp" }, helpText),
|
|
@@ -700,6 +704,7 @@ export function BotCard({
|
|
|
700
704
|
}, "移除接入")),
|
|
701
705
|
summary ? h("div", { className: "bxf-healthSummary dim-cardSummary", "data-error": actionError || connection.error ? "true" : undefined },
|
|
702
706
|
summary) : null,
|
|
707
|
+
(actionError || connection.error) ? h(ConnectionError, { error: actionError || connection.error, showMessage: false }) : null,
|
|
703
708
|
connection.lastMessageError ? h(LastMessageErrorSummary, {
|
|
704
709
|
className: "bxf-healthSummary",
|
|
705
710
|
error: connection.lastMessageError,
|
|
@@ -770,7 +775,7 @@ function PageError({ error, onRetry, busy }) {
|
|
|
770
775
|
h("div", { className: "bxf-error dim-inlineError", role: "alert" },
|
|
771
776
|
h("div", null,
|
|
772
777
|
h("h3", null, "无法读取飞书机器人"),
|
|
773
|
-
h(
|
|
778
|
+
h(ConnectionError, { error: error }),
|
|
774
779
|
error.code ? h("span", { className: "bxf-errorCode" }, error.code) : null,
|
|
775
780
|
h("div", { className: "bxf-actions dim-viewActions" },
|
|
776
781
|
h(Button, { kind: "primary", onClick: onRetry, disabled: busy },
|
|
@@ -812,6 +817,7 @@ export function mergeFeishuSnapshotState(
|
|
|
812
817
|
}
|
|
813
818
|
|
|
814
819
|
export function FeishuSettingsTab({ rpcCall }) {
|
|
820
|
+
const [operationError, setOperationError] = React.useState(null);
|
|
815
821
|
const [model, setModel] = React.useState({
|
|
816
822
|
phase: "loading",
|
|
817
823
|
revision: 0,
|
|
@@ -857,7 +863,16 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
857
863
|
}, [scheduleAnimationFrame]);
|
|
858
864
|
|
|
859
865
|
const invoke = React.useCallback(async (endpoint, payload = {}, signal) => {
|
|
860
|
-
|
|
866
|
+
const operation = !['connection.status', 'provision.poll', 'provision.begin', 'provision.cancel'].includes(endpoint);
|
|
867
|
+
if (operation && mountedRef.current) setOperationError(null);
|
|
868
|
+
try {
|
|
869
|
+
const value = unwrapRpcResult(await rpcCall(endpoint, payload, signal));
|
|
870
|
+
if (operation && mountedRef.current) setOperationError(value?.testMessage?.error ?? value?.warnings?.[0] ?? null);
|
|
871
|
+
return value;
|
|
872
|
+
} catch (error) {
|
|
873
|
+
if (endpoint === 'bot.delete' && mountedRef.current && !signal?.aborted && error?.name !== 'AbortError') setOperationError(normalizeConnectionError(error));
|
|
874
|
+
throw error;
|
|
875
|
+
}
|
|
861
876
|
}, [rpcCall]);
|
|
862
877
|
|
|
863
878
|
const mergeSnapshot = React.useCallback((snapshot, { restoreProvisioning = false } = {}) => {
|
|
@@ -1490,6 +1505,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
1490
1505
|
}, h(AgentPresetCatalogContext.Provider, {
|
|
1491
1506
|
value: model.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
|
|
1492
1507
|
}, h("section", { className: "bxf-page dim-channelPage", "aria-label": "飞书机器人设置" },
|
|
1508
|
+
operationError ? h(ConnectionError, { error: operationError }) : null,
|
|
1493
1509
|
h(Heading, {
|
|
1494
1510
|
totals: model.totals,
|
|
1495
1511
|
onAdd: () => void startProvisioning(),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ConnectionError } from '../../connection-error.js';
|
|
1
2
|
import * as React from 'react';
|
|
2
3
|
import { IMessageLogoGlyph } from '../../channel-logos.js';
|
|
3
4
|
import { createTokenChannelSettings } from '../shared/token-channel.js';
|
|
@@ -21,7 +22,7 @@ function PermissionPanel({ permissions, busy, error, onSubmit, onCancel }) {
|
|
|
21
22
|
h('strong', null, automationReady ? '✓ Messages 自动化权限已授予' : '2. 允许自动化控制 Messages'),
|
|
22
23
|
automationReady ? null : h('p', null, '在“系统设置 → 隐私与安全性 → 自动化”中,允许运行 DeepSeek Harness 的应用控制 Messages。'),
|
|
23
24
|
automationReady ? null : h('a', { href: 'x-apple.systempreferences:com.apple.preference.security?Privacy_Automation' }, '打开自动化设置'))),
|
|
24
|
-
error ? h(
|
|
25
|
+
error ? h(ConnectionError, { error: error }) : null,
|
|
25
26
|
h('div', { className: 'ddt-actions dim-viewActions' },
|
|
26
27
|
h('button', { type: 'button', className: 'ddt-button', onClick: onCancel, disabled: busy }, '取消'),
|
|
27
28
|
h('button', { type: 'button', className: 'ddt-button', 'data-kind': 'primary', onClick: () => onSubmit({}), disabled: busy },
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { diagnosticFields } from '../../../../src/channels/shared/diagnostic-details.mjs';
|
|
1
2
|
import {
|
|
2
3
|
OFFICE_PROTOCOL_VERSION,
|
|
3
4
|
OFFICE_RPC_CHANNEL,
|
|
@@ -12,6 +13,7 @@ export function unwrapOfficeRpc(result) {
|
|
|
12
13
|
if (!result.ok) {
|
|
13
14
|
const error = new Error(typeof result.error?.message === 'string' ? result.error.message : 'AI Office 操作失败');
|
|
14
15
|
error.code = typeof result.error?.code === 'string' ? result.error.code : 'office-rpc-error';
|
|
16
|
+
Object.assign(error, diagnosticFields(result.error));
|
|
15
17
|
throw error;
|
|
16
18
|
}
|
|
17
19
|
return result.value;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ConnectionError } from '../../connection-error.js';
|
|
1
2
|
import * as React from 'react';
|
|
2
3
|
|
|
3
4
|
import { h } from '../../i18n.js';
|
|
@@ -76,15 +77,15 @@ export function OfficeSettingsTab({ rpcCall, initialStatus }) {
|
|
|
76
77
|
|
|
77
78
|
const load = React.useCallback(async () => {
|
|
78
79
|
try { adopt(await invoke(OFFICE_RPC_ENDPOINTS.status)); setPhase('ready'); setError(''); }
|
|
79
|
-
catch (caught) { setPhase('error'); setError(caught
|
|
80
|
+
catch (caught) { setPhase('error'); setError(caught); }
|
|
80
81
|
}, [adopt, invoke]);
|
|
81
82
|
|
|
82
83
|
React.useEffect(() => { void load(); }, [load]);
|
|
83
84
|
|
|
84
85
|
const run = async (name, operation) => {
|
|
85
86
|
setBusy(name); setError(''); setNotice('');
|
|
86
|
-
try { const value = await operation(); adopt(value); setNotice(name === 'test' ? '连接测试通过。' : '配置已保存。'); }
|
|
87
|
-
catch (caught) { setError(caught
|
|
87
|
+
try { const value = await operation(); adopt(value); if (value?.warnings?.length) setError(value.warnings[0]); setNotice(name === 'test' ? '连接测试通过。' : '配置已保存。'); }
|
|
88
|
+
catch (caught) { setError(caught); }
|
|
88
89
|
finally { setBusy(''); }
|
|
89
90
|
};
|
|
90
91
|
|
|
@@ -128,9 +129,9 @@ export function OfficeSettingsTab({ rpcCall, initialStatus }) {
|
|
|
128
129
|
h('label', { className: 'dof-field', 'data-wide': 'true' }, 'Instruction Preset 映射',
|
|
129
130
|
h('textarea', { value: form.instructionPresets, placeholder: 'action-items=转换为负责人、截止和验收明确的工单', onChange: (event) => setForm({ ...form, instructionPresets: event.target.value }) }),
|
|
130
131
|
h('small', null, '每行 alias=指令;新增 preset 不需要改 Office 代码。'))),
|
|
131
|
-
error ? h(
|
|
132
|
+
error ? h(ConnectionError, { error }) : null,
|
|
132
133
|
notice ? h('p', { className: 'dof-notice', role: 'status' }, notice) : null,
|
|
133
|
-
health.error?.message ? h(
|
|
134
|
+
health.error?.message ? h(ConnectionError, { error: health.error }) : null,
|
|
134
135
|
h('div', { className: 'dof-actions' },
|
|
135
136
|
h(Button, { kind: 'primary', disabled: Boolean(busy), onClick: () => void run('save', () => invoke(OFFICE_RPC_ENDPOINTS.configure, {
|
|
136
137
|
baseUrl: form.baseUrl,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { normalizeConnectionError as normalizeTestError } from '../../connection-error.js';
|
|
2
|
+
import { diagnosticFields } from '../../../../src/channels/shared/diagnostic-details.mjs';
|
|
1
3
|
import { normalizeBotAlias } from '../../../../src/channels/shared/bot-alias.mjs';
|
|
2
4
|
import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
|
|
3
5
|
import { normalizeModelCatalog, normalizeModelSelection, SET_MODEL_ENDPOINT } from '../../model-setting.js';
|
|
@@ -52,6 +54,7 @@ export function unwrapRpcResult(result) {
|
|
|
52
54
|
if (!result.ok) {
|
|
53
55
|
const error = new Error(text(result.error?.message, 'QQ 操作失败'));
|
|
54
56
|
error.code = text(result.error?.code, 'QQ_RPC_ERROR', 80);
|
|
57
|
+
Object.assign(error, diagnosticFields(result.error));
|
|
55
58
|
throw error;
|
|
56
59
|
}
|
|
57
60
|
return result.value;
|
|
@@ -79,6 +82,7 @@ export function normalizeProvisioning(value, now = Date.now()) {
|
|
|
79
82
|
if (qrCodeDataUrl) result.qrCodeDataUrl = qrCodeDataUrl;
|
|
80
83
|
if (id(source.botId)) result.botId = id(source.botId);
|
|
81
84
|
if (isRecord(source.error)) result.error = {
|
|
85
|
+
...diagnosticFields(source.error),
|
|
82
86
|
code: text(source.error.code, 'QQ_PROVISION_FAILED', 80),
|
|
83
87
|
message: text(source.error.message, 'QQ 机器人没有接入完成'),
|
|
84
88
|
};
|
|
@@ -111,6 +115,7 @@ function normalizeBot(value) {
|
|
|
111
115
|
},
|
|
112
116
|
lastMessageError: normalizeLastMessageError(value.lastMessageError),
|
|
113
117
|
error: isRecord(value.error) ? {
|
|
118
|
+
...diagnosticFields(value.error),
|
|
114
119
|
code: text(value.error.code, 'QQ_ACCOUNT_ERROR', 80),
|
|
115
120
|
message: text(value.error.message, 'QQ 连接尚未就绪'),
|
|
116
121
|
} : null,
|
|
@@ -123,6 +128,7 @@ function normalizeTestMessage(value) {
|
|
|
123
128
|
const code = text(value.code, 'test-message-failed', 80);
|
|
124
129
|
return {
|
|
125
130
|
sent: false,
|
|
131
|
+
...(value.error ? { error: normalizeTestError(value.error) } : {}),
|
|
126
132
|
code: TEST_MESSAGE_CODES.has(code) ? code : 'test-message-failed',
|
|
127
133
|
};
|
|
128
134
|
}
|
|
@@ -153,6 +159,7 @@ export function connectionTestFeedback(result) {
|
|
|
153
159
|
|
|
154
160
|
export function presentError(error) {
|
|
155
161
|
return {
|
|
162
|
+
...diagnosticFields(error),
|
|
156
163
|
code: text(error?.code, 'QQ_ERROR', 80),
|
|
157
164
|
message: text(error?.message, 'QQ 操作失败,请稍后重试'),
|
|
158
165
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ConnectionError, normalizeConnectionError } from '../../connection-error.js';
|
|
1
2
|
import { BotName } from '../../bot-alias.js';
|
|
2
3
|
import * as React from 'react';
|
|
3
4
|
|
|
@@ -151,7 +152,7 @@ function ProvisionView({ provision, busy, onRetry, onClose }) {
|
|
|
151
152
|
return h('div', { className: 'ddt-card dim-surfaceCard' },
|
|
152
153
|
h('div', { className: 'ddt-inlineError dim-inlineError', role: 'alert' },
|
|
153
154
|
h('h3', null, 'QQ 机器人没有绑定完成'),
|
|
154
|
-
h(
|
|
155
|
+
h(ConnectionError, { error: error }),
|
|
155
156
|
h('span', { className: 'ddt-errorCode' }, error.code),
|
|
156
157
|
h('div', { className: 'ddt-actions dim-viewActions' },
|
|
157
158
|
h(Button, { kind: 'primary', onClick: onRetry, disabled: busy }, '重新生成二维码'),
|
|
@@ -242,6 +243,7 @@ export function AccountCard({
|
|
|
242
243
|
h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) }, busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
|
|
243
244
|
h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) }, '移除接入')),
|
|
244
245
|
summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
|
|
246
|
+
account.error ? h(ConnectionError, { error: account.error, showMessage: false }) : null,
|
|
245
247
|
account.lastMessageError ? h(LastMessageErrorSummary, {
|
|
246
248
|
className: 'ddt-summary',
|
|
247
249
|
error: account.lastMessageError,
|
|
@@ -259,6 +261,7 @@ export function AccountCard({
|
|
|
259
261
|
}
|
|
260
262
|
|
|
261
263
|
export function QqSettingsTab({ rpcCall }) {
|
|
264
|
+
const [operationError, setOperationError] = React.useState(null);
|
|
262
265
|
const [model, setModel] = React.useState({
|
|
263
266
|
phase: 'loading', bots: [], totals: { configured: 0, connected: 0 }, error: null,
|
|
264
267
|
agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG,
|
|
@@ -289,7 +292,16 @@ export function QqSettingsTab({ rpcCall }) {
|
|
|
289
292
|
|
|
290
293
|
const invoke = React.useCallback(async (endpoint, payload = {}, signal) => {
|
|
291
294
|
if (typeof rpcCall !== 'function') throw new TypeError('QQ 设置页缺少 RPC 连接');
|
|
292
|
-
|
|
295
|
+
const operation = !['connection.status', 'provision.poll', 'provision.begin', 'provision.cancel'].includes(endpoint);
|
|
296
|
+
if (operation && mounted.current) setOperationError(null);
|
|
297
|
+
try {
|
|
298
|
+
const value = unwrapRpcResult(await rpcCall(endpoint, payload, signal));
|
|
299
|
+
if (operation && mounted.current) setOperationError(value?.testMessage?.error ?? value?.warnings?.[0] ?? null);
|
|
300
|
+
return value;
|
|
301
|
+
} catch (error) {
|
|
302
|
+
if (operation && mounted.current && !signal?.aborted && error?.name !== 'AbortError') setOperationError(normalizeConnectionError(error));
|
|
303
|
+
throw error;
|
|
304
|
+
}
|
|
293
305
|
}, [rpcCall]);
|
|
294
306
|
|
|
295
307
|
const loadStatus = React.useCallback(async ({ signal, silent = false, restore = false } = {}) => {
|
|
@@ -558,6 +570,7 @@ export function QqSettingsTab({ rpcCall }) {
|
|
|
558
570
|
}, h(AgentPresetCatalogContext.Provider, {
|
|
559
571
|
value: model.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
|
|
560
572
|
}, h('section', { className: 'ddt-page dqq-page dim-channelPage', 'aria-label': 'QQ 设置' },
|
|
573
|
+
operationError ? h(ConnectionError, { error: operationError }) : null,
|
|
561
574
|
h(Heading, {
|
|
562
575
|
totals: model.totals,
|
|
563
576
|
adding: Boolean(provision),
|
|
@@ -569,7 +582,7 @@ export function QqSettingsTab({ rpcCall }) {
|
|
|
569
582
|
}),
|
|
570
583
|
model.phase === 'loading' ? h(LoadingView)
|
|
571
584
|
: model.phase === 'error'
|
|
572
|
-
? h('div', { className: 'ddt-card dim-surfaceCard' }, h('div', { className: 'ddt-inlineError dim-inlineError' }, h('h3', null, '无法读取 QQ 机器人状态'), h(
|
|
585
|
+
? h('div', { className: 'ddt-card dim-surfaceCard' }, h('div', { className: 'ddt-inlineError dim-inlineError' }, h('h3', null, '无法读取 QQ 机器人状态'), h(ConnectionError, { error: model.error }), h(Button, { onClick: () => void loadStatus() }, '重新读取')))
|
|
573
586
|
: h(React.Fragment, null,
|
|
574
587
|
credentialView,
|
|
575
588
|
provisionView,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { diagnosticFields } from '../../../../src/channels/shared/diagnostic-details.mjs';
|
|
1
2
|
import { normalizeBotAlias } from '../../../../src/channels/shared/bot-alias.mjs';
|
|
2
3
|
import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
|
|
3
4
|
import { normalizeModelCatalog, normalizeModelSelection, SET_MODEL_ENDPOINT } from '../../model-setting.js';
|
|
@@ -37,6 +38,7 @@ export const TOKEN_BOT_ENDPOINTS = Object.freeze({
|
|
|
37
38
|
setContextEnhancement: 'bot.context-enhancement.set',
|
|
38
39
|
setAccessPolicy: 'bot.access-policy.set',
|
|
39
40
|
setAlias: 'bot.alias.set',
|
|
41
|
+
setThinkingTraces: 'bot.thinking-traces.set',
|
|
40
42
|
});
|
|
41
43
|
|
|
42
44
|
export function createTokenChannelApi(channel, connectionSummary, {
|
|
@@ -49,7 +51,8 @@ export function createTokenChannelApi(channel, connectionSummary, {
|
|
|
49
51
|
if (!result.ok) {
|
|
50
52
|
const error = new Error(text(result.error?.message, `${channel} 操作失败`));
|
|
51
53
|
error.code = text(result.error?.code, `${channel.toUpperCase()}_RPC_ERROR`, 80);
|
|
52
|
-
|
|
54
|
+
Object.assign(error, diagnosticFields(result.error));
|
|
55
|
+
throw error;
|
|
53
56
|
}
|
|
54
57
|
return result.value;
|
|
55
58
|
};
|
|
@@ -67,6 +70,8 @@ export function createTokenChannelApi(channel, connectionSummary, {
|
|
|
67
70
|
model: normalizeModelSelection(value.model),
|
|
68
71
|
agentPreset: normalizeAgentPresetId(value.agentPreset),
|
|
69
72
|
contextEnhancement: normalizeContextEnhancementConfig(value.contextEnhancement),
|
|
73
|
+
// Absent from the backend means ON; only an explicit false opts out.
|
|
74
|
+
thinkingTraces: value.thinkingTraces !== false,
|
|
70
75
|
...(Object.hasOwn(value, 'accessPolicy')
|
|
71
76
|
? { accessPolicy: normalizeAccessPolicy(value.accessPolicy) }
|
|
72
77
|
: {}),
|
|
@@ -85,6 +90,7 @@ export function createTokenChannelApi(channel, connectionSummary, {
|
|
|
85
90
|
},
|
|
86
91
|
lastMessageError: normalizeLastMessageError(value.lastMessageError),
|
|
87
92
|
error: isRecord(value.error) ? {
|
|
93
|
+
...diagnosticFields(value.error),
|
|
88
94
|
code: text(value.error.code, `${channel.toUpperCase()}_ACCOUNT_ERROR`, 80),
|
|
89
95
|
message: text(value.error.message, `${channel}连接尚未就绪`),
|
|
90
96
|
} : null,
|
|
@@ -109,6 +115,7 @@ export function createTokenChannelApi(channel, connectionSummary, {
|
|
|
109
115
|
};
|
|
110
116
|
|
|
111
117
|
const presentError = (error) => ({
|
|
118
|
+
...diagnosticFields(error),
|
|
112
119
|
code: text(error?.code, `${channel.toUpperCase()}_ERROR`, 80),
|
|
113
120
|
message: text(error?.message, `${channel}操作失败,请稍后重试`),
|
|
114
121
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ConnectionError, normalizeConnectionError } from '../../connection-error.js';
|
|
1
2
|
import { BotName } from '../../bot-alias.js';
|
|
2
3
|
import * as React from 'react';
|
|
3
4
|
|
|
@@ -82,7 +83,7 @@ export function createTokenChannelSettings(definition) {
|
|
|
82
83
|
accountSettingsEndpoint = null,
|
|
83
84
|
} = definition;
|
|
84
85
|
|
|
85
|
-
function AccountCard({ account, busy, testNotice, removing, onReconnect, onWorkspaceSave, onAliasSave, onModelSave, onAgentPresetSave, onContextEnhancementSave, onAccountSettingsSave, onRequestRemove, onConfirmRemove, onCancelRemove }) {
|
|
86
|
+
function AccountCard({ account, busy, testNotice, removing, onReconnect, onWorkspaceSave, onAliasSave, onModelSave, onAgentPresetSave, onContextEnhancementSave, onAccountSettingsSave, onRequestRemove, onConfirmRemove, onCancelRemove, rpcCall, reload }) {
|
|
86
87
|
const state = busy === 'reconnect' ? 'connecting' : account.state;
|
|
87
88
|
const tone = account.connected ? 'success' : state === 'error' ? 'error' : 'warning';
|
|
88
89
|
const stateLabel = account.connected ? '运行正常' : state === 'connecting' ? '正在连接' : '连接未就绪';
|
|
@@ -144,6 +145,11 @@ export function createTokenChannelSettings(definition) {
|
|
|
144
145
|
account,
|
|
145
146
|
busy: Boolean(busy),
|
|
146
147
|
onSave: onAccountSettingsSave,
|
|
148
|
+
// Channels with extra settings panels (email session binding) call
|
|
149
|
+
// their own endpoints through the same RPC bridge.
|
|
150
|
+
rpcCall,
|
|
151
|
+
endpoints,
|
|
152
|
+
onChanged: reload,
|
|
147
153
|
}) : null,
|
|
148
154
|
h('div', { className: 'ddt-accountFooter dim-cardFooter' },
|
|
149
155
|
h('div', { className: 'dim-cardFooterLayout' },
|
|
@@ -160,6 +166,7 @@ export function createTokenChannelSettings(definition) {
|
|
|
160
166
|
disabled: Boolean(busy),
|
|
161
167
|
}, '移除接入')),
|
|
162
168
|
summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
|
|
169
|
+
account.error ? h(ConnectionError, { error: account.error, showMessage: false }) : null,
|
|
163
170
|
account.lastMessageError ? h(LastMessageErrorSummary, {
|
|
164
171
|
className: 'ddt-summary',
|
|
165
172
|
error: account.lastMessageError,
|
|
@@ -180,6 +187,7 @@ export function createTokenChannelSettings(definition) {
|
|
|
180
187
|
}
|
|
181
188
|
|
|
182
189
|
function SettingsTab({ rpcCall }) {
|
|
190
|
+
const [operationError, setOperationError] = React.useState(null);
|
|
183
191
|
const [model, setModel] = React.useState({
|
|
184
192
|
phase: 'loading', bots: [], totals: { configured: 0, connected: 0 }, error: null,
|
|
185
193
|
agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG,
|
|
@@ -207,7 +215,16 @@ export function createTokenChannelSettings(definition) {
|
|
|
207
215
|
|
|
208
216
|
const invoke = React.useCallback(async (endpoint, payload = {}, signal) => {
|
|
209
217
|
if (typeof rpcCall !== 'function') throw new TypeError(`${channel} 设置页缺少 RPC 连接`);
|
|
210
|
-
|
|
218
|
+
const operation = !['connection.status', 'provision.poll', 'provision.begin', 'provision.cancel'].includes(endpoint);
|
|
219
|
+
if (operation && mounted.current) setOperationError(null);
|
|
220
|
+
try {
|
|
221
|
+
const value = api.unwrapRpcResult(await rpcCall(endpoint, payload, signal));
|
|
222
|
+
if (operation && mounted.current) setOperationError(value?.testMessage?.error ?? value?.warnings?.[0] ?? null);
|
|
223
|
+
return value;
|
|
224
|
+
} catch (error) {
|
|
225
|
+
if (operation && mounted.current && !signal?.aborted && error?.name !== 'AbortError') setOperationError(normalizeConnectionError(error));
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
211
228
|
}, [rpcCall]);
|
|
212
229
|
|
|
213
230
|
const loadStatus = React.useCallback(async ({ signal, silent = false } = {}) => {
|
|
@@ -331,6 +348,8 @@ export function createTokenChannelSettings(definition) {
|
|
|
331
348
|
h('ul', { className: 'ddt-list dim-botList' }, model.bots.map((account) =>
|
|
332
349
|
h('li', { key: account.botId }, h(AccountCard, {
|
|
333
350
|
account,
|
|
351
|
+
rpcCall,
|
|
352
|
+
reload: loadStatus,
|
|
334
353
|
busy: busyByBot[account.botId],
|
|
335
354
|
testNotice: testNoticeByBot[account.botId],
|
|
336
355
|
removing: removeTarget === account.botId,
|
|
@@ -398,6 +417,7 @@ export function createTokenChannelSettings(definition) {
|
|
|
398
417
|
className: `ddt-page ${pageClass} dim-channelPage`,
|
|
399
418
|
'aria-label': `${channel} 设置`,
|
|
400
419
|
},
|
|
420
|
+
operationError ? h(ConnectionError, { error: operationError }) : null,
|
|
401
421
|
h('div', { className: 'ddt-heading' },
|
|
402
422
|
h('div', { className: 'ddt-tools' },
|
|
403
423
|
h('div', { className: 'dim-bindActions' },
|
|
@@ -422,7 +442,7 @@ export function createTokenChannelSettings(definition) {
|
|
|
422
442
|
? h('div', { className: 'ddt-card dim-surfaceCard' },
|
|
423
443
|
h('div', { className: 'ddt-inlineError dim-inlineError' },
|
|
424
444
|
h('h3', null, `无法读取 ${channel} 机器人状态`),
|
|
425
|
-
h(
|
|
445
|
+
h(ConnectionError, { error: model.error }),
|
|
426
446
|
h(Button, { onClick: () => void loadStatus() }, '重新读取')))
|
|
427
447
|
: h(React.Fragment, null,
|
|
428
448
|
credentialOpen ? (CredentialPanel
|
|
@@ -433,6 +453,11 @@ export function createTokenChannelSettings(definition) {
|
|
|
433
453
|
error: credentialError,
|
|
434
454
|
onSubmit: bindCredentials,
|
|
435
455
|
onCancel: () => { setCredentialOpen(false); setCredentialError(null); },
|
|
456
|
+
// A transport that authorizes out of band (the Agent mailbox
|
|
457
|
+
// shows a QR code) drives its own endpoints through the bridge.
|
|
458
|
+
rpcCall,
|
|
459
|
+
endpoints,
|
|
460
|
+
onAuthorized: bindCredentials,
|
|
436
461
|
})
|
|
437
462
|
: h(CredentialBindingPanel, {
|
|
438
463
|
channel,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ConnectionError } from '../../connection-error.js';
|
|
1
2
|
import * as React from 'react';
|
|
2
3
|
|
|
3
4
|
import { SlackLogoGlyph } from '../../channel-logos.js';
|
|
@@ -89,7 +90,7 @@ export function SlackCredentialPanel({ busy, error, onSubmit, onCancel }) {
|
|
|
89
90
|
required: true,
|
|
90
91
|
})),
|
|
91
92
|
h('p', { className: 'dsl-tokenHint' }, 'Bot Token 来自 OAuth & Permissions;App Token 来自 Basic Information,并且必须包含 connections:write。')),
|
|
92
|
-
error ? h(
|
|
93
|
+
error ? h(ConnectionError, { error: error }) : null,
|
|
93
94
|
h('div', { className: 'ddt-actions dim-viewActions dim-credentialActions' },
|
|
94
95
|
h('button', {
|
|
95
96
|
type: 'submit',
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
TELEGRAM_ENDPOINTS,
|
|
5
5
|
telegramClientApi,
|
|
6
6
|
} from './api.js';
|
|
7
|
+
import { ThinkingTracesSettings } from './thinking-traces.js';
|
|
7
8
|
import { installTelegramStyles } from './styles.js';
|
|
8
9
|
|
|
9
10
|
const channel = createTokenChannelSettings({
|
|
@@ -19,6 +20,8 @@ const channel = createTokenChannelSettings({
|
|
|
19
20
|
emptyTitle: '接入 Telegram 机器人',
|
|
20
21
|
emptyDescription: '先通过 @BotFather 获取 Bot Token,再在这里完成接入。',
|
|
21
22
|
platformLabel: 'Telegram',
|
|
23
|
+
AccountSettings: ThinkingTracesSettings,
|
|
24
|
+
accountSettingsEndpoint: 'bot.thinking-traces.set',
|
|
22
25
|
});
|
|
23
26
|
|
|
24
27
|
export const TelegramSettingsTab = channel.SettingsTab;
|
|
@@ -4,6 +4,18 @@ const CSS = String.raw`
|
|
|
4
4
|
.dtg-page { --ddt-accent: #229ed9; --ddt-accent-deep: #1687bd; --ddt-accent-wash: #eaf7fd; }
|
|
5
5
|
.dtg-avatar { color: #fff; background: #229ed9; }
|
|
6
6
|
.dtg-avatar svg { display: block; }
|
|
7
|
+
.dim-accountSettings { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--dsw-alias-border-l2, #eceef1); }
|
|
8
|
+
.dim-accountSettingsTitle { margin: 0 0 8px; font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-secondary, #646a73); letter-spacing: 0.02em; }
|
|
9
|
+
.dim-accountSettingsRow { display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 40px; cursor: pointer; }
|
|
10
|
+
.dim-accountSettingsText { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
|
|
11
|
+
.dim-accountSettingsLabel { font-size: 13px; font-weight: 500; color: var(--dsw-alias-label, #1f2329); }
|
|
12
|
+
.dim-accountSettingsHelp { font-size: 12px; color: var(--dsw-alias-label-tertiary, #8a919f); }
|
|
13
|
+
.dim-accountSettingsSwitch { appearance: none; flex: none; width: 32px; height: 19px; margin: 0; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 12px; background: var(--dsw-alias-interactive-bg-hover, #eef0f3); cursor: pointer; position: relative; }
|
|
14
|
+
.dim-accountSettingsSwitch::before { content: ""; position: absolute; top: 2px; left: 2px; width: 13px; height: 13px; border-radius: 50%; background: var(--dsw-alias-label-secondary, #646a73); transition: transform 120ms ease, background 120ms ease; }
|
|
15
|
+
.dim-accountSettingsSwitch:checked { border-color: #229ed9; background: #229ed9; }
|
|
16
|
+
.dim-accountSettingsSwitch:checked::before { transform: translateX(13px); background: #fff; }
|
|
17
|
+
.dim-accountSettingsSwitch:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
18
|
+
@media (prefers-reduced-motion: reduce) { .dim-accountSettingsSwitch::before { transition: none; } }
|
|
7
19
|
`;
|
|
8
20
|
|
|
9
21
|
export function installTelegramStyles() {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 会话行为设置块:思考过程留痕开关(Telegram 专属)。
|
|
3
|
+
*
|
|
4
|
+
* 开启后,机器人把 agent 的推理与工具调用作为独立的 💭/🔧 消息实时发出来,
|
|
5
|
+
* 最终答案单独成条;关闭则回到「占位符被最终答案覆盖」的原有单消息行为。
|
|
6
|
+
*/
|
|
7
|
+
import { h } from '../../i18n.js';
|
|
8
|
+
|
|
9
|
+
export function ThinkingTracesSettings({ account, busy, onSave }) {
|
|
10
|
+
const checked = account.thinkingTraces !== false;
|
|
11
|
+
return h('div', { className: 'dim-accountSettings' },
|
|
12
|
+
h('h4', { className: 'dim-accountSettingsTitle' }, '会话行为'),
|
|
13
|
+
h('label', { className: 'dim-accountSettingsRow' },
|
|
14
|
+
h('span', { className: 'dim-accountSettingsText' },
|
|
15
|
+
h('span', { className: 'dim-accountSettingsLabel' }, '思考过程留痕'),
|
|
16
|
+
h('span', { className: 'dim-accountSettingsHelp' },
|
|
17
|
+
'实时显示思考步骤与工具调用,最终答案单独成条')),
|
|
18
|
+
h('input', {
|
|
19
|
+
type: 'checkbox',
|
|
20
|
+
className: 'dim-accountSettingsSwitch',
|
|
21
|
+
checked,
|
|
22
|
+
disabled: Boolean(busy),
|
|
23
|
+
onChange: (event) => { onSave?.({ thinkingTraces: event.target.checked }); },
|
|
24
|
+
})));
|
|
25
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { normalizeConnectionError as normalizeTestError } from '../../connection-error.js';
|
|
2
|
+
import { diagnosticFields } from '../../../../src/channels/shared/diagnostic-details.mjs';
|
|
1
3
|
import { normalizeBotAlias } from '../../../../src/channels/shared/bot-alias.mjs';
|
|
2
4
|
import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
|
|
3
5
|
import { normalizeModelCatalog, normalizeModelSelection, SET_MODEL_ENDPOINT } from '../../model-setting.js';
|
|
@@ -53,7 +55,7 @@ function normalizeTestMessage(value) {
|
|
|
53
55
|
const code = value.code === 'test-target-unavailable'
|
|
54
56
|
? 'test-target-unavailable'
|
|
55
57
|
: 'test-message-failed';
|
|
56
|
-
return { sent: false, code };
|
|
58
|
+
return { sent: false, code, ...(value.error ? { error: normalizeTestError(value.error) } : {}) };
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
export function unwrapRpcResult(result) {
|
|
@@ -61,6 +63,7 @@ export function unwrapRpcResult(result) {
|
|
|
61
63
|
if (!result.ok) {
|
|
62
64
|
const error = new Error(text(result.error?.message, '企业微信操作失败'));
|
|
63
65
|
error.code = text(result.error?.code, 'WECOM_RPC_ERROR', 80);
|
|
66
|
+
Object.assign(error, diagnosticFields(result.error));
|
|
64
67
|
throw error;
|
|
65
68
|
}
|
|
66
69
|
return result.value;
|
|
@@ -88,6 +91,7 @@ export function normalizeProvisioning(value, now = Date.now()) {
|
|
|
88
91
|
if (qrCodeDataUrl) result.qrCodeDataUrl = qrCodeDataUrl;
|
|
89
92
|
if (id(source.botId)) result.botId = id(source.botId);
|
|
90
93
|
if (isRecord(source.error)) result.error = {
|
|
94
|
+
...diagnosticFields(source.error),
|
|
91
95
|
code: text(source.error.code, 'WECOM_PROVISION_FAILED', 80),
|
|
92
96
|
message: text(source.error.message, '企业微信机器人没有接入完成'),
|
|
93
97
|
};
|
|
@@ -120,6 +124,7 @@ function normalizeBot(value) {
|
|
|
120
124
|
},
|
|
121
125
|
lastMessageError: normalizeLastMessageError(value.lastMessageError),
|
|
122
126
|
error: isRecord(value.error) ? {
|
|
127
|
+
...diagnosticFields(value.error),
|
|
123
128
|
code: text(value.error.code, 'WECOM_ACCOUNT_ERROR', 80),
|
|
124
129
|
message: text(value.error.message, '企业微信连接尚未就绪'),
|
|
125
130
|
} : null,
|
|
@@ -143,6 +148,7 @@ export function normalizeSnapshot(value) {
|
|
|
143
148
|
|
|
144
149
|
export function presentError(error) {
|
|
145
150
|
return {
|
|
151
|
+
...diagnosticFields(error),
|
|
146
152
|
code: text(error?.code, 'WECOM_ERROR', 80),
|
|
147
153
|
message: text(error?.message, '企业微信操作失败,请稍后重试'),
|
|
148
154
|
};
|