@qwen-code/channel-dingtalk 0.23.4-nightly.20260914.f024b37689 → 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.
- package/dist/DingtalkAdapter.d.ts +27 -3
- package/dist/DingtalkAdapter.d.ts.map +1 -1
- package/dist/DingtalkAdapter.js +277 -28
- package/dist/DingtalkAdapter.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/interaction-presenter.d.ts +4 -2
- package/dist/interaction-presenter.d.ts.map +1 -1
- package/dist/interaction-presenter.js +49 -37
- package/dist/interaction-presenter.js.map +1 -1
- package/dist/interactive-card-client.d.ts +1 -0
- package/dist/interactive-card-client.d.ts.map +1 -1
- package/dist/interactive-card-client.js +2 -2
- package/dist/interactive-card-client.js.map +1 -1
- package/dist/presentation-phase.d.ts +2 -0
- package/dist/presentation-phase.d.ts.map +1 -1
- package/dist/presentation-phase.js +7 -0
- package/dist/presentation-phase.js.map +1 -1
- package/dist/status-card-controller.d.ts +9 -1
- package/dist/status-card-controller.d.ts.map +1 -1
- package/dist/status-card-controller.js +47 -16
- package/dist/status-card-controller.js.map +1 -1
- package/package.json +2 -2
|
@@ -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
|
|
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
|
-
|
|
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":"AAYA,OAAO,EACL,WAAW,
|
|
1
|
+
{"version":3,"file":"DingtalkAdapter.d.ts","sourceRoot":"","sources":["../src/DingtalkAdapter.ts"],"names":[],"mappings":"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"}
|
package/dist/DingtalkAdapter.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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(
|
|
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.
|
|
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
|
|
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,
|
|
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' ||
|