@xmanrui/dsh-im 4.16.1 → 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 +62 -18
- package/lib/index.js +259 -256
- package/package.json +1 -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/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
|
@@ -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 });
|