@qwen-code/channel-dingtalk 0.23.4 → 0.23.5-preview.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.
@@ -1,7 +1,7 @@
1
1
  import { ChannelBase } from '@qwen-code/channel-base';
2
2
  import { DingtalkInteractiveCardClient } from './interactive-card-client.js';
3
3
  import { type DingtalkCardCallback, type DingtalkCardCallbackResult } from './interactive-card-types.js';
4
- import type { ChannelConfig, ChannelBaseOptions, Envelope, ChannelAgentBridge, ChannelOutputSegmentContext, ChannelOutputSegmentEndReason, ChannelPermissionRequestContext, ChannelTaskLifecycleEvent, ChannelUserInputRequestContext, SessionTarget, UserInputPresentationResult } from '@qwen-code/channel-base';
4
+ import type { BackgroundResponseContext, ChannelConfig, ChannelBaseOptions, Envelope, ChannelAgentBridge, ChannelOutputSegmentContext, ChannelOutputSegmentEndReason, ChannelPermissionRequestContext, ChannelTaskLifecycleEvent, ChannelUserInputRequestContext, SessionTarget, UserInputPresentationResult } from '@qwen-code/channel-base';
5
5
  export declare class DingtalkChannel extends ChannelBase {
6
6
  private client;
7
7
  private readonly atSender;
@@ -19,7 +19,7 @@ export declare class DingtalkChannel extends ChannelBase {
19
19
  private reactionStates;
20
20
  /** sessionId → reaction keys, so a dead session's reactions can be recalled. */
21
21
  private sessionReactionKeys;
22
- /** Settles when the reaction cleanup queued by disconnect() has run. */
22
+ /** Settles after background output and reaction cleanup finish on disconnect. */
23
23
  private disconnectDrain;
24
24
  /**
25
25
  * Real inbound message ids (insertion-ordered, size-capped). Unlike the
@@ -33,6 +33,7 @@ export declare class DingtalkChannel extends ChannelBase {
33
33
  */
34
34
  private proactiveToken?;
35
35
  private readonly interactiveCardConfig;
36
+ private readonly outputMode?;
36
37
  protected readonly interactiveCardClient?: DingtalkInteractiveCardClient;
37
38
  private statusCardController?;
38
39
  private questionCardController?;
@@ -42,6 +43,7 @@ export declare class DingtalkChannel extends ChannelBase {
42
43
  private readonly cardRunBySession;
43
44
  private readonly cardRuns;
44
45
  private readonly fileProjectors;
46
+ private readonly backgroundOutputCoordinator;
45
47
  constructor(name: string, config: ChannelConfig, bridge: ChannelAgentBridge, options?: ChannelBaseOptions);
46
48
  private createClient;
47
49
  private installStructuredDownstreamHandler;
@@ -119,6 +121,7 @@ export declare class DingtalkChannel extends ChannelBase {
119
121
  * the sessions on a fresh bridge.
120
122
  */
121
123
  onBridgeDisconnected(): void;
124
+ protected onSessionRetiring(sessionId: string): void;
122
125
  protected onTaskLifecycle(event: ChannelTaskLifecycleEvent): void;
123
126
  protected onPromptBufferDropped(_chatId: string, sessionId: string, messageIds: string[]): void;
124
127
  protected onPromptBufferDrained(_chatId: string, sessionId: string, messageIds: string[]): void;
@@ -128,7 +131,28 @@ export declare class DingtalkChannel extends ChannelBase {
128
131
  protected processInbound(envelope: Envelope): Promise<void>;
129
132
  private untrackBufferedMentionTarget;
130
133
  protected onPromptEnd(chatId: string, sessionId: string, messageId?: string): void;
131
- protected sendResponseMessage(chatId: string, text: string, sessionId: string, sourceLabel?: string, prepared?: boolean): Promise<void>;
134
+ dispatchBackgroundResponse(sessionId: string, text: string, context?: BackgroundResponseContext): Promise<void>;
135
+ private createBackgroundOutputDelivery;
136
+ private formatBackgroundOutputHeader;
137
+ private formatBackgroundTaskLabel;
138
+ private prepareBackgroundOutput;
139
+ protected deliverBackgroundResponseToTarget(sessionId: string, text: string, delivery: {
140
+ target: SessionTarget;
141
+ sourceLabel?: string;
142
+ }, prepared?: boolean, result?: {
143
+ status: string;
144
+ partial?: boolean;
145
+ }): Promise<void>;
146
+ /**
147
+ * Body-identical to the base implementation — this override exists only to
148
+ * widen the signature, which the base seam does not declare. The background
149
+ * output delivery passes `prepared` so the body it already projected isn't
150
+ * projected a second time (that would re-upload its files), and
151
+ * `failOnHttpError` so a failed send throws instead of being swallowed,
152
+ * letting the flush capture the delivery plan for the next retry.
153
+ */
154
+ protected deliverBackgroundReply(chatId: string, text: string, sessionId: string, sourceLabel?: string, prepared?: boolean, failOnHttpError?: boolean): Promise<void>;
155
+ protected sendResponseMessage(chatId: string, text: string, sessionId: string, sourceLabel?: string, prepared?: boolean, failOnHttpError?: boolean): Promise<void>;
132
156
  private sendFallbackReply;
133
157
  protected onResponseComplete(chatId: string, text: string, sessionId: string, segment?: ChannelOutputSegmentContext): Promise<void>;
134
158
  protected onOutputSegmentEnd(_chatId: string, _sessionId: string, segment: ChannelOutputSegmentContext, reason: ChannelOutputSegmentEndReason): void | Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"DingtalkAdapter.d.ts","sourceRoot":"","sources":["../src/DingtalkAdapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DingtalkAdapter.d.ts","sourceRoot":"","sources":["../src/DingtalkAdapter.ts"],"names":[],"mappings":"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"}
@@ -4,16 +4,16 @@ import { basename, join } from 'node:path';
4
4
  import { tmpdir } from 'node:os';
5
5
  import { Buffer } from 'node:buffer';
6
6
  import { DWClient, TOPIC_CARD, TOPIC_ROBOT, EventAck, } from 'dingtalk-stream-sdk-nodejs';
7
- import { ChannelBase, isTerminalTaskLifecycleType, sanitizeLogText, sanitizePromptText, sanitizeSenderName, truncateUtf16Units, } from '@qwen-code/channel-base';
7
+ import { ChannelBase, BackgroundOutputCoordinator, parseChannelOutputMode, isTerminalTaskLifecycleType, sanitizeLogText, sanitizePromptText, sanitizeSenderName, truncateUtf16Units, } from '@qwen-code/channel-base';
8
8
  import { DINGTALK_CHUNK_LIMIT, escapeDingTalkMarkdown, normalizeDingTalkMarkdown, extractTitle, } from './markdown.js';
9
9
  import { downloadMedia } from './media.js';
10
10
  import { DingTalkMediaUploadError, findImageMarkers, readValidatedImage, replaceImageMarkers, uploadDingTalkImage, } from './outbound-image.js';
11
11
  import { FILE_UNAVAILABLE_NOTICE, OutboundFileProjector, projectFileText, readValidatedFile, safeFileName, uploadDingTalkFile, } from './outbound-file.js';
12
12
  import { DingtalkConnectionManager, } from './DingtalkConnectionManager.js';
13
- import { DingtalkCardRequestError, DingtalkInteractiveCardClient, } from './interactive-card-client.js';
13
+ import { DingtalkCardRequestError, DingtalkInteractiveCardClient, isRetryableDingtalkStatus, } from './interactive-card-client.js';
14
14
  import { parseDingtalkCardActorId, parseDingtalkCardCallback, parseDingtalkInteractiveCardConfig, } from './interactive-card-types.js';
15
15
  import { StatusCardController } from './status-card-controller.js';
16
- import { isChinesePresentationLanguage, lifecyclePresentationPhase, presentationPhaseLabel, } from './presentation-phase.js';
16
+ import { isChinesePresentationLanguage, lifecyclePresentationPhase, markPartialOutput, partialOutputLabel, presentationPhaseLabel, } from './presentation-phase.js';
17
17
  import { QuestionCardController } from './question-card-controller.js';
18
18
  import { PermissionCardController } from './permission-card-controller.js';
19
19
  import { DingtalkInteractionPresenter } from './interaction-presenter.js';
@@ -625,6 +625,28 @@ async function withConnectLoggingSuppressed(connect) {
625
625
  }
626
626
  }
627
627
  }
628
+ class ProactiveTextDeliveryError extends Error {
629
+ plan;
630
+ retryable;
631
+ constructor(plan, cause) {
632
+ super(cause instanceof Error ? cause.message : String(cause), { cause });
633
+ this.plan = plan;
634
+ if (cause instanceof DingtalkCardRequestError) {
635
+ this.retryable = cause.retryable;
636
+ }
637
+ }
638
+ }
639
+ class ReplyTextDeliveryError extends Error {
640
+ plan;
641
+ retryable;
642
+ constructor(plan, cause) {
643
+ super(cause instanceof Error ? cause.message : String(cause), { cause });
644
+ this.plan = plan;
645
+ if (cause instanceof DingtalkCardRequestError) {
646
+ this.retryable = cause.retryable;
647
+ }
648
+ }
649
+ }
628
650
  export class DingtalkChannel extends ChannelBase {
629
651
  client;
630
652
  atSender;
@@ -642,7 +664,7 @@ export class DingtalkChannel extends ChannelBase {
642
664
  reactionStates = new Map();
643
665
  /** sessionId → reaction keys, so a dead session's reactions can be recalled. */
644
666
  sessionReactionKeys = new Map();
645
- /** Settles when the reaction cleanup queued by disconnect() has run. */
667
+ /** Settles after background output and reaction cleanup finish on disconnect. */
646
668
  disconnectDrain;
647
669
  /**
648
670
  * Real inbound message ids (insertion-ordered, size-capped). Unlike the
@@ -656,6 +678,7 @@ export class DingtalkChannel extends ChannelBase {
656
678
  */
657
679
  proactiveToken;
658
680
  interactiveCardConfig;
681
+ outputMode;
659
682
  interactiveCardClient;
660
683
  statusCardController;
661
684
  questionCardController;
@@ -668,6 +691,7 @@ export class DingtalkChannel extends ChannelBase {
668
691
  // boundary, input requested) mints a fresh segment UUID but the projection
669
692
  // state must survive it, or a marker split across the reset leaks.
670
693
  fileProjectors = new Map();
694
+ backgroundOutputCoordinator;
671
695
  constructor(name, config, bridge, options) {
672
696
  super(name, config, bridge, options);
673
697
  this.atSender =
@@ -688,6 +712,19 @@ export class DingtalkChannel extends ChannelBase {
688
712
  this.config.instructions += FILE_INSTRUCTIONS;
689
713
  }
690
714
  this.interactiveCardConfig = parseDingtalkInteractiveCardConfig(config.interactiveCards);
715
+ this.outputMode = parseChannelOutputMode(name, config.outputMode, true);
716
+ this.backgroundOutputCoordinator = new BackgroundOutputCoordinator({
717
+ outputMode: this.outputMode,
718
+ getTarget: (sessionId) => this.router.getTarget(sessionId),
719
+ getSourceLabel: (sessionId) => this.getBackgroundResponseSourceLabel(sessionId),
720
+ resolveDelivery: (sessionId) => this.resolveBackgroundResponseDelivery(sessionId),
721
+ createDelivery: (sessionId, target) => this.createBackgroundOutputDelivery(sessionId, target),
722
+ isRetryableError: (error) => !((error instanceof ProactiveTextDeliveryError ||
723
+ error instanceof ReplyTextDeliveryError ||
724
+ error instanceof DingtalkCardRequestError) &&
725
+ error.retryable === false),
726
+ log: (message) => process.stderr.write(`[DingTalk:${this.name}] ${message}`),
727
+ });
691
728
  if (!config.clientId || !config.clientSecret) {
692
729
  throw new Error(`Channel "${name}" requires clientId and clientSecret for DingTalk.`);
693
730
  }
@@ -744,20 +781,18 @@ export class DingtalkChannel extends ChannelBase {
744
781
  },
745
782
  });
746
783
  }
747
- if (this.statusCardController ||
748
- this.questionCardController ||
749
- this.permissionCardController) {
750
- this.interactionPresenter = new DingtalkInteractionPresenter({
751
- statusCards: this.statusCardController,
752
- questionCards: this.questionCardController,
753
- permissionCards: this.permissionCardController,
754
- ...(options?.displayLanguage
755
- ? { language: options.displayLanguage }
756
- : {}),
757
- sendFallback: (chatId, text, sessionId, sourceLabel) => this.sendFallbackReply(chatId, text, sessionId, sourceLabel),
758
- });
759
- }
760
784
  }
785
+ this.interactionPresenter = new DingtalkInteractionPresenter({
786
+ outputMode: this.outputMode,
787
+ prepareOutput: (chatId, text) => this.prepareReplyOutput(chatId, text),
788
+ statusCards: this.statusCardController,
789
+ questionCards: this.questionCardController,
790
+ permissionCards: this.permissionCardController,
791
+ ...(options?.displayLanguage
792
+ ? { language: options.displayLanguage }
793
+ : {}),
794
+ sendFallback: (chatId, text, sessionId, sourceLabel) => this.sendFallbackReply(chatId, text, sessionId, sourceLabel),
795
+ });
761
796
  if (useConnectionManager) {
762
797
  this.connectionManager = new DingtalkConnectionManager({
763
798
  initialClient: this.client,
@@ -1121,11 +1156,14 @@ export class DingtalkChannel extends ChannelBase {
1121
1156
  }
1122
1157
  return replaceImageMarkers(text, markers, replacements);
1123
1158
  }
1124
- async sendReply(chatId, text, atUserId, sourceLabel, prepared = false) {
1159
+ async sendReply(chatId, text, atUserId, sourceLabel, prepared = false, failOnHttpError = false) {
1125
1160
  // chatId is a conversationId — resolve to the latest sessionWebhook.
1126
1161
  const webhook = this.webhooks.get(chatId);
1127
1162
  if (!webhook) {
1128
1163
  process.stderr.write(`[DingTalk:${this.name}] No webhook for chatId ${chatId}, cannot send.\n`);
1164
+ if (failOnHttpError) {
1165
+ throw new Error('DingTalk session webhook unavailable');
1166
+ }
1129
1167
  return;
1130
1168
  }
1131
1169
  const outgoingText = prepared
@@ -1134,7 +1172,12 @@ export class DingtalkChannel extends ChannelBase {
1134
1172
  if (!outgoingText.trim())
1135
1173
  return;
1136
1174
  const plan = this.createReplyTextDelivery(outgoingText, atUserId, sourceLabel);
1137
- await this.deliverReplyText(chatId, plan);
1175
+ try {
1176
+ await this.deliverReplyText(chatId, plan, failOnHttpError);
1177
+ }
1178
+ catch (error) {
1179
+ throw new ReplyTextDeliveryError(plan, error);
1180
+ }
1138
1181
  }
1139
1182
  createReplyTextDelivery(outgoingText, atUserId, sourceLabel) {
1140
1183
  const mentionPrefix = atUserId ? `@${atUserId}\n\n` : '';
@@ -1153,9 +1196,12 @@ export class DingtalkChannel extends ChannelBase {
1153
1196
  ...(atUserId ? { atUserId } : {}),
1154
1197
  };
1155
1198
  }
1156
- async deliverReplyText(chatId, plan) {
1199
+ async deliverReplyText(chatId, plan, failOnHttpError = false) {
1157
1200
  const webhook = this.webhooks.get(chatId);
1158
1201
  if (!webhook) {
1202
+ if (failOnHttpError) {
1203
+ throw new Error('DingTalk session webhook unavailable');
1204
+ }
1159
1205
  return;
1160
1206
  }
1161
1207
  while (plan.nextChunk < plan.chunks.length) {
@@ -1200,6 +1246,24 @@ export class DingtalkChannel extends ChannelBase {
1200
1246
  if (!resp.ok) {
1201
1247
  const detail = await resp.text().catch(() => '');
1202
1248
  process.stderr.write(`[DingTalk:${this.name}] sendMessage failed: HTTP ${resp.status} ${detail}\n`);
1249
+ if (failOnHttpError) {
1250
+ throw new DingtalkCardRequestError(`DingTalk reply send failed: HTTP ${resp.status} ${detail}`, isRetryableDingtalkStatus(resp.status));
1251
+ }
1252
+ }
1253
+ else if (failOnHttpError) {
1254
+ const payload = (await resp
1255
+ .clone()
1256
+ .json()
1257
+ .catch(() => undefined));
1258
+ const response = payload && typeof payload === 'object'
1259
+ ? payload
1260
+ : undefined;
1261
+ const value = response?.['errcode'] ?? response?.['code'];
1262
+ if (value !== undefined && String(value) !== '0') {
1263
+ const detail = sanitizeLogText(String(response?.['errmsg'] ?? response?.['message'] ?? value), 300);
1264
+ process.stderr.write(`[DingTalk:${this.name}] sendMessage failed: ${detail}\n`);
1265
+ throw new Error(`DingTalk reply send failed: ${detail}`);
1266
+ }
1203
1267
  }
1204
1268
  plan.nextChunk++;
1205
1269
  }
@@ -1240,7 +1304,12 @@ export class DingtalkChannel extends ChannelBase {
1240
1304
  const plan = await this.createProactiveTextDelivery(target, text, sourceLabel);
1241
1305
  if (!plan)
1242
1306
  return;
1243
- await this.deliverProactiveText(target, plan);
1307
+ try {
1308
+ await this.deliverProactiveText(target, plan);
1309
+ }
1310
+ catch (error) {
1311
+ throw new ProactiveTextDeliveryError(plan, error);
1312
+ }
1244
1313
  }
1245
1314
  async createProactiveTextDelivery(target, text, sourceLabel) {
1246
1315
  const outgoingText = await this.prepareFileOutput(text, (file, mediaId) => this.sendProactiveFile(target, file, mediaId));
@@ -1362,7 +1431,7 @@ export class DingtalkChannel extends ChannelBase {
1362
1431
  if (!resp.ok) {
1363
1432
  const detail = sanitizeLogText(await resp.text().catch(() => ''), 300);
1364
1433
  process.stderr.write(`[DingTalk:${this.name}] proactive send failed (${targetKind}, ${chunkLabel}): HTTP ${resp.status} ${detail}\n`);
1365
- throw new Error(`DingTalk proactive send failed: HTTP ${resp.status}${detail ? ` ${detail}` : ''}`);
1434
+ throw new DingtalkCardRequestError(`DingTalk proactive send failed: HTTP ${resp.status}${detail ? ` ${detail}` : ''}`, isRetryableDingtalkStatus(resp.status));
1366
1435
  }
1367
1436
  if (target.isGroup === true) {
1368
1437
  if (msgKey !== PROACTIVE_FILE_MSG_KEY) {
@@ -1484,7 +1553,7 @@ export class DingtalkChannel extends ChannelBase {
1484
1553
  for (const state of reactionStates) {
1485
1554
  this.finishReaction(state.chatId, state.messageId, state.sessionId);
1486
1555
  }
1487
- this.statusCardController?.dispose();
1556
+ const backgroundDrain = this.backgroundOutputCoordinator.drain();
1488
1557
  this.activeReactionKeys.clear();
1489
1558
  this.sessionReactionKeys.clear();
1490
1559
  this.reactionStates.clear();
@@ -1495,7 +1564,12 @@ export class DingtalkChannel extends ChannelBase {
1495
1564
  this.client.disconnect();
1496
1565
  }
1497
1566
  process.stderr.write(`[DingTalk:${this.name}] Disconnected.\n`);
1498
- this.disconnectDrain = Promise.allSettled(reactionStates.map((state) => state.tail)).then(() => undefined);
1567
+ this.disconnectDrain = Promise.allSettled([
1568
+ backgroundDrain,
1569
+ ...reactionStates.map((state) => state.tail),
1570
+ ]).then(() => {
1571
+ this.statusCardController?.dispose();
1572
+ });
1499
1573
  }
1500
1574
  waitForDisconnect() {
1501
1575
  return this.disconnectDrain ?? Promise.resolve();
@@ -1764,6 +1838,8 @@ export class DingtalkChannel extends ChannelBase {
1764
1838
  }
1765
1839
  }
1766
1840
  this.sessionMentionTargets.delete(sessionId);
1841
+ // Preserve a pending background turn's latest result if its session dies.
1842
+ this.backgroundOutputCoordinator.drain(sessionId);
1767
1843
  const cardRunId = this.cardRunBySession.get(sessionId);
1768
1844
  if (cardRunId) {
1769
1845
  this.cardRunBySession.delete(sessionId);
@@ -1787,6 +1863,7 @@ export class DingtalkChannel extends ChannelBase {
1787
1863
  * the sessions on a fresh bridge.
1788
1864
  */
1789
1865
  onBridgeDisconnected() {
1866
+ void this.backgroundOutputCoordinator.drain();
1790
1867
  for (const [sessionId, keys] of this.sessionReactionKeys) {
1791
1868
  this.sessionReactionKeys.delete(sessionId);
1792
1869
  for (const { messageId, chatId } of keys.values()) {
@@ -1799,6 +1876,9 @@ export class DingtalkChannel extends ChannelBase {
1799
1876
  this.cardRuns.delete(runId);
1800
1877
  }
1801
1878
  }
1879
+ onSessionRetiring(sessionId) {
1880
+ this.backgroundOutputCoordinator.drain(sessionId);
1881
+ }
1802
1882
  onTaskLifecycle(event) {
1803
1883
  if (event.type === 'started') {
1804
1884
  this.startReaction(event.chatId, event.messageId, event.sessionId);
@@ -1813,7 +1893,10 @@ export class DingtalkChannel extends ChannelBase {
1813
1893
  this.cardRuns.set(event.runId, inboundOwner);
1814
1894
  this.cardRunBySession.set(event.sessionId, event.runId);
1815
1895
  const sourceLabel = this.getResponseSourceLabel(event.sessionId);
1816
- this.interactionPresenter?.registerRun(event.runId, event.owner.id, inboundOwner.target, event.sessionId, inboundOwner.sender, ...(sourceLabel ? [sourceLabel] : []));
1896
+ const atUserId = this.atSender && event.messageId
1897
+ ? this.mentionTargets.get(event.messageId)
1898
+ : undefined;
1899
+ this.interactionPresenter?.registerRun(event.runId, event.owner.id, inboundOwner.target, event.sessionId, inboundOwner.sender, sourceLabel, (chatId, text, _sessionId, label) => this.sendReply(chatId, text, atUserId, label));
1817
1900
  this.interactionPresenter?.startStatusCard(event.runId);
1818
1901
  }
1819
1902
  return;
@@ -1944,13 +2027,177 @@ export class DingtalkChannel extends ChannelBase {
1944
2027
  this.sessionMentionTargets.delete(sessionId);
1945
2028
  this.stopReaction(chatId, messageId, sessionId);
1946
2029
  }
1947
- async sendResponseMessage(chatId, text, sessionId, sourceLabel, prepared = false) {
2030
+ async dispatchBackgroundResponse(sessionId, text, context) {
2031
+ const target = this.router.getTarget(sessionId);
2032
+ if (!target || target.channelName !== this.name) {
2033
+ return super.dispatchBackgroundResponse(sessionId, text, context);
2034
+ }
2035
+ if (await this.backgroundOutputCoordinator.dispatch(sessionId, text, context)) {
2036
+ return;
2037
+ }
2038
+ if (!text.trim())
2039
+ return;
2040
+ const delivery = await this.resolveBackgroundResponseDelivery(sessionId);
2041
+ if (!delivery || this.router.getTarget(sessionId) !== delivery.target)
2042
+ return;
2043
+ await this.deliverBackgroundResponseToTarget(sessionId, text, delivery, false, context);
2044
+ }
2045
+ createBackgroundOutputDelivery(sessionId, target) {
2046
+ let proactivePlan;
2047
+ let replyPlan;
2048
+ // Prepared output can already have sent attachment messages.
2049
+ let preparedReplyBody;
2050
+ let composedHeader;
2051
+ let composedTurnComplete = false;
2052
+ return async (output) => {
2053
+ const header = this.formatBackgroundOutputHeader(output);
2054
+ const body = [header, output.text].filter(Boolean).join('\n\n');
2055
+ const plan = proactivePlan ?? replyPlan;
2056
+ if (!plan || plan.nextChunk === 0) {
2057
+ const replaceHeader = (text) => {
2058
+ if (composedHeader === undefined || composedHeader === header) {
2059
+ return text;
2060
+ }
2061
+ const separator = output.text ? '\n\n' : '';
2062
+ const previous = composedHeader
2063
+ ? `${composedHeader}${separator}`
2064
+ : '';
2065
+ const next = header ? `${header}${separator}` : '';
2066
+ return text.startsWith(previous)
2067
+ ? `${next}${text.slice(previous.length)}`
2068
+ : text;
2069
+ };
2070
+ if (plan) {
2071
+ plan.title = extractTitle(body);
2072
+ const prefix = (replyPlan?.atUserId ? `@${replyPlan.atUserId}\n\n` : '') +
2073
+ (target.sourceLabel
2074
+ ? `${escapeDingTalkMarkdown(target.sourceLabel)}\n\n`
2075
+ : '');
2076
+ plan.chunks[0] =
2077
+ prefix + replaceHeader(plan.chunks[0].slice(prefix.length));
2078
+ }
2079
+ if (preparedReplyBody) {
2080
+ preparedReplyBody = replaceHeader(preparedReplyBody);
2081
+ }
2082
+ composedHeader = header;
2083
+ composedTurnComplete = output.turnComplete;
2084
+ }
2085
+ try {
2086
+ if (proactivePlan) {
2087
+ await this.deliverProactiveText(target.target, proactivePlan);
2088
+ }
2089
+ else if (replyPlan) {
2090
+ await this.deliverReplyText(target.target.chatId, replyPlan, true);
2091
+ }
2092
+ else {
2093
+ if (this.statusCardController ||
2094
+ !this.supportsProactiveSend() ||
2095
+ !this.supportsProactiveTarget(target.target)) {
2096
+ preparedReplyBody ??= await this.prepareBackgroundOutput(target.target, body);
2097
+ }
2098
+ await this.deliverBackgroundResponseToTarget(sessionId, preparedReplyBody ?? body, target, preparedReplyBody !== undefined, output);
2099
+ }
2100
+ return { turnComplete: composedTurnComplete };
2101
+ }
2102
+ catch (error) {
2103
+ if (error instanceof ProactiveTextDeliveryError) {
2104
+ proactivePlan = error.plan;
2105
+ }
2106
+ else if (error instanceof ReplyTextDeliveryError) {
2107
+ replyPlan = error.plan;
2108
+ }
2109
+ throw error;
2110
+ }
2111
+ };
2112
+ }
2113
+ formatBackgroundOutputHeader(delivery) {
2114
+ const chinese = isChinesePresentationLanguage(this.displayLanguage);
2115
+ const partialLabel = partialOutputLabel(this.displayLanguage);
2116
+ const icon = delivery.status === 'completed'
2117
+ ? '✅'
2118
+ : delivery.status === 'failed'
2119
+ ? '❌'
2120
+ : '⏹️';
2121
+ if (delivery.kind === 'agent') {
2122
+ if (delivery.text)
2123
+ return delivery.partial ? partialLabel : '';
2124
+ const status = delivery.status === 'completed'
2125
+ ? chinese
2126
+ ? '已完成'
2127
+ : 'completed'
2128
+ : delivery.status === 'failed'
2129
+ ? chinese
2130
+ ? '失败'
2131
+ : 'failed'
2132
+ : chinese
2133
+ ? '已停止'
2134
+ : 'stopped';
2135
+ return chinese
2136
+ ? `${icon} 后台任务${status}`
2137
+ : `${icon} Background task ${status}`;
2138
+ }
2139
+ const label = this.formatBackgroundTaskLabel(delivery.label);
2140
+ const kind = {
2141
+ shell: 'Shell',
2142
+ monitor: 'Monitor',
2143
+ workflow: 'Workflow',
2144
+ }[delivery.kind];
2145
+ return `## ${icon} ${kind} · ${label}${delivery.partial ? `${chinese ? '' : ' '}${partialLabel}` : ''}`;
2146
+ }
2147
+ formatBackgroundTaskLabel(label) {
2148
+ const normalized = label
2149
+ ?.replace(/\p{Cc}+/gu, ' ')
2150
+ .replace(/\s+/g, ' ')
2151
+ .trim();
2152
+ return escapeDingTalkMarkdown(normalized ||
2153
+ (isChinesePresentationLanguage(this.displayLanguage)
2154
+ ? '后台任务'
2155
+ : 'Background task'));
2156
+ }
2157
+ prepareBackgroundOutput(target, text) {
2158
+ return this.supportsProactiveSend() && this.supportsProactiveTarget(target)
2159
+ ? this.prepareFileOutput(text, (file, mediaId) => this.sendProactiveFile(target, file, mediaId))
2160
+ : this.prepareReplyOutput(target.chatId, text);
2161
+ }
2162
+ async deliverBackgroundResponseToTarget(sessionId, text, delivery, prepared = false, result) {
2163
+ const { target, sourceLabel } = delivery;
2164
+ if (this.statusCardController &&
2165
+ this.supportsProactiveDeliveryTarget(target) &&
2166
+ (target.isGroup === true || this.isStableTargetId(target.senderId))) {
2167
+ if (!prepared) {
2168
+ text = await this.prepareBackgroundOutput(target, text);
2169
+ prepared = true;
2170
+ }
2171
+ if (await this.statusCardController.deliverCompletedResult({
2172
+ chatId: target.isGroup ? target.chatId : target.senderId,
2173
+ isGroup: target.isGroup === true,
2174
+ }, text, sourceLabel, result)) {
2175
+ return;
2176
+ }
2177
+ }
2178
+ if (this.supportsProactiveSend() && this.supportsProactiveTarget(target)) {
2179
+ return super.deliverBackgroundResponseToTarget(sessionId, text, delivery);
2180
+ }
2181
+ await this.deliverBackgroundReply(target.chatId, text, sessionId, sourceLabel, prepared, true);
2182
+ }
2183
+ /**
2184
+ * Body-identical to the base implementation — this override exists only to
2185
+ * widen the signature, which the base seam does not declare. The background
2186
+ * output delivery passes `prepared` so the body it already projected isn't
2187
+ * projected a second time (that would re-upload its files), and
2188
+ * `failOnHttpError` so a failed send throws instead of being swallowed,
2189
+ * letting the flush capture the delivery plan for the next retry.
2190
+ */
2191
+ async deliverBackgroundReply(chatId, text, sessionId, sourceLabel, prepared = false, failOnHttpError = false) {
2192
+ await this.sendResponseMessage(chatId, text, sessionId, sourceLabel, prepared, failOnHttpError);
2193
+ }
2194
+ async sendResponseMessage(chatId, text, sessionId, sourceLabel, prepared = false, failOnHttpError = false) {
1948
2195
  const atUserId = this.atSender
1949
2196
  ? this.sessionMentionTargets.get(sessionId)
1950
2197
  : undefined;
1951
2198
  if (atUserId)
1952
2199
  this.sessionMentionTargets.delete(sessionId);
1953
- await this.sendReply(chatId, text, atUserId, sourceLabel ?? this.getResponseSourceLabel(sessionId), prepared);
2200
+ await this.sendReply(chatId, text, atUserId, sourceLabel ?? this.getResponseSourceLabel(sessionId), prepared, failOnHttpError);
1954
2201
  }
1955
2202
  async sendFallbackReply(chatId, text, sessionId, sourceLabel) {
1956
2203
  // Mid-run fallbacks must not consume the prompt's mention target: the
@@ -1972,7 +2219,9 @@ export class DingtalkChannel extends ChannelBase {
1972
2219
  return;
1973
2220
  }
1974
2221
  }
1975
- await this.sendResponseMessage(chatId, outgoingText, sessionId, segment?.sourceLabel, true);
2222
+ await this.sendResponseMessage(chatId, segment?.partial
2223
+ ? markPartialOutput(outgoingText, this.displayLanguage)
2224
+ : outgoingText, sessionId, segment?.sourceLabel, true);
1976
2225
  }
1977
2226
  onOutputSegmentEnd(_chatId, _sessionId, segment, reason) {
1978
2227
  if (reason === 'completed' ||