@larksuite/openclaw-lark 2026.4.1 → 2026.4.7
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/index.js +25 -6
- package/package.json +1 -1
- package/src/card/builder.d.ts +29 -7
- package/src/card/builder.js +241 -29
- package/src/card/reasoning-utils.d.ts +14 -0
- package/src/card/reasoning-utils.js +64 -0
- package/src/card/reply-dispatcher-types.d.ts +12 -15
- package/src/card/reply-dispatcher-types.js +1 -0
- package/src/card/reply-dispatcher.js +63 -11
- package/src/card/streaming-card-controller.d.ts +15 -0
- package/src/card/streaming-card-controller.js +188 -65
- package/src/card/tool-use-config.d.ts +26 -0
- package/src/card/tool-use-config.js +76 -0
- package/src/card/tool-use-display.d.ts +29 -0
- package/src/card/tool-use-display.js +438 -0
- package/src/card/tool-use-trace-store.d.ts +51 -0
- package/src/card/tool-use-trace-store.js +271 -0
- package/src/channel/event-handlers.d.ts +1 -0
- package/src/channel/event-handlers.js +51 -0
- package/src/channel/monitor.js +2 -0
- package/src/core/comment-target.d.ts +65 -0
- package/src/core/comment-target.js +100 -0
- package/src/core/config-schema.d.ts +9 -0
- package/src/core/config-schema.js +5 -0
- package/src/core/tool-scopes.d.ts +1 -1
- package/src/core/tool-scopes.js +7 -0
- package/src/messaging/inbound/comment-context.d.ts +82 -0
- package/src/messaging/inbound/comment-context.js +353 -0
- package/src/messaging/inbound/comment-handler.d.ts +30 -0
- package/src/messaging/inbound/comment-handler.js +269 -0
- package/src/messaging/inbound/dispatch-commands.js +23 -2
- package/src/messaging/inbound/dispatch-context.js +19 -7
- package/src/messaging/inbound/dispatch.js +87 -8
- package/src/messaging/outbound/deliver.d.ts +29 -0
- package/src/messaging/outbound/deliver.js +94 -0
- package/src/messaging/outbound/outbound.js +19 -0
- package/src/messaging/types.d.ts +63 -0
- package/src/tools/oapi/drive/doc-comments.js +93 -24
- package/src/tools/oapi/index.js +1 -0
- package/src/tools/oapi/task/index.d.ts +1 -0
- package/src/tools/oapi/task/index.js +3 -1
- package/src/tools/oapi/task/section.d.ts +17 -0
- package/src/tools/oapi/task/section.js +285 -0
|
@@ -22,6 +22,7 @@ const lark_logger_1 = require("../core/lark-logger.js");
|
|
|
22
22
|
const deliver_1 = require("../messaging/outbound/deliver.js");
|
|
23
23
|
const send_1 = require("../messaging/outbound/send.js");
|
|
24
24
|
const typing_1 = require("../messaging/outbound/typing.js");
|
|
25
|
+
const builder_1 = require("./builder.js");
|
|
25
26
|
const card_error_1 = require("./card-error.js");
|
|
26
27
|
const reply_mode_1 = require("./reply-mode.js");
|
|
27
28
|
const streaming_card_controller_1 = require("./streaming-card-controller.js");
|
|
@@ -32,7 +33,7 @@ const log = (0, lark_logger_1.larkLogger)('card/reply-dispatcher');
|
|
|
32
33
|
// ---------------------------------------------------------------------------
|
|
33
34
|
function createFeishuReplyDispatcher(params) {
|
|
34
35
|
const core = lark_client_1.LarkClient.runtime;
|
|
35
|
-
const { cfg, agentId,
|
|
36
|
+
const { cfg, agentId, chatId, sessionKey, replyToMessageId, accountId, replyInThread } = params;
|
|
36
37
|
// Resolve account so we can read per-account config (e.g. replyMode)
|
|
37
38
|
const account = (0, accounts_1.getLarkAccount)(cfg, accountId);
|
|
38
39
|
const feishuCfg = account.config;
|
|
@@ -50,6 +51,7 @@ function createFeishuReplyDispatcher(params) {
|
|
|
50
51
|
const useStreamingCards = replyMode === 'streaming';
|
|
51
52
|
// ---- Block streaming for static mode ----
|
|
52
53
|
const enableBlockStreaming = feishuCfg?.blockStreaming === true && !useStreamingCards;
|
|
54
|
+
const { toolUseDisplay } = params;
|
|
53
55
|
const resolvedFooter = (0, footer_config_1.resolveFooterConfig)(feishuCfg?.footer);
|
|
54
56
|
log.info('reply mode resolved', {
|
|
55
57
|
effectiveReplyMode,
|
|
@@ -81,6 +83,7 @@ function createFeishuReplyDispatcher(params) {
|
|
|
81
83
|
chatId,
|
|
82
84
|
replyToMessageId,
|
|
83
85
|
replyInThread,
|
|
86
|
+
toolUseDisplay,
|
|
84
87
|
resolvedFooter,
|
|
85
88
|
})
|
|
86
89
|
: null;
|
|
@@ -171,9 +174,10 @@ function createFeishuReplyDispatcher(params) {
|
|
|
171
174
|
return;
|
|
172
175
|
await typingCallbacks.onReplyStart?.();
|
|
173
176
|
},
|
|
174
|
-
deliver: async (payload) => {
|
|
177
|
+
deliver: async (payload, meta) => {
|
|
175
178
|
log.debug('deliver called', {
|
|
176
179
|
textPreview: payload.text?.slice(0, 100),
|
|
180
|
+
kind: meta?.kind,
|
|
177
181
|
});
|
|
178
182
|
if (shouldSkip('deliver.entry'))
|
|
179
183
|
return;
|
|
@@ -190,7 +194,7 @@ function createFeishuReplyDispatcher(params) {
|
|
|
190
194
|
return;
|
|
191
195
|
}
|
|
192
196
|
// 提取文本和媒体 URL
|
|
193
|
-
const text = payload
|
|
197
|
+
const text = getVisiblePayloadText(payload);
|
|
194
198
|
const payloadMediaUrls = payload.mediaUrls?.length
|
|
195
199
|
? payload.mediaUrls
|
|
196
200
|
: payload.mediaUrl
|
|
@@ -202,15 +206,21 @@ function createFeishuReplyDispatcher(params) {
|
|
|
202
206
|
}
|
|
203
207
|
// ---- Streaming card mode ----
|
|
204
208
|
if (controller) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
return;
|
|
208
|
-
if (controller.cardMessageId) {
|
|
209
|
-
await controller.onDeliver(payload);
|
|
209
|
+
if (meta?.kind === 'tool' && shouldRouteToolPayloadToCard(payload, toolUseDisplay.showToolUse)) {
|
|
210
|
+
await controller.onToolPayload(payload);
|
|
210
211
|
return;
|
|
211
212
|
}
|
|
212
|
-
|
|
213
|
-
|
|
213
|
+
if (text.trim()) {
|
|
214
|
+
await controller.ensureCardCreated();
|
|
215
|
+
if (controller.isTerminated)
|
|
216
|
+
return;
|
|
217
|
+
if (controller.cardMessageId) {
|
|
218
|
+
await controller.onDeliver({ ...payload, text });
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
// Card creation failed — fall through to static delivery
|
|
222
|
+
log.warn('deliver: card creation failed, falling back to static delivery');
|
|
223
|
+
}
|
|
214
224
|
}
|
|
215
225
|
// ---- Static text delivery ----
|
|
216
226
|
if (text.trim()) {
|
|
@@ -374,12 +384,21 @@ function createFeishuReplyDispatcher(params) {
|
|
|
374
384
|
dispatcher,
|
|
375
385
|
replyOptions: {
|
|
376
386
|
...replyOptions,
|
|
377
|
-
|
|
387
|
+
...(controller
|
|
388
|
+
? {
|
|
389
|
+
shouldEmitToolResult: () => false,
|
|
390
|
+
shouldEmitToolOutput: () => false,
|
|
391
|
+
}
|
|
392
|
+
: {}),
|
|
393
|
+
onModelSelected: (ctx) => {
|
|
394
|
+
prefixContext.onModelSelected(ctx);
|
|
395
|
+
},
|
|
378
396
|
disableBlockStreaming: !enableBlockStreaming,
|
|
379
397
|
...(controller
|
|
380
398
|
? {
|
|
381
399
|
onReasoningStream: (payload) => controller.onReasoningStream(payload),
|
|
382
400
|
onPartialReply: (payload) => controller.onPartialReply(payload),
|
|
401
|
+
onToolStart: (payload) => controller.onToolStart(payload),
|
|
383
402
|
}
|
|
384
403
|
: {}),
|
|
385
404
|
},
|
|
@@ -391,3 +410,36 @@ function createFeishuReplyDispatcher(params) {
|
|
|
391
410
|
abortCard,
|
|
392
411
|
};
|
|
393
412
|
}
|
|
413
|
+
function getVisiblePayloadText(payload) {
|
|
414
|
+
if (payload.isReasoning === true)
|
|
415
|
+
return '';
|
|
416
|
+
const rawText = payload.text ?? '';
|
|
417
|
+
if (!rawText)
|
|
418
|
+
return '';
|
|
419
|
+
const split = (0, builder_1.splitReasoningText)(rawText);
|
|
420
|
+
if (split.answerText != null) {
|
|
421
|
+
return split.answerText;
|
|
422
|
+
}
|
|
423
|
+
return (0, builder_1.stripReasoningTags)(rawText);
|
|
424
|
+
}
|
|
425
|
+
function shouldRouteToolPayloadToCard(payload, showToolUse) {
|
|
426
|
+
if (!showToolUse)
|
|
427
|
+
return false;
|
|
428
|
+
if (!getVisiblePayloadText(payload).trim())
|
|
429
|
+
return false;
|
|
430
|
+
if (payload.interactive)
|
|
431
|
+
return false;
|
|
432
|
+
if (payload.btw)
|
|
433
|
+
return false;
|
|
434
|
+
if (payload.audioAsVoice)
|
|
435
|
+
return false;
|
|
436
|
+
if (payload.mediaUrl || (payload.mediaUrls?.length ?? 0) > 0)
|
|
437
|
+
return false;
|
|
438
|
+
const execApproval = payload.channelData && typeof payload.channelData === 'object' && !Array.isArray(payload.channelData)
|
|
439
|
+
? payload.channelData.execApproval
|
|
440
|
+
: undefined;
|
|
441
|
+
if (execApproval && typeof execApproval === 'object' && !Array.isArray(execApproval)) {
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
return true;
|
|
445
|
+
}
|
|
@@ -24,6 +24,7 @@ export declare class StreamingCardController {
|
|
|
24
24
|
private cardKit;
|
|
25
25
|
private text;
|
|
26
26
|
private reasoning;
|
|
27
|
+
private toolUse;
|
|
27
28
|
private readonly flush;
|
|
28
29
|
private readonly guard;
|
|
29
30
|
private readonly imageResolver;
|
|
@@ -57,6 +58,10 @@ export declare class StreamingCardController {
|
|
|
57
58
|
get terminalReason(): TerminalReason | null;
|
|
58
59
|
/** @internal — exposed for test assertions only. */
|
|
59
60
|
get currentPhase(): CardPhase;
|
|
61
|
+
private get shouldDisplayToolUse();
|
|
62
|
+
private computeToolUseDisplay;
|
|
63
|
+
private get visibleToolUseElapsedMs();
|
|
64
|
+
private computeToolUseTitleSuffix;
|
|
60
65
|
/**
|
|
61
66
|
* Unified callback guard — returns true if the pipeline is active
|
|
62
67
|
* and the callback should proceed.
|
|
@@ -70,6 +75,8 @@ export declare class StreamingCardController {
|
|
|
70
75
|
private isStaleCreate;
|
|
71
76
|
private transition;
|
|
72
77
|
private onEnterTerminalPhase;
|
|
78
|
+
private markToolUseActivity;
|
|
79
|
+
private captureToolUseElapsed;
|
|
73
80
|
/**
|
|
74
81
|
* Handle a deliver() call in streaming card mode.
|
|
75
82
|
*
|
|
@@ -78,6 +85,11 @@ export declare class StreamingCardController {
|
|
|
78
85
|
*/
|
|
79
86
|
onDeliver(payload: ReplyPayload): Promise<void>;
|
|
80
87
|
onReasoningStream(payload: ReplyPayload): Promise<void>;
|
|
88
|
+
onToolStart(payload: {
|
|
89
|
+
name?: string;
|
|
90
|
+
phase?: string;
|
|
91
|
+
}): Promise<void>;
|
|
92
|
+
onToolPayload(_payload: ReplyPayload): Promise<void>;
|
|
81
93
|
onPartialReply(payload: ReplyPayload): Promise<void>;
|
|
82
94
|
onError(err: unknown, info: {
|
|
83
95
|
kind: string;
|
|
@@ -89,6 +101,9 @@ export declare class StreamingCardController {
|
|
|
89
101
|
private performFlush;
|
|
90
102
|
private buildDisplayText;
|
|
91
103
|
private throttledCardUpdate;
|
|
104
|
+
private lastToolUseStatusUpdateTime;
|
|
105
|
+
private throttledToolUseStatusUpdate;
|
|
106
|
+
private updateToolUseStatus;
|
|
92
107
|
private finalizeCard;
|
|
93
108
|
/**
|
|
94
109
|
* Close streaming mode then update card content (shared by onError and abortCard).
|
|
@@ -15,8 +15,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
exports.StreamingCardController = void 0;
|
|
16
16
|
exports.prepareTerminalCardContent = prepareTerminalCardContent;
|
|
17
17
|
const promises_1 = require("node:fs/promises");
|
|
18
|
-
const reply_runtime_1 = require("openclaw/plugin-sdk/reply-runtime");
|
|
19
18
|
const agent_runtime_1 = require("openclaw/plugin-sdk/agent-runtime");
|
|
19
|
+
const reply_runtime_1 = require("openclaw/plugin-sdk/reply-runtime");
|
|
20
20
|
const api_error_1 = require("../core/api-error.js");
|
|
21
21
|
const lark_logger_1 = require("../core/lark-logger.js");
|
|
22
22
|
const lark_client_1 = require("../core/lark-client.js");
|
|
@@ -28,47 +28,12 @@ const cardkit_1 = require("./cardkit.js");
|
|
|
28
28
|
const flush_controller_1 = require("./flush-controller.js");
|
|
29
29
|
const image_resolver_1 = require("./image-resolver.js");
|
|
30
30
|
const markdown_style_1 = require("./markdown-style.js");
|
|
31
|
+
const tool_use_display_1 = require("./tool-use-display.js");
|
|
32
|
+
const tool_use_trace_store_1 = require("./tool-use-trace-store.js");
|
|
31
33
|
const reply_dispatcher_types_1 = require("./reply-dispatcher-types.js");
|
|
32
34
|
const unavailable_guard_1 = require("./unavailable-guard.js");
|
|
33
35
|
const log = (0, lark_logger_1.larkLogger)('card/streaming');
|
|
34
36
|
// ---------------------------------------------------------------------------
|
|
35
|
-
// CardKit 2.0 initial streaming payload
|
|
36
|
-
// ---------------------------------------------------------------------------
|
|
37
|
-
const STREAMING_THINKING_CARD = {
|
|
38
|
-
schema: '2.0',
|
|
39
|
-
config: {
|
|
40
|
-
streaming_mode: true,
|
|
41
|
-
// locales 用于支持多语言摘要展示
|
|
42
|
-
locales: ['zh_cn', 'en_us'],
|
|
43
|
-
summary: {
|
|
44
|
-
content: 'Thinking...',
|
|
45
|
-
i18n_content: { zh_cn: '思考中...', en_us: 'Thinking...' },
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
body: {
|
|
49
|
-
elements: [
|
|
50
|
-
{
|
|
51
|
-
tag: 'markdown',
|
|
52
|
-
content: '',
|
|
53
|
-
text_align: 'left',
|
|
54
|
-
text_size: 'normal_v2',
|
|
55
|
-
margin: '0px 0px 0px 0px',
|
|
56
|
-
element_id: builder_1.STREAMING_ELEMENT_ID,
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
tag: 'markdown',
|
|
60
|
-
content: ' ',
|
|
61
|
-
icon: {
|
|
62
|
-
tag: 'custom_icon',
|
|
63
|
-
img_key: 'img_v3_02vb_496bec09-4b43-4773-ad6b-0cdd103cd2bg',
|
|
64
|
-
size: '16px 16px',
|
|
65
|
-
},
|
|
66
|
-
element_id: 'loading_icon',
|
|
67
|
-
},
|
|
68
|
-
],
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
// ---------------------------------------------------------------------------
|
|
72
37
|
// StreamingCardController
|
|
73
38
|
// ---------------------------------------------------------------------------
|
|
74
39
|
class StreamingCardController {
|
|
@@ -86,6 +51,7 @@ class StreamingCardController {
|
|
|
86
51
|
completedText: '',
|
|
87
52
|
streamingPrefix: '',
|
|
88
53
|
lastPartialText: '',
|
|
54
|
+
lastFlushedText: '',
|
|
89
55
|
};
|
|
90
56
|
reasoning = {
|
|
91
57
|
accumulatedReasoningText: '',
|
|
@@ -93,6 +59,11 @@ class StreamingCardController {
|
|
|
93
59
|
reasoningElapsedMs: 0,
|
|
94
60
|
isReasoningPhase: false,
|
|
95
61
|
};
|
|
62
|
+
toolUse = {
|
|
63
|
+
startedAt: null,
|
|
64
|
+
elapsedMs: 0,
|
|
65
|
+
isActive: false,
|
|
66
|
+
};
|
|
96
67
|
// ---- Sub-controllers ----
|
|
97
68
|
flush;
|
|
98
69
|
guard;
|
|
@@ -285,6 +256,31 @@ class StreamingCardController {
|
|
|
285
256
|
get currentPhase() {
|
|
286
257
|
return this.phase;
|
|
287
258
|
}
|
|
259
|
+
get shouldDisplayToolUse() {
|
|
260
|
+
return this.deps.toolUseDisplay.showToolUse;
|
|
261
|
+
}
|
|
262
|
+
computeToolUseDisplay() {
|
|
263
|
+
if (!this.shouldDisplayToolUse)
|
|
264
|
+
return null;
|
|
265
|
+
const traceSteps = (0, tool_use_trace_store_1.getToolUseTraceSteps)(this.deps.sessionKey);
|
|
266
|
+
return (0, tool_use_display_1.normalizeToolUseDisplay)({
|
|
267
|
+
traceSteps,
|
|
268
|
+
showFullPaths: this.deps.toolUseDisplay.showFullPaths,
|
|
269
|
+
showResultDetails: this.deps.toolUseDisplay.showToolResultDetails,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
get visibleToolUseElapsedMs() {
|
|
273
|
+
if (!this.shouldDisplayToolUse || !this.toolUse.startedAt) {
|
|
274
|
+
return undefined;
|
|
275
|
+
}
|
|
276
|
+
return this.toolUse.elapsedMs || Date.now() - this.toolUse.startedAt;
|
|
277
|
+
}
|
|
278
|
+
computeToolUseTitleSuffix(display) {
|
|
279
|
+
if (!this.shouldDisplayToolUse)
|
|
280
|
+
return undefined;
|
|
281
|
+
const stepCount = display?.stepCount ?? 0;
|
|
282
|
+
return stepCount > 0 ? (0, tool_use_display_1.buildToolUseTitleSuffix)({ stepCount }) : undefined;
|
|
283
|
+
}
|
|
288
284
|
// ------------------------------------------------------------------
|
|
289
285
|
// Unified callback guard
|
|
290
286
|
// ------------------------------------------------------------------
|
|
@@ -330,6 +326,22 @@ class StreamingCardController {
|
|
|
330
326
|
this.flush.complete();
|
|
331
327
|
this.disposeShutdownHook?.();
|
|
332
328
|
this.disposeShutdownHook = null;
|
|
329
|
+
if (this.phase === 'terminated' || this.phase === 'creation_failed') {
|
|
330
|
+
(0, tool_use_trace_store_1.clearToolUseTraceRun)(this.deps.sessionKey);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
markToolUseActivity() {
|
|
334
|
+
if (!this.toolUse.startedAt) {
|
|
335
|
+
this.toolUse.startedAt = Date.now();
|
|
336
|
+
}
|
|
337
|
+
this.toolUse.elapsedMs = Date.now() - this.toolUse.startedAt;
|
|
338
|
+
this.toolUse.isActive = true;
|
|
339
|
+
}
|
|
340
|
+
captureToolUseElapsed() {
|
|
341
|
+
if (!this.toolUse.startedAt)
|
|
342
|
+
return;
|
|
343
|
+
this.toolUse.elapsedMs = Date.now() - this.toolUse.startedAt;
|
|
344
|
+
this.toolUse.isActive = false;
|
|
333
345
|
}
|
|
334
346
|
// ------------------------------------------------------------------
|
|
335
347
|
// SDK callback bindings
|
|
@@ -351,6 +363,7 @@ class StreamingCardController {
|
|
|
351
363
|
return;
|
|
352
364
|
if (!this.cardKit.cardMessageId)
|
|
353
365
|
return;
|
|
366
|
+
this.captureToolUseElapsed();
|
|
354
367
|
const split = (0, builder_1.splitReasoningText)(text);
|
|
355
368
|
if (split.reasoningText && !split.answerText) {
|
|
356
369
|
// Pure reasoning payload
|
|
@@ -396,6 +409,42 @@ class StreamingCardController {
|
|
|
396
409
|
this.reasoning.accumulatedReasoningText = split.reasoningText ?? rawText;
|
|
397
410
|
await this.throttledCardUpdate();
|
|
398
411
|
}
|
|
412
|
+
async onToolStart(payload) {
|
|
413
|
+
if (!this.shouldProceed('onToolStart'))
|
|
414
|
+
return;
|
|
415
|
+
if (!this.shouldDisplayToolUse)
|
|
416
|
+
return;
|
|
417
|
+
if (payload.phase && payload.phase !== 'start')
|
|
418
|
+
return;
|
|
419
|
+
this.markToolUseActivity();
|
|
420
|
+
await this.ensureCardCreated();
|
|
421
|
+
if (!this.shouldProceed('onToolStart.postCreate'))
|
|
422
|
+
return;
|
|
423
|
+
if (!this.cardKit.cardMessageId)
|
|
424
|
+
return;
|
|
425
|
+
if (!this.text.accumulatedText && this.cardKit.cardKitCardId) {
|
|
426
|
+
await this.throttledToolUseStatusUpdate();
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
await this.throttledCardUpdate();
|
|
430
|
+
}
|
|
431
|
+
async onToolPayload(_payload) {
|
|
432
|
+
if (!this.shouldProceed('onToolPayload'))
|
|
433
|
+
return;
|
|
434
|
+
if (!this.shouldDisplayToolUse)
|
|
435
|
+
return;
|
|
436
|
+
this.markToolUseActivity();
|
|
437
|
+
await this.ensureCardCreated();
|
|
438
|
+
if (!this.shouldProceed('onToolPayload.postCreate'))
|
|
439
|
+
return;
|
|
440
|
+
if (!this.cardKit.cardMessageId)
|
|
441
|
+
return;
|
|
442
|
+
if (!this.text.accumulatedText && this.cardKit.cardKitCardId) {
|
|
443
|
+
await this.throttledToolUseStatusUpdate();
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
await this.throttledCardUpdate();
|
|
447
|
+
}
|
|
399
448
|
async onPartialReply(payload) {
|
|
400
449
|
if (!this.shouldProceed('onPartialReply'))
|
|
401
450
|
return;
|
|
@@ -416,6 +465,7 @@ class StreamingCardController {
|
|
|
416
465
|
log.debug('onPartialReply', { len: text.length });
|
|
417
466
|
if (!text)
|
|
418
467
|
return;
|
|
468
|
+
this.captureToolUseElapsed();
|
|
419
469
|
if (!this.reasoning.reasoningStartTime) {
|
|
420
470
|
this.reasoning.reasoningStartTime = Date.now();
|
|
421
471
|
}
|
|
@@ -447,14 +497,16 @@ class StreamingCardController {
|
|
|
447
497
|
if (this.guard.terminate('onError', err))
|
|
448
498
|
return;
|
|
449
499
|
log.error(`${info.kind} reply failed`, { error: String(err) });
|
|
500
|
+
this.captureToolUseElapsed();
|
|
450
501
|
this.finalizeCard('onError', 'error');
|
|
451
502
|
await this.flush.waitForFlush();
|
|
452
503
|
if (this.cardCreationPromise)
|
|
453
504
|
await this.cardCreationPromise;
|
|
454
505
|
const errorEffectiveCardId = this.cardKit.cardKitCardId ?? this.cardKit.originalCardKitCardId;
|
|
455
506
|
const footerMetrics = this.needsFooterMetrics() ? await this.getFooterSessionMetrics() : undefined;
|
|
456
|
-
|
|
457
|
-
|
|
507
|
+
const toolUseDisplay = this.computeToolUseDisplay();
|
|
508
|
+
try {
|
|
509
|
+
if (this.cardKit.cardMessageId) {
|
|
458
510
|
const rawErrorText = this.text.accumulatedText
|
|
459
511
|
? `${this.text.accumulatedText}\n\n---\n**Error**: An error occurred while generating the response.`
|
|
460
512
|
: '**Error**: An error occurred while generating the response.';
|
|
@@ -466,6 +518,10 @@ class StreamingCardController {
|
|
|
466
518
|
text: terminalContent.text,
|
|
467
519
|
reasoningText: terminalContent.reasoningText,
|
|
468
520
|
reasoningElapsedMs: this.reasoning.reasoningElapsedMs || undefined,
|
|
521
|
+
toolUseSteps: toolUseDisplay?.steps,
|
|
522
|
+
toolUseTitleSuffix: this.computeToolUseTitleSuffix(toolUseDisplay),
|
|
523
|
+
toolUseElapsedMs: this.visibleToolUseElapsedMs,
|
|
524
|
+
showToolUse: this.deps.toolUseDisplay.showToolUse,
|
|
469
525
|
elapsedMs: this.elapsed(),
|
|
470
526
|
isError: true,
|
|
471
527
|
footer: this.deps.resolvedFooter,
|
|
@@ -483,9 +539,12 @@ class StreamingCardController {
|
|
|
483
539
|
});
|
|
484
540
|
}
|
|
485
541
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
542
|
+
}
|
|
543
|
+
catch {
|
|
544
|
+
// Ignore update failures during error handling
|
|
545
|
+
}
|
|
546
|
+
finally {
|
|
547
|
+
(0, tool_use_trace_store_1.clearToolUseTraceRun)(this.deps.sessionKey);
|
|
489
548
|
}
|
|
490
549
|
}
|
|
491
550
|
async onIdle() {
|
|
@@ -495,6 +554,7 @@ class StreamingCardController {
|
|
|
495
554
|
return;
|
|
496
555
|
if (this.isTerminalPhase)
|
|
497
556
|
return;
|
|
557
|
+
this.captureToolUseElapsed();
|
|
498
558
|
this.finalizeCard('onIdle', 'normal');
|
|
499
559
|
await this.flush.waitForFlush();
|
|
500
560
|
if (this.cardCreationPromise) {
|
|
@@ -503,8 +563,8 @@ class StreamingCardController {
|
|
|
503
563
|
await this.flush.waitForFlush();
|
|
504
564
|
}
|
|
505
565
|
const idleEffectiveCardId = this.cardKit.cardKitCardId ?? this.cardKit.originalCardKitCardId;
|
|
506
|
-
|
|
507
|
-
|
|
566
|
+
try {
|
|
567
|
+
if (this.cardKit.cardMessageId) {
|
|
508
568
|
if (idleEffectiveCardId) {
|
|
509
569
|
const seqBeforeClose = this.cardKit.cardKitSequence;
|
|
510
570
|
this.cardKit.cardKitSequence += 1;
|
|
@@ -527,6 +587,7 @@ class StreamingCardController {
|
|
|
527
587
|
}
|
|
528
588
|
// 等待图片异步解析(最多 15s),避免终态卡片留占位符
|
|
529
589
|
const resolvedDisplayText = await this.imageResolver.resolveImagesAwait(displayText, 15_000);
|
|
590
|
+
const idleToolUseDisplay = this.computeToolUseDisplay();
|
|
530
591
|
const terminalContent = prepareTerminalCardContent({
|
|
531
592
|
text: resolvedDisplayText,
|
|
532
593
|
reasoningText: this.reasoning.accumulatedReasoningText || undefined,
|
|
@@ -536,6 +597,10 @@ class StreamingCardController {
|
|
|
536
597
|
text: terminalContent.text,
|
|
537
598
|
reasoningText: terminalContent.reasoningText,
|
|
538
599
|
reasoningElapsedMs: this.reasoning.reasoningElapsedMs || undefined,
|
|
600
|
+
toolUseSteps: idleToolUseDisplay?.steps,
|
|
601
|
+
toolUseTitleSuffix: this.computeToolUseTitleSuffix(idleToolUseDisplay),
|
|
602
|
+
toolUseElapsedMs: this.visibleToolUseElapsedMs,
|
|
603
|
+
showToolUse: this.deps.toolUseDisplay.showToolUse,
|
|
539
604
|
elapsedMs: this.elapsed(),
|
|
540
605
|
footer: this.deps.resolvedFooter,
|
|
541
606
|
footerMetrics,
|
|
@@ -568,9 +633,12 @@ class StreamingCardController {
|
|
|
568
633
|
isCardKit: !!idleEffectiveCardId,
|
|
569
634
|
});
|
|
570
635
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
}
|
|
636
|
+
}
|
|
637
|
+
catch (err) {
|
|
638
|
+
log.warn('final card update failed', { error: String(err) });
|
|
639
|
+
}
|
|
640
|
+
finally {
|
|
641
|
+
(0, tool_use_trace_store_1.clearToolUseTraceRun)(this.deps.sessionKey);
|
|
574
642
|
}
|
|
575
643
|
}
|
|
576
644
|
// ------------------------------------------------------------------
|
|
@@ -585,6 +653,7 @@ class StreamingCardController {
|
|
|
585
653
|
}
|
|
586
654
|
async abortCard() {
|
|
587
655
|
try {
|
|
656
|
+
this.captureToolUseElapsed();
|
|
588
657
|
if (!this.transition('aborted', 'abortCard', 'abort'))
|
|
589
658
|
return;
|
|
590
659
|
// transition() already executed onEnterTerminalPhase (cancel + complete + dispose hook)
|
|
@@ -594,6 +663,7 @@ class StreamingCardController {
|
|
|
594
663
|
await this.cardCreationPromise;
|
|
595
664
|
const effectiveCardId = this.cardKit.cardKitCardId ?? this.cardKit.originalCardKitCardId;
|
|
596
665
|
const elapsedMs = Date.now() - this.dispatchStartTime;
|
|
666
|
+
const abortToolUseDisplay = this.computeToolUseDisplay();
|
|
597
667
|
const terminalContent = prepareTerminalCardContent({
|
|
598
668
|
text: this.text.accumulatedText || 'Aborted.',
|
|
599
669
|
reasoningText: this.reasoning.accumulatedReasoningText || undefined,
|
|
@@ -604,6 +674,10 @@ class StreamingCardController {
|
|
|
604
674
|
text: terminalContent.text,
|
|
605
675
|
reasoningText: terminalContent.reasoningText,
|
|
606
676
|
reasoningElapsedMs: this.reasoning.reasoningElapsedMs || undefined,
|
|
677
|
+
toolUseSteps: abortToolUseDisplay?.steps,
|
|
678
|
+
toolUseTitleSuffix: this.computeToolUseTitleSuffix(abortToolUseDisplay),
|
|
679
|
+
toolUseElapsedMs: this.visibleToolUseElapsedMs,
|
|
680
|
+
showToolUse: this.deps.toolUseDisplay.showToolUse,
|
|
607
681
|
elapsedMs,
|
|
608
682
|
isAborted: true,
|
|
609
683
|
footer: this.deps.resolvedFooter,
|
|
@@ -618,6 +692,10 @@ class StreamingCardController {
|
|
|
618
692
|
text: terminalContent.text,
|
|
619
693
|
reasoningText: terminalContent.reasoningText,
|
|
620
694
|
reasoningElapsedMs: this.reasoning.reasoningElapsedMs || undefined,
|
|
695
|
+
toolUseSteps: abortToolUseDisplay?.steps,
|
|
696
|
+
toolUseTitleSuffix: this.computeToolUseTitleSuffix(abortToolUseDisplay),
|
|
697
|
+
toolUseElapsedMs: this.visibleToolUseElapsedMs,
|
|
698
|
+
showToolUse: this.deps.toolUseDisplay.showToolUse,
|
|
621
699
|
elapsedMs,
|
|
622
700
|
isAborted: true,
|
|
623
701
|
footer: this.deps.resolvedFooter,
|
|
@@ -637,6 +715,9 @@ class StreamingCardController {
|
|
|
637
715
|
catch (err) {
|
|
638
716
|
log.warn('abortCard failed', { error: String(err) });
|
|
639
717
|
}
|
|
718
|
+
finally {
|
|
719
|
+
(0, tool_use_trace_store_1.clearToolUseTraceRun)(this.deps.sessionKey);
|
|
720
|
+
}
|
|
640
721
|
}
|
|
641
722
|
// ------------------------------------------------------------------
|
|
642
723
|
// Internal: card creation
|
|
@@ -661,7 +742,7 @@ class StreamingCardController {
|
|
|
661
742
|
// Step 1: Create card entity
|
|
662
743
|
const cId = await (0, cardkit_1.createCardEntity)({
|
|
663
744
|
cfg: this.deps.cfg,
|
|
664
|
-
card:
|
|
745
|
+
card: (0, builder_1.buildStreamingThinkingCard)(this.deps.toolUseDisplay.showToolUse),
|
|
665
746
|
accountId: this.deps.accountId,
|
|
666
747
|
});
|
|
667
748
|
if (this.isStaleCreate(epoch)) {
|
|
@@ -722,7 +803,9 @@ class StreamingCardController {
|
|
|
722
803
|
log.warn('CardKit flow failed, falling back to IM', { apiDetail });
|
|
723
804
|
this.cardKit.cardKitCardId = null;
|
|
724
805
|
this.cardKit.originalCardKitCardId = null;
|
|
725
|
-
const fallbackCard = (0, builder_1.buildCardContent)('
|
|
806
|
+
const fallbackCard = (0, builder_1.buildCardContent)('streaming', {
|
|
807
|
+
showToolUse: this.deps.toolUseDisplay.showToolUse,
|
|
808
|
+
});
|
|
726
809
|
const result = await (0, send_1.sendCardFeishu)({
|
|
727
810
|
cfg: this.deps.cfg,
|
|
728
811
|
to: this.deps.chatId,
|
|
@@ -781,27 +864,33 @@ class StreamingCardController {
|
|
|
781
864
|
// 流式中间帧使用同步 resolveImages(不等待异步上传)
|
|
782
865
|
const resolvedText = this.imageResolver.resolveImages(displayText);
|
|
783
866
|
if (this.cardKit.cardKitCardId) {
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
867
|
+
if (resolvedText !== this.text.lastFlushedText) {
|
|
868
|
+
const prevSeq = this.cardKit.cardKitSequence;
|
|
869
|
+
this.cardKit.cardKitSequence += 1;
|
|
870
|
+
log.debug('flushCardUpdate: answer seq bump', {
|
|
871
|
+
seqBefore: prevSeq,
|
|
872
|
+
seqAfter: this.cardKit.cardKitSequence,
|
|
873
|
+
});
|
|
874
|
+
await (0, cardkit_1.streamCardContent)({
|
|
875
|
+
cfg: this.deps.cfg,
|
|
876
|
+
cardId: this.cardKit.cardKitCardId,
|
|
877
|
+
elementId: builder_1.STREAMING_ELEMENT_ID,
|
|
878
|
+
content: (0, markdown_style_1.optimizeMarkdownStyle)(resolvedText),
|
|
879
|
+
sequence: this.cardKit.cardKitSequence,
|
|
880
|
+
accountId: this.deps.accountId,
|
|
881
|
+
});
|
|
882
|
+
this.text.lastFlushedText = resolvedText;
|
|
883
|
+
}
|
|
799
884
|
}
|
|
800
885
|
else {
|
|
801
886
|
log.debug('flushCardUpdate: IM patch fallback');
|
|
887
|
+
const flushDisplay = this.computeToolUseDisplay();
|
|
802
888
|
const card = (0, builder_1.buildCardContent)('streaming', {
|
|
803
889
|
text: this.reasoning.isReasoningPhase ? '' : resolvedText,
|
|
804
890
|
reasoningText: this.reasoning.isReasoningPhase ? this.reasoning.accumulatedReasoningText : undefined,
|
|
891
|
+
toolUseSteps: flushDisplay?.steps,
|
|
892
|
+
toolUseTitleSuffix: this.computeToolUseTitleSuffix(flushDisplay),
|
|
893
|
+
showToolUse: this.deps.toolUseDisplay.showToolUse,
|
|
805
894
|
});
|
|
806
895
|
await (0, send_1.updateCardFeishu)({
|
|
807
896
|
cfg: this.deps.cfg,
|
|
@@ -856,6 +945,40 @@ class StreamingCardController {
|
|
|
856
945
|
const throttleMs = this.cardKit.cardKitCardId ? reply_dispatcher_types_1.THROTTLE_CONSTANTS.CARDKIT_MS : reply_dispatcher_types_1.THROTTLE_CONSTANTS.PATCH_MS;
|
|
857
946
|
await this.flush.throttledUpdate(throttleMs);
|
|
858
947
|
}
|
|
948
|
+
// ---- Tool-use status streaming (pre-answer phase) ----
|
|
949
|
+
lastToolUseStatusUpdateTime = 0;
|
|
950
|
+
async throttledToolUseStatusUpdate() {
|
|
951
|
+
if (!this.cardKit.cardKitCardId)
|
|
952
|
+
return;
|
|
953
|
+
const now = Date.now();
|
|
954
|
+
if (now - this.lastToolUseStatusUpdateTime < reply_dispatcher_types_1.THROTTLE_CONSTANTS.REASONING_STATUS_MS)
|
|
955
|
+
return;
|
|
956
|
+
this.lastToolUseStatusUpdateTime = now;
|
|
957
|
+
await this.updateToolUseStatus();
|
|
958
|
+
}
|
|
959
|
+
async updateToolUseStatus() {
|
|
960
|
+
if (!this.cardKit.cardKitCardId || this.isTerminalPhase)
|
|
961
|
+
return;
|
|
962
|
+
try {
|
|
963
|
+
const display = this.computeToolUseDisplay();
|
|
964
|
+
const card = (0, builder_1.buildStreamingPreAnswerCard)({
|
|
965
|
+
steps: display?.steps,
|
|
966
|
+
elapsedMs: this.visibleToolUseElapsedMs,
|
|
967
|
+
showToolUse: this.shouldDisplayToolUse,
|
|
968
|
+
});
|
|
969
|
+
this.cardKit.cardKitSequence += 1;
|
|
970
|
+
await (0, cardkit_1.updateCardKitCard)({
|
|
971
|
+
cfg: this.deps.cfg,
|
|
972
|
+
cardId: this.cardKit.cardKitCardId,
|
|
973
|
+
card,
|
|
974
|
+
sequence: this.cardKit.cardKitSequence,
|
|
975
|
+
accountId: this.deps.accountId,
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
catch (err) {
|
|
979
|
+
log.debug('updateToolUseStatus failed', { error: String(err) });
|
|
980
|
+
}
|
|
981
|
+
}
|
|
859
982
|
// ------------------------------------------------------------------
|
|
860
983
|
// Internal: lifecycle helpers
|
|
861
984
|
// ------------------------------------------------------------------
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Resolution logic for Feishu tool-use display.
|
|
6
|
+
*
|
|
7
|
+
* The source of truth is OpenClaw's effective verbose state:
|
|
8
|
+
* inline `/verbose` override > session store override > config default.
|
|
9
|
+
* Feishu channel config only retains UI-level detail (`showFullPaths`).
|
|
10
|
+
*/
|
|
11
|
+
import type { ClawdbotConfig } from 'openclaw/plugin-sdk';
|
|
12
|
+
import type { FeishuConfig } from '../core/types';
|
|
13
|
+
export type ToolUseMode = 'off' | 'on' | 'full';
|
|
14
|
+
export interface ToolUseDisplayConfig {
|
|
15
|
+
mode: ToolUseMode;
|
|
16
|
+
showToolUse: boolean;
|
|
17
|
+
showToolResultDetails: boolean;
|
|
18
|
+
showFullPaths: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare function resolveToolUseDisplayConfig(params: {
|
|
21
|
+
cfg: ClawdbotConfig;
|
|
22
|
+
feishuCfg: FeishuConfig | undefined;
|
|
23
|
+
agentId: string;
|
|
24
|
+
sessionKey: string;
|
|
25
|
+
body?: string;
|
|
26
|
+
}): ToolUseDisplayConfig;
|