@qwen-code/channel-dingtalk 0.23.1 → 0.23.2-nightly.20260909.2e212144d3
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 +9 -19
- package/dist/DingtalkAdapter.d.ts.map +1 -1
- package/dist/DingtalkAdapter.js +59 -98
- package/dist/DingtalkAdapter.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -1
- package/dist/interaction-presenter.d.ts +4 -1
- package/dist/interaction-presenter.d.ts.map +1 -1
- package/dist/interaction-presenter.js +15 -0
- package/dist/interaction-presenter.js.map +1 -1
- package/dist/interactive-card-types.d.ts +4 -0
- package/dist/interactive-card-types.d.ts.map +1 -1
- package/dist/interactive-card-types.js +21 -5
- package/dist/interactive-card-types.js.map +1 -1
- package/dist/outbound-file.d.ts +0 -1
- package/dist/outbound-file.d.ts.map +1 -1
- package/dist/outbound-file.js +0 -4
- package/dist/outbound-file.js.map +1 -1
- package/dist/permission-card-controller.d.ts +34 -0
- package/dist/permission-card-controller.d.ts.map +1 -0
- package/dist/permission-card-controller.js +297 -0
- package/dist/permission-card-controller.js.map +1 -0
- 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 { BackgroundResponseContext, ChannelConfig, ChannelBaseOptions, Envelope, ChannelAgentBridge, ChannelOutputSegmentContext, ChannelOutputSegmentEndReason, 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;
|
|
@@ -37,13 +37,12 @@ export declare class DingtalkChannel extends ChannelBase {
|
|
|
37
37
|
protected readonly interactiveCardClient?: DingtalkInteractiveCardClient;
|
|
38
38
|
private statusCardController?;
|
|
39
39
|
private questionCardController?;
|
|
40
|
+
private permissionCardController?;
|
|
40
41
|
private interactionPresenter?;
|
|
41
42
|
private readonly inboundCardOwners;
|
|
42
43
|
private readonly cardRunBySession;
|
|
43
44
|
private readonly cardRuns;
|
|
44
45
|
private readonly fileProjectors;
|
|
45
|
-
private readonly blockFileProjectors;
|
|
46
|
-
private readonly blockProjectionArmed;
|
|
47
46
|
private readonly backgroundResponseAggregations;
|
|
48
47
|
private readonly detachedBackgroundResponseAggregations;
|
|
49
48
|
private readonly pendingBackgroundResponseTerminals;
|
|
@@ -135,18 +134,6 @@ export declare class DingtalkChannel extends ChannelBase {
|
|
|
135
134
|
protected processInbound(envelope: Envelope): Promise<void>;
|
|
136
135
|
private untrackBufferedMentionTarget;
|
|
137
136
|
protected onPromptEnd(chatId: string, sessionId: string, messageId?: string): void;
|
|
138
|
-
/**
|
|
139
|
-
* Turn end is the only point where the block projector's held state can be
|
|
140
|
-
* settled: ChannelBase drains the turn's queued block sends before calling
|
|
141
|
-
* onPromptEnd, so everything already appended belongs to this turn. Flush
|
|
142
|
-
* the held candidate bytes (a trailing `[FILE:` prefix the stream never
|
|
143
|
-
* completed) before deleting the entry — a later delete-without-settle
|
|
144
|
-
* would silently drop them from the delivered answer. Also disarm the
|
|
145
|
-
* session: /clear eviction and session death settle WITHOUT draining the
|
|
146
|
-
* turn's send chain, and any block that lands afterwards must be dropped
|
|
147
|
-
* rather than recreating projector state.
|
|
148
|
-
*/
|
|
149
|
-
private settleBlockFileProjector;
|
|
150
137
|
/** Deliver every Agent segment immediately unless aggregation is enabled. */
|
|
151
138
|
dispatchBackgroundResponse(sessionId: string, text: string, context?: BackgroundResponseContext): Promise<void>;
|
|
152
139
|
private flushBackgroundResponseAggregation;
|
|
@@ -178,19 +165,22 @@ export declare class DingtalkChannel extends ChannelBase {
|
|
|
178
165
|
private formatBackgroundAgentResponse;
|
|
179
166
|
private formatBackgroundAgentLabel;
|
|
180
167
|
/**
|
|
181
|
-
*
|
|
182
|
-
* the
|
|
183
|
-
*
|
|
168
|
+
* Body-identical to the base implementation — this override exists only to
|
|
169
|
+
* widen the signature, which the base seam does not declare. The background
|
|
170
|
+
* aggregation flush passes `prepared` so the body it already projected isn't
|
|
171
|
+
* projected a second time (that would re-upload its files), and
|
|
172
|
+
* `failOnHttpError` so a failed send throws instead of being swallowed,
|
|
173
|
+
* letting the flush capture the delivery plan for the next retry.
|
|
184
174
|
*/
|
|
185
175
|
protected deliverBackgroundReply(chatId: string, text: string, sessionId: string, sourceLabel?: string, prepared?: boolean, failOnHttpError?: boolean): Promise<void>;
|
|
186
176
|
protected sendResponseMessage(chatId: string, text: string, sessionId: string, sourceLabel?: string, prepared?: boolean, failOnHttpError?: boolean): Promise<void>;
|
|
187
|
-
private projectBlockStreamChunk;
|
|
188
177
|
private sendFallbackReply;
|
|
189
178
|
protected onResponseComplete(chatId: string, text: string, sessionId: string, segment?: ChannelOutputSegmentContext): Promise<void>;
|
|
190
179
|
protected onOutputSegmentEnd(_chatId: string, _sessionId: string, segment: ChannelOutputSegmentContext, reason: ChannelOutputSegmentEndReason): void | Promise<void>;
|
|
191
180
|
protected onResponseChunk(_chatId: string, chunk: string, _sessionId: string, segment?: ChannelOutputSegmentContext): void;
|
|
192
181
|
private deleteFileProjectorsForRun;
|
|
193
182
|
protected presentUserInputRequest(context: ChannelUserInputRequestContext): Promise<UserInputPresentationResult>;
|
|
183
|
+
protected presentPermissionRequest(context: ChannelPermissionRequestContext): Promise<UserInputPresentationResult>;
|
|
194
184
|
/**
|
|
195
185
|
* Extract quoted/referenced message context from a reply.
|
|
196
186
|
* DingTalk provides this via text.repliedMsg (newer) or quoteMessage (legacy).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DingtalkAdapter.d.ts","sourceRoot":"","sources":["../src/DingtalkAdapter.ts"],"names":[],"mappings":"AAYA,OAAO,EACL,WAAW,EAMZ,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"DingtalkAdapter.d.ts","sourceRoot":"","sources":["../src/DingtalkAdapter.ts"],"names":[],"mappings":"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"}
|
package/dist/DingtalkAdapter.js
CHANGED
|
@@ -8,13 +8,14 @@ import { ChannelBase, isTerminalTaskLifecycleType, sanitizeLogText, sanitizeProm
|
|
|
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
|
-
import { FILE_UNAVAILABLE_NOTICE, OutboundFileProjector, projectFileText, readValidatedFile, safeFileName, uploadDingTalkFile,
|
|
11
|
+
import { FILE_UNAVAILABLE_NOTICE, OutboundFileProjector, projectFileText, readValidatedFile, safeFileName, uploadDingTalkFile, } from './outbound-file.js';
|
|
12
12
|
import { DingtalkConnectionManager, } from './DingtalkConnectionManager.js';
|
|
13
13
|
import { DingtalkCardRequestError, DingtalkInteractiveCardClient, } 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
16
|
import { isChinesePresentationLanguage, lifecyclePresentationPhase, presentationPhaseLabel, } from './presentation-phase.js';
|
|
17
17
|
import { QuestionCardController } from './question-card-controller.js';
|
|
18
|
+
import { PermissionCardController } from './permission-card-controller.js';
|
|
18
19
|
import { DingtalkInteractionPresenter } from './interaction-presenter.js';
|
|
19
20
|
function nonEmptyString(value) {
|
|
20
21
|
if (typeof value !== 'string')
|
|
@@ -683,6 +684,7 @@ export class DingtalkChannel extends ChannelBase {
|
|
|
683
684
|
interactiveCardClient;
|
|
684
685
|
statusCardController;
|
|
685
686
|
questionCardController;
|
|
687
|
+
permissionCardController;
|
|
686
688
|
interactionPresenter;
|
|
687
689
|
inboundCardOwners = new Map();
|
|
688
690
|
cardRunBySession = new Map();
|
|
@@ -691,14 +693,6 @@ export class DingtalkChannel extends ChannelBase {
|
|
|
691
693
|
// boundary, input requested) mints a fresh segment UUID but the projection
|
|
692
694
|
// state must survive it, or a marker split across the reset leaks.
|
|
693
695
|
fileProjectors = new Map();
|
|
694
|
-
blockFileProjectors = new Map();
|
|
695
|
-
// Sessions armed for block projection by onPromptStart and disarmed when
|
|
696
|
-
// the turn settles (or the session dies). A block send that finds NO
|
|
697
|
-
// projector state is only legitimate as a turn's FIRST block, which always
|
|
698
|
-
// lands while armed: late sends from an evicted (/clear) or dead session
|
|
699
|
-
// must be dropped, because recreating state would post the tail of a
|
|
700
|
-
// force-split [FILE: ...] marker verbatim.
|
|
701
|
-
blockProjectionArmed = new Set();
|
|
702
696
|
backgroundResponseAggregations = new Map();
|
|
703
697
|
detachedBackgroundResponseAggregations = new Set();
|
|
704
698
|
pendingBackgroundResponseTerminals = new Map();
|
|
@@ -719,8 +713,7 @@ export class DingtalkChannel extends ChannelBase {
|
|
|
719
713
|
else if (!this.config.instructions.includes('[IMAGE:')) {
|
|
720
714
|
this.config.instructions += IMAGE_INSTRUCTIONS;
|
|
721
715
|
}
|
|
722
|
-
if (config.
|
|
723
|
-
!this.config.instructions.includes('[FILE:')) {
|
|
716
|
+
if (!this.config.instructions.includes('[FILE:')) {
|
|
724
717
|
this.config.instructions += FILE_INSTRUCTIONS;
|
|
725
718
|
}
|
|
726
719
|
this.interactiveCardConfig = parseDingtalkInteractiveCardConfig(config.interactiveCards);
|
|
@@ -752,8 +745,7 @@ export class DingtalkChannel extends ChannelBase {
|
|
|
752
745
|
}
|
|
753
746
|
},
|
|
754
747
|
});
|
|
755
|
-
if (this.interactiveCardConfig.statusCard.enabled
|
|
756
|
-
config.blockStreaming !== 'on') {
|
|
748
|
+
if (this.interactiveCardConfig.statusCard.enabled) {
|
|
757
749
|
this.statusCardController = new StatusCardController({
|
|
758
750
|
client: this.interactiveCardClient,
|
|
759
751
|
cancelRun: (sessionId, runId) => this.requestPromptRunCancellation(sessionId, runId),
|
|
@@ -777,18 +769,28 @@ export class DingtalkChannel extends ChannelBase {
|
|
|
777
769
|
},
|
|
778
770
|
});
|
|
779
771
|
}
|
|
780
|
-
if (this.
|
|
772
|
+
if (this.interactiveCardConfig.permissionCard.enabled) {
|
|
773
|
+
this.permissionCardController = new PermissionCardController({
|
|
774
|
+
client: this.interactiveCardClient,
|
|
775
|
+
timeoutMs: this.interactiveCardConfig.permissionCard.timeoutMs,
|
|
776
|
+
locale: this.locale,
|
|
777
|
+
reserveRunProjection: (runId) => this.interactionPresenter?.reserveProjection(runId),
|
|
778
|
+
onError: (operation, error) => {
|
|
779
|
+
process.stderr.write(`[DingTalk:${this.name}] ${operation} failed: ${sanitizeLogText(String(error), 300)}\n`);
|
|
780
|
+
},
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
if (this.statusCardController ||
|
|
784
|
+
this.questionCardController ||
|
|
785
|
+
this.permissionCardController) {
|
|
781
786
|
this.interactionPresenter = new DingtalkInteractionPresenter({
|
|
782
787
|
statusCards: this.statusCardController,
|
|
783
788
|
questionCards: this.questionCardController,
|
|
789
|
+
permissionCards: this.permissionCardController,
|
|
784
790
|
...(options?.displayLanguage
|
|
785
791
|
? { language: options.displayLanguage }
|
|
786
792
|
: {}),
|
|
787
|
-
|
|
788
|
-
? {
|
|
789
|
-
sendFallback: (chatId, text, sessionId, sourceLabel) => this.sendFallbackReply(chatId, text, sessionId, sourceLabel),
|
|
790
|
-
}
|
|
791
|
-
: {}),
|
|
793
|
+
sendFallback: (chatId, text, sessionId, sourceLabel) => this.sendFallbackReply(chatId, text, sessionId, sourceLabel),
|
|
792
794
|
});
|
|
793
795
|
}
|
|
794
796
|
}
|
|
@@ -877,7 +879,12 @@ export class DingtalkChannel extends ChannelBase {
|
|
|
877
879
|
if (callback.actionId === 'btn_stop') {
|
|
878
880
|
return (this.statusCardController?.claimStop(callback.outTrackId, callback.actorId) ?? { kind: 'ignored', actorId: callback.actorId });
|
|
879
881
|
}
|
|
880
|
-
|
|
882
|
+
const permissionResult = this.permissionCardController?.claim(callback);
|
|
883
|
+
if (permissionResult && permissionResult.kind !== 'ignored') {
|
|
884
|
+
return permissionResult;
|
|
885
|
+
}
|
|
886
|
+
return (this.questionCardController?.claim(callback) ??
|
|
887
|
+
permissionResult ?? {
|
|
881
888
|
kind: 'ignored',
|
|
882
889
|
actorId: callback.actorId,
|
|
883
890
|
});
|
|
@@ -1095,10 +1102,6 @@ export class DingtalkChannel extends ChannelBase {
|
|
|
1095
1102
|
if (projection.markerCount > 0 || streamedMarkers > 0) {
|
|
1096
1103
|
process.stderr.write(`[DingTalk:${this.name}] file markers projected (final=${projection.markerCount}, streamed=${streamedMarkers})\n`);
|
|
1097
1104
|
}
|
|
1098
|
-
if (this.config.blockStreaming === 'on' &&
|
|
1099
|
-
(projection.markerCount > 0 || streamedMarkers > 0)) {
|
|
1100
|
-
return this.prepareOutgoingText(withFileUnavailableNotice(projection.text));
|
|
1101
|
-
}
|
|
1102
1105
|
const notices = [];
|
|
1103
1106
|
if (projection.invalidMarkers > 0) {
|
|
1104
1107
|
notices.push('[File delivery failed: invalid marker]');
|
|
@@ -1359,20 +1362,31 @@ export class DingtalkChannel extends ChannelBase {
|
|
|
1359
1362
|
return data.access_token;
|
|
1360
1363
|
}
|
|
1361
1364
|
sendCardInteractionFeedback(actorId, target) {
|
|
1365
|
+
const copy = this.locale === 'zh'
|
|
1366
|
+
? {
|
|
1367
|
+
title: '卡片操作',
|
|
1368
|
+
group: '仅任务发起人可以操作这张卡片,本次操作未生效。',
|
|
1369
|
+
direct: '你无权操作这张卡片,仅任务发起人可以提交或停止。',
|
|
1370
|
+
}
|
|
1371
|
+
: {
|
|
1372
|
+
title: 'Card interaction',
|
|
1373
|
+
group: 'Only the task initiator can operate this card. This action had no effect.',
|
|
1374
|
+
direct: 'You cannot operate this card. Only the task initiator can submit or stop it.',
|
|
1375
|
+
};
|
|
1362
1376
|
if (target?.isGroup) {
|
|
1363
1377
|
return this.sendProactiveChunk({
|
|
1364
1378
|
channelName: this.name,
|
|
1365
1379
|
senderId: actorId,
|
|
1366
1380
|
chatId: target.chatId,
|
|
1367
1381
|
isGroup: true,
|
|
1368
|
-
},
|
|
1382
|
+
}, copy.title, copy.group, 'card interaction feedback');
|
|
1369
1383
|
}
|
|
1370
1384
|
return this.sendProactiveChunk({
|
|
1371
1385
|
channelName: this.name,
|
|
1372
1386
|
senderId: actorId,
|
|
1373
1387
|
chatId: actorId,
|
|
1374
1388
|
isGroup: false,
|
|
1375
|
-
},
|
|
1389
|
+
}, copy.title, copy.direct, 'card interaction feedback');
|
|
1376
1390
|
}
|
|
1377
1391
|
async sendProactiveChunk(target, title, text, chunkLabel) {
|
|
1378
1392
|
return this.sendProactivePayload(target, PROACTIVE_MSG_KEY, { title, text }, chunkLabel);
|
|
@@ -1808,8 +1822,6 @@ export class DingtalkChannel extends ChannelBase {
|
|
|
1808
1822
|
}
|
|
1809
1823
|
/** Recall reactions left behind when a session dies without terminal lifecycle events. */
|
|
1810
1824
|
onSessionDied(sessionId) {
|
|
1811
|
-
this.blockProjectionArmed.delete(sessionId);
|
|
1812
|
-
this.blockFileProjectors.delete(sessionId);
|
|
1813
1825
|
for (const [runId, state] of this.fileProjectors) {
|
|
1814
1826
|
if (state.sessionId === sessionId)
|
|
1815
1827
|
this.fileProjectors.delete(runId);
|
|
@@ -1943,7 +1955,6 @@ export class DingtalkChannel extends ChannelBase {
|
|
|
1943
1955
|
}
|
|
1944
1956
|
}
|
|
1945
1957
|
onPromptStart(chatId, sessionId, messageId) {
|
|
1946
|
-
this.blockProjectionArmed.add(sessionId);
|
|
1947
1958
|
if (messageId) {
|
|
1948
1959
|
this.bufferedMentionTargets.delete(messageId);
|
|
1949
1960
|
this.untrackBufferedMentionTarget(sessionId, messageId);
|
|
@@ -2009,38 +2020,9 @@ export class DingtalkChannel extends ChannelBase {
|
|
|
2009
2020
|
this.bufferedMentionTargetsBySession.delete(sessionId);
|
|
2010
2021
|
}
|
|
2011
2022
|
onPromptEnd(chatId, sessionId, messageId) {
|
|
2012
|
-
this.settleBlockFileProjector(chatId, sessionId);
|
|
2013
2023
|
this.sessionMentionTargets.delete(sessionId);
|
|
2014
2024
|
this.stopReaction(chatId, messageId, sessionId);
|
|
2015
2025
|
}
|
|
2016
|
-
/**
|
|
2017
|
-
* Turn end is the only point where the block projector's held state can be
|
|
2018
|
-
* settled: ChannelBase drains the turn's queued block sends before calling
|
|
2019
|
-
* onPromptEnd, so everything already appended belongs to this turn. Flush
|
|
2020
|
-
* the held candidate bytes (a trailing `[FILE:` prefix the stream never
|
|
2021
|
-
* completed) before deleting the entry — a later delete-without-settle
|
|
2022
|
-
* would silently drop them from the delivered answer. Also disarm the
|
|
2023
|
-
* session: /clear eviction and session death settle WITHOUT draining the
|
|
2024
|
-
* turn's send chain, and any block that lands afterwards must be dropped
|
|
2025
|
-
* rather than recreating projector state.
|
|
2026
|
-
*/
|
|
2027
|
-
settleBlockFileProjector(chatId, sessionId) {
|
|
2028
|
-
this.blockProjectionArmed.delete(sessionId);
|
|
2029
|
-
const state = this.blockFileProjectors.get(sessionId);
|
|
2030
|
-
if (!state)
|
|
2031
|
-
return;
|
|
2032
|
-
this.blockFileProjectors.delete(sessionId);
|
|
2033
|
-
const tail = state.projector.complete();
|
|
2034
|
-
if (!tail.trim())
|
|
2035
|
-
return;
|
|
2036
|
-
// Deliberately fire-and-forget: complete() can only return a strict
|
|
2037
|
-
// prefix of '[FILE:' (at most 5 chars, no path bytes), so the worst case
|
|
2038
|
-
// is a stray fragment landing out of order with the next turn — not a
|
|
2039
|
-
// leak — and blocking settle on a delivery that may hang is worse.
|
|
2040
|
-
void this.sendReply(chatId, tail, undefined, this.getResponseSourceLabel(sessionId)).catch((err) => {
|
|
2041
|
-
process.stderr.write(`[DingTalk:${this.name}] projector tail delivery failed for session ${sessionId}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
2042
|
-
});
|
|
2043
|
-
}
|
|
2044
2026
|
/** Deliver every Agent segment immediately unless aggregation is enabled. */
|
|
2045
2027
|
async dispatchBackgroundResponse(sessionId, text, context) {
|
|
2046
2028
|
const target = this.router.getTarget(sessionId);
|
|
@@ -2683,54 +2665,23 @@ export class DingtalkChannel extends ChannelBase {
|
|
|
2683
2665
|
return escapeDingTalkMarkdown(normalized || '后台任务');
|
|
2684
2666
|
}
|
|
2685
2667
|
/**
|
|
2686
|
-
*
|
|
2687
|
-
* the
|
|
2688
|
-
*
|
|
2668
|
+
* Body-identical to the base implementation — this override exists only to
|
|
2669
|
+
* widen the signature, which the base seam does not declare. The background
|
|
2670
|
+
* aggregation flush passes `prepared` so the body it already projected isn't
|
|
2671
|
+
* projected a second time (that would re-upload its files), and
|
|
2672
|
+
* `failOnHttpError` so a failed send throws instead of being swallowed,
|
|
2673
|
+
* letting the flush capture the delivery plan for the next retry.
|
|
2689
2674
|
*/
|
|
2690
2675
|
async deliverBackgroundReply(chatId, text, sessionId, sourceLabel, prepared = false, failOnHttpError = false) {
|
|
2691
|
-
|
|
2692
|
-
return this.sendResponseMessage(chatId, text, sessionId, sourceLabel, prepared, failOnHttpError);
|
|
2693
|
-
}
|
|
2694
|
-
await this.sendReply(chatId, text, undefined, sourceLabel, prepared, failOnHttpError);
|
|
2676
|
+
await this.sendResponseMessage(chatId, text, sessionId, sourceLabel, prepared, failOnHttpError);
|
|
2695
2677
|
}
|
|
2696
2678
|
async sendResponseMessage(chatId, text, sessionId, sourceLabel, prepared = false, failOnHttpError = false) {
|
|
2697
|
-
|
|
2698
|
-
let consumesMention = true;
|
|
2699
|
-
if (this.config.blockStreaming === 'on') {
|
|
2700
|
-
const projected = this.projectBlockStreamChunk(text, sessionId);
|
|
2701
|
-
if (!projected.text.trim())
|
|
2702
|
-
return;
|
|
2703
|
-
outgoingText = projected.text;
|
|
2704
|
-
// A notice-only block must not consume the prompt's mention target:
|
|
2705
|
-
// the @mention belongs to the block carrying the actual answer.
|
|
2706
|
-
consumesMention = projected.hasContent;
|
|
2707
|
-
}
|
|
2708
|
-
const atUserId = consumesMention && this.atSender
|
|
2679
|
+
const atUserId = this.atSender
|
|
2709
2680
|
? this.sessionMentionTargets.get(sessionId)
|
|
2710
2681
|
: undefined;
|
|
2711
2682
|
if (atUserId)
|
|
2712
2683
|
this.sessionMentionTargets.delete(sessionId);
|
|
2713
|
-
await this.sendReply(chatId,
|
|
2714
|
-
}
|
|
2715
|
-
projectBlockStreamChunk(text, sessionId) {
|
|
2716
|
-
let state = this.blockFileProjectors.get(sessionId);
|
|
2717
|
-
if (!state) {
|
|
2718
|
-
if (!this.blockProjectionArmed.has(sessionId)) {
|
|
2719
|
-
return { text: '', hasContent: false };
|
|
2720
|
-
}
|
|
2721
|
-
state = { projector: new OutboundFileProjector(), reportedMarkers: 0 };
|
|
2722
|
-
this.blockFileProjectors.set(sessionId, state);
|
|
2723
|
-
}
|
|
2724
|
-
const safe = state.projector.append(text);
|
|
2725
|
-
const hasContent = safe.trim().length > 0;
|
|
2726
|
-
const result = state.projector.result(safe);
|
|
2727
|
-
let outgoingText = safe;
|
|
2728
|
-
if (result.markerCount > state.reportedMarkers) {
|
|
2729
|
-
outgoingText = withFileUnavailableNotice(safe);
|
|
2730
|
-
state.reportedMarkers = result.markerCount;
|
|
2731
|
-
process.stderr.write(`[DingTalk:${this.name}] file markers redacted in block stream (session ${sessionId}, markers=${result.markerCount})\n`);
|
|
2732
|
-
}
|
|
2733
|
-
return { text: outgoingText, hasContent };
|
|
2684
|
+
await this.sendReply(chatId, text, atUserId, sourceLabel ?? this.getResponseSourceLabel(sessionId), prepared, failOnHttpError);
|
|
2734
2685
|
}
|
|
2735
2686
|
async sendFallbackReply(chatId, text, sessionId, sourceLabel) {
|
|
2736
2687
|
// Mid-run fallbacks must not consume the prompt's mention target: the
|
|
@@ -2794,6 +2745,16 @@ export class DingtalkChannel extends ChannelBase {
|
|
|
2794
2745
|
}
|
|
2795
2746
|
return this.interactionPresenter.presentInput(context);
|
|
2796
2747
|
}
|
|
2748
|
+
async presentPermissionRequest(context) {
|
|
2749
|
+
const run = this.cardRuns.get(context.runId);
|
|
2750
|
+
if (!run || run.ownerId !== context.owner.id) {
|
|
2751
|
+
return { kind: 'unsupported' };
|
|
2752
|
+
}
|
|
2753
|
+
if (!this.permissionCardController || !this.interactionPresenter) {
|
|
2754
|
+
return { kind: 'unsupported' };
|
|
2755
|
+
}
|
|
2756
|
+
return this.interactionPresenter.presentPermission(context);
|
|
2757
|
+
}
|
|
2797
2758
|
/**
|
|
2798
2759
|
* Extract quoted/referenced message context from a reply.
|
|
2799
2760
|
* DingTalk provides this via text.repliedMsg (newer) or quoteMessage (legacy).
|