@xmanrui/dsh-im 4.16.0 → 4.17.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/lib/client.js +66 -18
- package/lib/index.js +259 -256
- package/package.json +5 -1
- package/plugin-src/client/channels/feishu/api.js +3 -0
- package/plugin-src/client/channels/feishu/index.js +45 -13
- package/plugin-src/client/i18n.js +13 -2
- package/plugin-src/host/channels/feishu/production.mjs +1 -0
- package/plugin-src/host/channels/feishu/rpc.mjs +20 -0
- package/src/channels/dingtalk/dingtalk-api.mjs +41 -48
- package/src/channels/dingtalk/dingtalk-bridge.mjs +5 -1
- package/src/channels/feishu/bridge.mjs +475 -11
- package/src/channels/feishu/feishu-cards.mjs +130 -0
- package/src/channels/feishu/feishu-runtime.mjs +10 -0
- package/src/channels/feishu/multi-bot-controller.mjs +28 -0
- package/src/channels/feishu/plugin-config-store.mjs +2 -0
- package/src/channels/feishu/step-push-mode.mjs +19 -0
- package/src/channels/shared/i18n-en/feishu.mjs +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xmanrui/dsh-im",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.17.0",
|
|
4
4
|
"description": "把十种 IM 机器人和公网 AI Office 接入本机 DeepSeek Harness。 Connect ten IM channels and a public AI Office to a local DeepSeek Harness.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
@@ -39,6 +39,10 @@
|
|
|
39
39
|
{
|
|
40
40
|
"name": "BuvkB",
|
|
41
41
|
"url": "https://github.com/BuvkB"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"name": "masucc",
|
|
45
|
+
"url": "https://github.com/masucc"
|
|
42
46
|
}
|
|
43
47
|
],
|
|
44
48
|
"license": "MIT",
|
|
@@ -11,6 +11,7 @@ import { normalizeModelCatalog, normalizeModelSelection, SET_MODEL_ENDPOINT } fr
|
|
|
11
11
|
import { normalizeLastMessageError } from "../../last-message-error.js";
|
|
12
12
|
import { normalizeAccessPolicy } from "../../../../src/channels/shared/access-policy.mjs";
|
|
13
13
|
import { normalizeContextEnhancementConfig } from "../../../../src/channels/shared/context-enhancement.mjs";
|
|
14
|
+
import { normalizeFeishuStepPushMode } from "../../../../src/channels/feishu/step-push-mode.mjs";
|
|
14
15
|
|
|
15
16
|
export const FEISHU_RPC_CHANNEL = "/feishu";
|
|
16
17
|
|
|
@@ -33,6 +34,7 @@ export const FEISHU_ENDPOINTS = Object.freeze({
|
|
|
33
34
|
setGroupResponseMode: "bot.group-response-mode.set",
|
|
34
35
|
setGroupTopicReply: "bot.group-topic-reply.set",
|
|
35
36
|
setStepPush: "bot.step-push.set",
|
|
37
|
+
setStepPushMode: "bot.step-push-mode.set",
|
|
36
38
|
// Kept for rolling upgrades. The multi-bot UI never calls these endpoints.
|
|
37
39
|
testConnection: "connection.test",
|
|
38
40
|
disconnect: "connection.disconnect",
|
|
@@ -219,6 +221,7 @@ export function normalizeBotConnection(value, fallbackBotId) {
|
|
|
219
221
|
groupResponseMode: normalizeGroupResponseMode(value.groupResponseMode),
|
|
220
222
|
groupTopicReply: value.groupTopicReply === true,
|
|
221
223
|
stepPush: value.stepPush === true,
|
|
224
|
+
stepPushMode: normalizeFeishuStepPushMode(value.stepPushMode),
|
|
222
225
|
groupMessagePermissionGranted: value.groupMessagePermissionGranted === true,
|
|
223
226
|
bot: normalizeBot(value.bot),
|
|
224
227
|
health: normalizeHealth(value.health, connected),
|
|
@@ -464,21 +464,20 @@ function RemoveConfirmation({ bot, busy, onConfirm, onCancel }) {
|
|
|
464
464
|
);
|
|
465
465
|
}
|
|
466
466
|
|
|
467
|
-
/**
|
|
468
|
-
function StepPushEditor({ value = false, disabled = false, onSave }) {
|
|
467
|
+
/** One select for the step-push presentation: off / per-step posts / process card. */
|
|
468
|
+
function StepPushEditor({ value = false, mode = "post", disabled = false, onSave, onModeSave }) {
|
|
469
469
|
const titleId = React.useId();
|
|
470
470
|
const helpId = `${titleId}-help`;
|
|
471
|
-
const current = value === true ?
|
|
471
|
+
const current = value === true ? mode : "off";
|
|
472
472
|
const [saving, setSaving] = React.useState(false);
|
|
473
473
|
const [error, setError] = React.useState(null);
|
|
474
474
|
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
if ((next ? "on" : "off") === current || saving || disabled) return;
|
|
475
|
+
const save = async (run) => {
|
|
476
|
+
if (saving || disabled) return;
|
|
478
477
|
setSaving(true);
|
|
479
478
|
setError(null);
|
|
480
479
|
try {
|
|
481
|
-
await
|
|
480
|
+
await run();
|
|
482
481
|
} catch (cause) {
|
|
483
482
|
setError(cause?.message ?? "分步直推设置保存失败,请重试。");
|
|
484
483
|
} finally {
|
|
@@ -486,13 +485,37 @@ function StepPushEditor({ value = false, disabled = false, onSave }) {
|
|
|
486
485
|
}
|
|
487
486
|
};
|
|
488
487
|
|
|
488
|
+
const change = (event) => {
|
|
489
|
+
const next = event.target.value;
|
|
490
|
+
if (next === current) return;
|
|
491
|
+
void save(async () => {
|
|
492
|
+
if (next === "off") {
|
|
493
|
+
// Turning off only needs the flag when it was on.
|
|
494
|
+
if (value === true) await onSave?.(false);
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
const nextMode = next === "streaming_card" ? "streaming_card" : "post";
|
|
498
|
+
// Enabling (or switching presentation) may need both writes; the flag
|
|
499
|
+
// must land before the mode so the runtime never sees a mode without
|
|
500
|
+
// step push enabled.
|
|
501
|
+
if (value !== true) await onSave?.(true);
|
|
502
|
+
if (nextMode !== mode) await onModeSave?.(nextMode);
|
|
503
|
+
});
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
const helpText = current === "off"
|
|
507
|
+
? "适合日常问答:执行过程中不显示工具调用等中间步骤,只回复最终结果"
|
|
508
|
+
: current === "streaming_card"
|
|
509
|
+
? "推荐长任务使用:过程与最终答案都在同一张卡片里实时更新,不刷屏"
|
|
510
|
+
: "每一步都单独发一条消息(含工具调用和过程说明);注意长任务会连续发送较多消息";
|
|
511
|
+
|
|
489
512
|
return h("section", {
|
|
490
513
|
className: "dim-feishuGroupControl",
|
|
491
514
|
"aria-labelledby": titleId,
|
|
492
515
|
},
|
|
493
516
|
h("div", { className: "dim-feishuGroupControlHeader" },
|
|
494
517
|
h("div", { className: "dim-presetTitle" },
|
|
495
|
-
h("h3", { id: titleId }, "
|
|
518
|
+
h("h3", { id: titleId }, "任务过程展示"),
|
|
496
519
|
h("span", { className: "dim-presetHelp" },
|
|
497
520
|
h("button", {
|
|
498
521
|
type: "button",
|
|
@@ -504,7 +527,7 @@ function StepPushEditor({ value = false, disabled = false, onSave }) {
|
|
|
504
527
|
id: helpId,
|
|
505
528
|
className: "dim-presetTooltip",
|
|
506
529
|
role: "tooltip",
|
|
507
|
-
}, "
|
|
530
|
+
}, "设置任务执行过程的呈现方式:不显示、实时卡片或逐步消息"))),
|
|
508
531
|
saving
|
|
509
532
|
? h("span", { className: "dim-feishuGroupControlStatus", role: "status" }, "保存中…")
|
|
510
533
|
: null),
|
|
@@ -512,11 +535,13 @@ function StepPushEditor({ value = false, disabled = false, onSave }) {
|
|
|
512
535
|
className: "dim-feishuGroupSelect",
|
|
513
536
|
value: current,
|
|
514
537
|
disabled: disabled || saving,
|
|
515
|
-
"aria-label": "
|
|
516
|
-
onChange:
|
|
538
|
+
"aria-label": "任务过程展示",
|
|
539
|
+
onChange: change,
|
|
517
540
|
},
|
|
518
|
-
h("option", { value: "off" }, "
|
|
519
|
-
h("option", { value: "
|
|
541
|
+
h("option", { value: "off" }, "不显示过程(只发送最终答案)"),
|
|
542
|
+
h("option", { value: "streaming_card" }, "实时过程卡(全程一张卡片动态更新)"),
|
|
543
|
+
h("option", { value: "post" }, "逐步直播(每一步单独发一条消息)")),
|
|
544
|
+
h("p", { className: "dim-feishuGroupHelp" }, helpText),
|
|
520
545
|
error ? h("p", {
|
|
521
546
|
className: "dim-feishuGroupError",
|
|
522
547
|
role: "alert",
|
|
@@ -539,6 +564,7 @@ export function BotCard({
|
|
|
539
564
|
onAgentPresetSave,
|
|
540
565
|
onContextEnhancementSave,
|
|
541
566
|
onStepPushSave,
|
|
567
|
+
onStepPushModeSave,
|
|
542
568
|
onRequestRemove,
|
|
543
569
|
onConfirmRemove,
|
|
544
570
|
onCancelRemove,
|
|
@@ -627,8 +653,10 @@ export function BotCard({
|
|
|
627
653
|
}),
|
|
628
654
|
h(StepPushEditor, {
|
|
629
655
|
value: connection.stepPush,
|
|
656
|
+
mode: connection.stepPushMode,
|
|
630
657
|
disabled: Boolean(busy),
|
|
631
658
|
onSave: onStepPushSave,
|
|
659
|
+
onModeSave: onStepPushModeSave,
|
|
632
660
|
}),
|
|
633
661
|
provisionContent
|
|
634
662
|
? h("section", {
|
|
@@ -723,6 +751,7 @@ function BotList(props) {
|
|
|
723
751
|
onAgentPresetSave: (agentPreset) => props.onAgentPresetSave(bot, agentPreset),
|
|
724
752
|
onContextEnhancementSave: (config) => props.onContextEnhancementSave(bot, config),
|
|
725
753
|
onStepPushSave: (stepPush) => props.onStepPushSave(bot, stepPush),
|
|
754
|
+
onStepPushModeSave: (stepPushMode) => props.onStepPushModeSave(bot, stepPushMode),
|
|
726
755
|
onRequestRemove: () => props.onRequestRemove(bot),
|
|
727
756
|
onConfirmRemove: () => props.onConfirmRemove(bot),
|
|
728
757
|
onCancelRemove: props.onCancelRemove,
|
|
@@ -1500,6 +1529,9 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
1500
1529
|
onStepPushSave: (connection, stepPush) => saveBotSetting(
|
|
1501
1530
|
connection, "step-push", FEISHU_ENDPOINTS.setStepPush, { stepPush },
|
|
1502
1531
|
),
|
|
1532
|
+
onStepPushModeSave: (connection, stepPushMode) => saveBotSetting(
|
|
1533
|
+
connection, "step-push-mode", FEISHU_ENDPOINTS.setStepPushMode, { stepPushMode },
|
|
1534
|
+
),
|
|
1503
1535
|
onRequestRemove: requestRemove,
|
|
1504
1536
|
onConfirmRemove: (bot) => void confirmRemove(bot),
|
|
1505
1537
|
onCancelRemove: cancelRemove,
|
|
@@ -223,9 +223,20 @@ const EN = Object.freeze({
|
|
|
223
223
|
'分步直推': 'Step push',
|
|
224
224
|
'查看分步直推说明': 'View step push help',
|
|
225
225
|
'开启后逐步推送工具调用与过程说明': 'Push tool calls and process notes step by step',
|
|
226
|
-
'
|
|
227
|
-
'
|
|
226
|
+
'任务过程展示': 'Task progress display',
|
|
227
|
+
'设置任务执行过程的呈现方式:不显示、实时卡片或逐步消息': 'Choose how the execution is presented: hidden, one live card, or step-by-step messages',
|
|
228
|
+
'不显示过程(只发送最终答案)': 'Hide the process (send the final answer only)',
|
|
229
|
+
'实时过程卡(全程一张卡片动态更新)': 'Live process card (one card updated throughout)',
|
|
230
|
+
'逐步直播(每一步单独发一条消息)': 'Step-by-step feed (one message per step)',
|
|
231
|
+
'适合日常问答:执行过程中不显示工具调用等中间步骤,只回复最终结果': 'For everyday Q&A: tool calls and other interim steps stay hidden, only the final result is replied',
|
|
232
|
+
'推荐长任务使用:过程与最终答案都在同一张卡片里实时更新,不刷屏': 'Recommended for long tasks: the process and the final answer update live in one card without flooding the chat',
|
|
233
|
+
'每一步都单独发一条消息(含工具调用和过程说明);注意长任务会连续发送较多消息': 'Every step is sent as its own message (including tool calls and notes); long tasks may send many messages in a row',
|
|
228
234
|
'分步直推设置保存失败,请重试。': 'Could not save the step push setting. Try again.',
|
|
235
|
+
'zcode过程卡模式': 'ZCode process-card mode',
|
|
236
|
+
'过程说明、工具摘要与最终答案都在同一张卡片中原地刷新':
|
|
237
|
+
'Process notes, the tool summary, and the final answer refresh in place inside one card',
|
|
238
|
+
'思考与工具摘要折叠展示,最终答案在同一张卡片中原地刷新':
|
|
239
|
+
'Thinking and the tool summary stay folded; the final answer refreshes in place inside one card',
|
|
229
240
|
'钉钉设置': 'DingTalk settings',
|
|
230
241
|
'企业微信设置': 'WeCom settings',
|
|
231
242
|
'扫码接入机器人': 'Scan QR code',
|
|
@@ -219,6 +219,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
219
219
|
groupResponseMode: botConfig.groupResponseMode,
|
|
220
220
|
groupTopicReply: botConfig.groupTopicReply,
|
|
221
221
|
stepPush: botConfig.stepPush,
|
|
222
|
+
stepPushMode: botConfig.stepPushMode,
|
|
222
223
|
ownerOpenIds: botConfig.ownerOpenIds ?? [botConfig.ownerOpenId],
|
|
223
224
|
harness: workspaceScope.harness,
|
|
224
225
|
state: workspaceScope.state,
|
|
@@ -21,6 +21,10 @@ import {
|
|
|
21
21
|
isFeishuGroupResponseMode,
|
|
22
22
|
normalizeFeishuGroupResponseMode,
|
|
23
23
|
} from '../../../../src/channels/feishu/group-response-mode.mjs';
|
|
24
|
+
import {
|
|
25
|
+
isFeishuStepPushMode,
|
|
26
|
+
normalizeFeishuStepPushMode,
|
|
27
|
+
} from '../../../../src/channels/feishu/step-push-mode.mjs';
|
|
24
28
|
import {
|
|
25
29
|
FEISHU_ENDPOINTS as FEISHU_CLIENT_ENDPOINTS,
|
|
26
30
|
FEISHU_RPC_CHANNEL,
|
|
@@ -295,6 +299,7 @@ function publicBotEntry(entry) {
|
|
|
295
299
|
groupResponseMode: normalizeFeishuGroupResponseMode(source.groupResponseMode),
|
|
296
300
|
groupTopicReply: source.groupTopicReply === true,
|
|
297
301
|
stepPush: source.stepPush === true,
|
|
302
|
+
stepPushMode: normalizeFeishuStepPushMode(source.stepPushMode),
|
|
298
303
|
groupMessagePermissionGranted: source.groupMessagePermissionGranted === true,
|
|
299
304
|
bot: publicBot(source.bot),
|
|
300
305
|
health: publicHealth(source, connected),
|
|
@@ -466,6 +471,13 @@ function validPayload(endpoint, payload) {
|
|
|
466
471
|
? null
|
|
467
472
|
: '请选择是否分步直推。';
|
|
468
473
|
}
|
|
474
|
+
if (endpoint === FEISHU_ENDPOINTS.setStepPushMode) {
|
|
475
|
+
return hasOnlyKeys(payload, new Set(['botId', 'stepPushMode']))
|
|
476
|
+
&& safeOpaqueId(payload.botId)
|
|
477
|
+
&& isFeishuStepPushMode(payload.stepPushMode)
|
|
478
|
+
? null
|
|
479
|
+
: '请选择分步直推的呈现方式。';
|
|
480
|
+
}
|
|
469
481
|
return 'Unknown Feishu endpoint.';
|
|
470
482
|
}
|
|
471
483
|
|
|
@@ -758,6 +770,14 @@ export function createFeishuRpcHandler(controller, { encodeQr = qrCodeDataUrl }
|
|
|
758
770
|
await controller.updateStepPush(payload.botId, payload.stepPush),
|
|
759
771
|
{ encodeQr: cachedEncodeQr },
|
|
760
772
|
);
|
|
773
|
+
} else if (endpoint === FEISHU_ENDPOINTS.setStepPushMode) {
|
|
774
|
+
if (typeof controller.updateStepPushMode !== 'function') {
|
|
775
|
+
throw new Error('Step push mode update is unavailable');
|
|
776
|
+
}
|
|
777
|
+
value = await toPublicFeishuStatus(
|
|
778
|
+
await controller.updateStepPushMode(payload.botId, payload.stepPushMode),
|
|
779
|
+
{ encodeQr: cachedEncodeQr },
|
|
780
|
+
);
|
|
761
781
|
} else {
|
|
762
782
|
if (typeof controller.deleteBot !== 'function') throw new Error('Multi-bot delete is unavailable');
|
|
763
783
|
value = await toPublicFeishuStatus(await controller.deleteBot(payload.botId), { encodeQr: cachedEncodeQr });
|
|
@@ -337,10 +337,13 @@ function cardMarkdown(text, target) {
|
|
|
337
337
|
}
|
|
338
338
|
|
|
339
339
|
function cardData(text, flowStatus, target) {
|
|
340
|
+
const markdown = cardMarkdown(text, target);
|
|
341
|
+
// The shared template uses msgContent for processing, finished, and
|
|
342
|
+
// failed cards. Switching its order to staticMsgContent hides the reply.
|
|
340
343
|
return {
|
|
341
344
|
cardParamMap: {
|
|
342
345
|
flowStatus,
|
|
343
|
-
msgContent:
|
|
346
|
+
msgContent: markdown,
|
|
344
347
|
staticMsgContent: '',
|
|
345
348
|
sys_full_json_obj: JSON.stringify({ order: ['msgContent'] }),
|
|
346
349
|
config: JSON.stringify({ autoLayout: true }),
|
|
@@ -922,13 +925,17 @@ export function createDingtalkApi({
|
|
|
922
925
|
|
|
923
926
|
let delivered = false;
|
|
924
927
|
try {
|
|
925
|
-
|
|
928
|
+
// Deliver the thinking copy in the same request that first shows the
|
|
929
|
+
// card. Creating an empty instance and filling it after deliver (the
|
|
930
|
+
// previous three-request sequence) leaves a blank bubble visible in
|
|
931
|
+
// DingTalk for the time between deliver and the follow-up PUT, and
|
|
932
|
+
// leaves a permanently blank card if that PUT ever fails.
|
|
933
|
+
await cardRequest('v1.0/card/instances/createAndDeliver', {
|
|
926
934
|
body: {
|
|
935
|
+
...cardDeliverBody(cardInstanceId, normalizedTarget, appKey),
|
|
927
936
|
cardTemplateId: DINGTALK_AI_CARD_TEMPLATE_ID,
|
|
928
937
|
outTrackId: cardInstanceId,
|
|
929
|
-
cardData:
|
|
930
|
-
cardParamMap: { config: JSON.stringify({ autoLayout: true }) },
|
|
931
|
-
},
|
|
938
|
+
cardData: cardData(content, '2', normalizedTarget),
|
|
932
939
|
callbackType: 'STREAM',
|
|
933
940
|
cardAtUserIds: normalizedTarget.atUserIds
|
|
934
941
|
? Object.keys(normalizedTarget.atUserIds)
|
|
@@ -938,37 +945,31 @@ export function createDingtalkApi({
|
|
|
938
945
|
},
|
|
939
946
|
headers,
|
|
940
947
|
signal,
|
|
941
|
-
action: 'AI Card
|
|
942
|
-
});
|
|
943
|
-
await cardRequest('v1.0/card/instances/deliver', {
|
|
944
|
-
body: cardDeliverBody(cardInstanceId, normalizedTarget, appKey),
|
|
945
|
-
headers,
|
|
946
|
-
signal,
|
|
947
|
-
action: 'AI Card 投放',
|
|
948
|
+
action: 'AI Card 创建并投放',
|
|
948
949
|
});
|
|
949
950
|
delivered = true;
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
}
|
|
951
|
+
try {
|
|
952
|
+
await cardRequest('v1.0/card/streaming', {
|
|
953
|
+
method: 'PUT',
|
|
954
|
+
body: {
|
|
955
|
+
outTrackId: cardInstanceId,
|
|
956
|
+
guid: randomUUID(),
|
|
957
|
+
key: 'msgContent',
|
|
958
|
+
content: cardMarkdown(content, normalizedTarget).replace(/\n+$/, ''),
|
|
959
|
+
isFull: true,
|
|
960
|
+
isFinalize: false,
|
|
961
|
+
isError: false,
|
|
962
|
+
},
|
|
963
|
+
headers,
|
|
964
|
+
signal,
|
|
965
|
+
action: 'AI Card 启动',
|
|
966
|
+
});
|
|
967
|
+
} catch (error) {
|
|
968
|
+
if (signal?.aborted) throw error;
|
|
969
|
+
// The card is already visible with the thinking copy from
|
|
970
|
+
// createAndDeliver above. Keep the instance so finishAiCard can
|
|
971
|
+
// still replace it in place instead of sending a second message.
|
|
972
|
+
}
|
|
972
973
|
} catch (error) {
|
|
973
974
|
if (delivered) {
|
|
974
975
|
const cleanupSignal = AbortSignal.timeout(5_000);
|
|
@@ -1018,6 +1019,8 @@ export function createDingtalkApi({
|
|
|
1018
1019
|
const token = await accessToken({ clientId, clientSecret, signal });
|
|
1019
1020
|
const headers = { 'x-acs-dingtalk-access-token': token };
|
|
1020
1021
|
const normalizedContent = cardMarkdown(content, target);
|
|
1022
|
+
// Close the streaming widget before persisting the template's
|
|
1023
|
+
// finished state and full answer in msgContent.
|
|
1021
1024
|
await cardRequest('v1.0/card/streaming', {
|
|
1022
1025
|
method: 'PUT',
|
|
1023
1026
|
body: {
|
|
@@ -1033,8 +1036,7 @@ export function createDingtalkApi({
|
|
|
1033
1036
|
signal,
|
|
1034
1037
|
action: 'AI Card 完成',
|
|
1035
1038
|
});
|
|
1036
|
-
|
|
1037
|
-
const completionRequest = {
|
|
1039
|
+
await cardRequest('v1.0/card/instances', {
|
|
1038
1040
|
method: 'PUT',
|
|
1039
1041
|
body: {
|
|
1040
1042
|
outTrackId: instanceId,
|
|
@@ -1043,18 +1045,9 @@ export function createDingtalkApi({
|
|
|
1043
1045
|
},
|
|
1044
1046
|
headers,
|
|
1045
1047
|
signal,
|
|
1046
|
-
action: 'AI Card
|
|
1047
|
-
};
|
|
1048
|
-
|
|
1049
|
-
await cardRequest('v1.0/card/instances', completionRequest);
|
|
1050
|
-
} catch {
|
|
1051
|
-
try {
|
|
1052
|
-
await cardRequest('v1.0/card/instances', completionRequest);
|
|
1053
|
-
} catch {
|
|
1054
|
-
completed = false;
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
return { delivered: true, completed };
|
|
1048
|
+
action: 'AI Card 完成状态',
|
|
1049
|
+
});
|
|
1050
|
+
return { delivered: true, completed: true };
|
|
1058
1051
|
},
|
|
1059
1052
|
|
|
1060
1053
|
failAiCard: failCard,
|
|
@@ -1336,6 +1336,8 @@ export class DingtalkHarnessBridge {
|
|
|
1336
1336
|
providerMessageIds: cardStream.providerMessageIds,
|
|
1337
1337
|
});
|
|
1338
1338
|
} else {
|
|
1339
|
+
// Failed streams close the card with a notice pointing to a
|
|
1340
|
+
// follow-up message. Deliver the answer through that fallback.
|
|
1339
1341
|
textReceipt = createDeliveryReceipt({
|
|
1340
1342
|
deliveryId: messageId,
|
|
1341
1343
|
presentation: 'dingtalk-text',
|
|
@@ -1411,7 +1413,9 @@ export class DingtalkHarnessBridge {
|
|
|
1411
1413
|
? `${errorText}\n\n${batchFailureMessage}`
|
|
1412
1414
|
: errorText;
|
|
1413
1415
|
const streamed = cardStarted && await cardStream.finish(visibleError);
|
|
1414
|
-
if (!streamed)
|
|
1416
|
+
if (!streamed) {
|
|
1417
|
+
await this.#send(sessionWebhook, visibleError, this.#atUsersFor(message));
|
|
1418
|
+
}
|
|
1415
1419
|
} catch {
|
|
1416
1420
|
this.#logger.error?.('[dsh-dingtalk] failed to send the safe error reply');
|
|
1417
1421
|
}
|