@jeik/dingtalk-connector 0.8.21-fix13 → 0.8.21-fix16

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/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as initDingtalkPluginConfigSchema, i as dingtalkPlugin, n as setDingtalkRuntime } from "./runtime-CmHKkoFi.mjs";
1
+ import { a as initDingtalkPluginConfigSchema, i as dingtalkPlugin, n as setDingtalkRuntime } from "./runtime-CaDlY3DU.mjs";
2
2
  import { t as registerGatewayMethods } from "./gateway-methods-5IlNoS6J.mjs";
3
3
  //#region index.ts
4
4
  /**
@@ -1,6 +1,6 @@
1
1
  import { u as uploadMediaToDingTalk } from "./media-cz72EVS3.mjs";
2
2
  import { a as resolveDingtalkAccount } from "./accounts-BAzdqkAV.mjs";
3
- import { r as CHANNEL_ID, t as getDingtalkRuntime } from "./runtime-CmHKkoFi.mjs";
3
+ import { r as CHANNEL_ID, t as getDingtalkRuntime } from "./runtime-CaDlY3DU.mjs";
4
4
  import { n as createLoggerFromConfig } from "./logger-mZ9OSbmD.mjs";
5
5
  import { t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
6
6
  import { i as getOapiAccessToken } from "./utils-CIfI_3Jh.mjs";
@@ -399,6 +399,7 @@ function createDingtalkReplyDispatcher(params) {
399
399
  log.info(`[DingTalk][closeStreaming] 开始关闭 AI Card...`);
400
400
  try {
401
401
  let finalText = finalClean(pickFinalText());
402
+ const hadRealAnswer = finalText.trim().length > 0;
402
403
  log.info(`[DingTalk][closeStreaming] 最终答案来源=${finalMarkedText !== null ? "marker[-final-]" : lastAnswerText ? "非reasoning答案" : "accumulatedText兜底"},长度=${finalText.length}`);
403
404
  if (!finalText.trim()) {
404
405
  finalText = "✅ 任务执行完成(无文本输出)";
@@ -451,19 +452,28 @@ function createDingtalkReplyDispatcher(params) {
451
452
  }
452
453
  finalText = await applyReplyTemplate(finalText);
453
454
  if (account.config?.answerCard === true) {
454
- log.info(`[DingTalk][closeStreaming] answerCard 模式:原卡定格思考完成 + 新建答案卡`);
455
- try {
456
- await finishAICard(cardSnapshot, "✅ 思考完成", account.config, log);
457
- } catch (e) {
458
- log.warn(`[DingTalk][closeStreaming] 原卡定格思考完成失败(忽略):${e?.message || e}`);
459
- }
460
- const answerCard = await createAICardForTarget(account.config, target, log, ANSWER_CARD_TEMPLATE_ID);
461
- if (answerCard) {
462
- await finishAICard(answerCard, finalText, account.config, log);
463
- log.info(`[DingTalk][closeStreaming] ✅ 答案卡投放成功`);
455
+ const finalizeOriginalToDone = async () => {
456
+ try {
457
+ await streamAICard(cardSnapshot, "✅ 思考完成", true, account.config, log);
458
+ await finishAICard(cardSnapshot, "✅ 思考完成", account.config, log);
459
+ } catch (e) {
460
+ log.warn(`[DingTalk][closeStreaming] 原卡定格思考完成失败(忽略):${e?.message || e}`);
461
+ }
462
+ };
463
+ if (!hadRealAnswer) {
464
+ log.info(`[DingTalk][closeStreaming] answerCard 模式:无真实答案,仅定格原卡思考完成(不建答案卡)`);
465
+ await finalizeOriginalToDone();
464
466
  } else {
465
- log.warn(`[DingTalk][closeStreaming] 答案卡创建失败,降级定稿原卡`);
466
- await finishAICard(cardSnapshot, finalText, account.config, log);
467
+ log.info(`[DingTalk][closeStreaming] answerCard 模式:原卡定格思考完成 + 新建答案卡`);
468
+ await finalizeOriginalToDone();
469
+ const answerCard = await createAICardForTarget(account.config, target, log, ANSWER_CARD_TEMPLATE_ID);
470
+ if (answerCard) {
471
+ await finishAICard(answerCard, finalText, account.config, log);
472
+ log.info(`[DingTalk][closeStreaming] ✅ 答案卡投放成功`);
473
+ } else {
474
+ log.warn(`[DingTalk][closeStreaming] 答案卡创建失败,降级定稿原卡`);
475
+ await finishAICard(cardSnapshot, finalText, account.config, log);
476
+ }
467
477
  }
468
478
  } else {
469
479
  log.info(`[DingTalk][closeStreaming] 准备调用 finishAICard,文本长度=${finalText.length}`);
@@ -666,6 +676,23 @@ function createDingtalkReplyDispatcher(params) {
666
676
  } else log.debug(`[DingTalk][onPartialReply] 节流控制,跳过本次更新(距离上次更新 ${now - lastUpdateTime}ms)`);
667
677
  } else log.warn(`[DingTalk][onPartialReply] ⚠️ AI Card 不存在,跳过更新`);
668
678
  } },
679
+ onToolStart: async (payload) => {
680
+ const toolName = (payload?.name || "").trim();
681
+ if (!toolName) return;
682
+ if (payload?.phase === "end" || payload?.phase === "complete") return;
683
+ if (!streamingEnabled || asyncMode || finalMarkedText !== null) return;
684
+ try {
685
+ await startStreaming();
686
+ if (!currentCardTarget) return;
687
+ const now = Date.now();
688
+ if (now - lastUpdateTime < updateInterval) return;
689
+ lastUpdateTime = now;
690
+ await streamAICard(currentCardTarget, `🔧 正在调用工具:${toolName}`, false, account.config, log, account.config?.cardContentVar || "msgContent");
691
+ log.debug(`[DingTalk][onToolStart] 工具进度写卡:${toolName}(phase=${payload?.phase})`);
692
+ } catch (e) {
693
+ if (!isQpsLimitError(e)) log.warn(`[DingTalk][onToolStart] 工具进度写卡失败:${e?.message || e}`);
694
+ }
695
+ },
669
696
  onCommandOutput: (payload) => {
670
697
  const dwsMatch = (payload.title || payload.name || "").match(DWS_PRODUCT_PATTERN) || payload.output?.match(DWS_PRODUCT_PATTERN);
671
698
  if (dwsMatch) {
@@ -922,7 +922,7 @@ async function monitorDingtalkProvider(opts = {}) {
922
922
  const log = createLogger(cfg.channels?.["dingtalk-connector"]?.debug ?? false);
923
923
  const [accountsModule, monitorAccountModule, monitorSingleModule] = await Promise.all([
924
924
  import("./accounts-BQptOmgB.mjs"),
925
- import("./message-handler-BVUTuUpc.mjs"),
925
+ import("./message-handler-CCyUaGHW.mjs"),
926
926
  import("./connection-DHHFFNQJ.mjs")
927
927
  ]);
928
928
  const { resolveDingtalkAccount, listEnabledDingtalkAccounts } = accountsModule;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jeik/dingtalk-connector",
3
- "version": "0.8.21-fix13",
3
+ "version": "0.8.21-fix16",
4
4
  "description": "OpenClaw DingTalk channel plugin (fork) | 钉钉 OpenClaw 渠道插件(fork 自官方,含标记/卡片渲染修复)",
5
5
  "type": "module",
6
6
  "exports": {
@@ -391,6 +391,9 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
391
391
  try {
392
392
  // 选定最终答案:marker 优先 → 最近非 reasoning 答案 → accumulatedText 兜底,并剥离尾标记
393
393
  let finalText = finalClean(pickFinalText());
394
+ // 是否有真实对话答案(在套兜底文案之前判断)。纯工具进度/无回复时为 false,
395
+ // 用于 answerCard 模式下决定"不另建无文本输出答案卡"。
396
+ const hadRealAnswer = finalText.trim().length > 0;
394
397
  log.info(
395
398
  `[DingTalk][closeStreaming] 最终答案来源=${finalMarkedText !== null ? "marker[-final-]" : (lastAnswerText ? "非reasoning答案" : "accumulatedText兜底")},长度=${finalText.length}`
396
399
  );
@@ -506,27 +509,40 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
506
509
  // 代价:多一条消息,但渲染正确、更快。
507
510
  const useAnswerCard = (account.config as any)?.answerCard === true;
508
511
  if (useAnswerCard) {
509
- log.info(`[DingTalk][closeStreaming] answerCard 模式:原卡定格思考完成 + 新建答案卡`);
510
- // 1. 原卡定格"思考完成"
511
- try {
512
- await finishAICard(cardSnapshot as any, "✅ 思考完成", account.config as DingtalkConfig, log);
513
- } catch (e: any) {
514
- log.warn(`[DingTalk][closeStreaming] 原卡定格思考完成失败(忽略):${e?.message || e}`);
515
- }
516
- // 2. 新建答案专用卡(静态模板)并投放最终答案
517
- const answerCard = await createAICardForTarget(
518
- account.config as DingtalkConfig,
519
- target,
520
- log,
521
- ANSWER_CARD_TEMPLATE_ID,
522
- );
523
- if (answerCard) {
524
- await finishAICard(answerCard, finalText, account.config as DingtalkConfig, log);
525
- log.info(`[DingTalk][closeStreaming] ✅ 答案卡投放成功`);
512
+ // 原卡定格"思考完成"。必须用流式端点 streamAICard 覆盖可见内容——
513
+ // FINISHED(/card/instances) 不会刷新已流式过的内容(钉钉 bug),先 streamAICard 盖文本再 FINISHED。
514
+ const finalizeOriginalToDone = async () => {
515
+ try {
516
+ await streamAICard(cardSnapshot as any, "✅ 思考完成", true, account.config as DingtalkConfig, log);
517
+ await finishAICard(cardSnapshot as any, "✅ 思考完成", account.config as DingtalkConfig, log);
518
+ } catch (e: any) {
519
+ log.warn(`[DingTalk][closeStreaming] 原卡定格思考完成失败(忽略):${e?.message || e}`);
520
+ }
521
+ };
522
+
523
+ if (!hadRealAnswer) {
524
+ // 只有工具进度 / 没真实对话答案 → 只把原卡定格"思考完成",不另建"无文本输出"答案卡,
525
+ // 避免重新引入"多段/纯工具轮次留空卡"问题。
526
+ log.info(`[DingTalk][closeStreaming] answerCard 模式:无真实答案,仅定格原卡思考完成(不建答案卡)`);
527
+ await finalizeOriginalToDone();
526
528
  } else {
527
- // 答案卡建失败 → 降级直接定稿原卡,保证用户能看到回复
528
- log.warn(`[DingTalk][closeStreaming] 答案卡创建失败,降级定稿原卡`);
529
- await finishAICard(cardSnapshot as any, finalText, account.config as DingtalkConfig, log);
529
+ log.info(`[DingTalk][closeStreaming] answerCard 模式:原卡定格思考完成 + 新建答案卡`);
530
+ await finalizeOriginalToDone();
531
+ // 新建答案专用卡(静态模板)并投放最终答案
532
+ const answerCard = await createAICardForTarget(
533
+ account.config as DingtalkConfig,
534
+ target,
535
+ log,
536
+ ANSWER_CARD_TEMPLATE_ID,
537
+ );
538
+ if (answerCard) {
539
+ await finishAICard(answerCard, finalText, account.config as DingtalkConfig, log);
540
+ log.info(`[DingTalk][closeStreaming] ✅ 答案卡投放成功`);
541
+ } else {
542
+ // 答案卡建失败 → 降级直接定稿原卡,保证用户能看到回复
543
+ log.warn(`[DingTalk][closeStreaming] 答案卡创建失败,降级定稿原卡`);
544
+ await finishAICard(cardSnapshot as any, finalText, account.config as DingtalkConfig, log);
545
+ }
530
546
  }
531
547
  } else {
532
548
  log.info(`[DingTalk][closeStreaming] 准备调用 finishAICard,文本长度=${finalText.length}`);
@@ -594,7 +610,7 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
594
610
 
595
611
  // 观察标记:更新最终答案认定(marker / 非 reasoning 兜底)
596
612
  observeReply(payload.text, (payload as any).isReasoning);
597
-
613
+
598
614
  // ✅ 在 final 响应时,先处理裸露的文件路径
599
615
  if (info?.kind === "final" && text.trim()) {
600
616
  const target: AICardTarget = isDirect
@@ -877,6 +893,32 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
877
893
  }
878
894
  },
879
895
  }),
896
+ // ===== 工具调用进度:开始调用工具时,往原卡流式显示"正在调用工具:工具名" =====
897
+ // OpenClaw 的 onToolStart 带干净的工具名(payload.name)。已进入最终答案 / 异步 / 非流式则跳过。
898
+ onToolStart: async (payload: { name?: string; phase?: string; args?: Record<string, unknown> }) => {
899
+ const toolName = (payload?.name || "").trim();
900
+ if (!toolName) return;
901
+ if (payload?.phase === "end" || payload?.phase === "complete") return;
902
+ if (!streamingEnabled || asyncMode || finalMarkedText !== null) return;
903
+ try {
904
+ await startStreaming();
905
+ if (!currentCardTarget) return;
906
+ const now = Date.now();
907
+ if (now - lastUpdateTime < updateInterval) return;
908
+ lastUpdateTime = now;
909
+ await streamAICard(
910
+ currentCardTarget as any,
911
+ `🔧 正在调用工具:${toolName}`,
912
+ false,
913
+ account.config as DingtalkConfig,
914
+ log,
915
+ (account.config as DingtalkConfig)?.cardContentVar as string || "msgContent",
916
+ );
917
+ log.debug(`[DingTalk][onToolStart] 工具进度写卡:${toolName}(phase=${payload?.phase})`);
918
+ } catch (e: any) {
919
+ if (!isQpsLimitError(e)) log.warn(`[DingTalk][onToolStart] 工具进度写卡失败:${e?.message || e}`);
920
+ }
921
+ },
880
922
  // ===== 养成系统:监听 dws 命令执行 =====
881
923
  onCommandOutput: (payload: {
882
924
  itemId?: string;