@larksuite/openclaw-lark 2026.4.1 → 2026.4.7-beta.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/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 +53 -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
package/index.js
CHANGED
|
@@ -22,6 +22,8 @@ const diagnose_1 = require("./src/commands/diagnose.js");
|
|
|
22
22
|
const index_3 = require("./src/commands/index.js");
|
|
23
23
|
const lark_logger_1 = require("./src/core/lark-logger.js");
|
|
24
24
|
const security_check_1 = require("./src/core/security-check.js");
|
|
25
|
+
const tool_use_trace_store_1 = require("./src/card/tool-use-trace-store.js");
|
|
26
|
+
const reasoning_utils_1 = require("./src/card/reasoning-utils.js");
|
|
25
27
|
const log = (0, lark_logger_1.larkLogger)('plugin');
|
|
26
28
|
// ---------------------------------------------------------------------------
|
|
27
29
|
// Re-exports for external consumers
|
|
@@ -105,20 +107,37 @@ const plugin = {
|
|
|
105
107
|
(0, oauth_batch_auth_1.registerFeishuOAuthBatchAuthTool)(api);
|
|
106
108
|
// Register AskUserQuestion tool (interactive card-based user prompting)
|
|
107
109
|
(0, ask_user_question_1.registerAskUserQuestionTool)(api);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
+
api.on('before_tool_call', (event, ctx) => {
|
|
111
|
+
(0, tool_use_trace_store_1.recordToolUseStart)({
|
|
112
|
+
sessionKey: ctx.sessionKey,
|
|
113
|
+
toolName: event.toolName,
|
|
114
|
+
toolParams: event.params,
|
|
115
|
+
toolCallId: event.toolCallId ?? ctx.toolCallId,
|
|
116
|
+
runId: event.runId ?? ctx.runId,
|
|
117
|
+
});
|
|
110
118
|
if (!event.toolName.startsWith('feishu_'))
|
|
111
119
|
return;
|
|
112
|
-
|
|
120
|
+
const paramsPreview = (0, reasoning_utils_1.sanitizeParamsForLog)(event.params);
|
|
121
|
+
log.info(`tool call: ${event.toolName} session=${ctx.sessionKey ?? '-'} params=${paramsPreview}`);
|
|
113
122
|
});
|
|
114
|
-
api.on('after_tool_call', (event) => {
|
|
123
|
+
api.on('after_tool_call', (event, ctx) => {
|
|
124
|
+
(0, tool_use_trace_store_1.recordToolUseEnd)({
|
|
125
|
+
sessionKey: ctx.sessionKey,
|
|
126
|
+
toolName: event.toolName,
|
|
127
|
+
toolParams: event.params,
|
|
128
|
+
toolCallId: event.toolCallId ?? ctx.toolCallId,
|
|
129
|
+
runId: event.runId ?? ctx.runId,
|
|
130
|
+
result: event.result,
|
|
131
|
+
error: event.error,
|
|
132
|
+
durationMs: event.durationMs,
|
|
133
|
+
});
|
|
115
134
|
if (!event.toolName.startsWith('feishu_'))
|
|
116
135
|
return;
|
|
117
136
|
if (event.error) {
|
|
118
|
-
log.error(`tool fail: ${event.toolName} ${event.error} (${event.durationMs ?? 0}ms)`);
|
|
137
|
+
log.error(`tool fail: ${event.toolName} session=${ctx.sessionKey ?? '-'} ${event.error} (${event.durationMs ?? 0}ms)`);
|
|
119
138
|
}
|
|
120
139
|
else {
|
|
121
|
-
log.info(`tool done: ${event.toolName} ok (${event.durationMs ?? 0}ms)`);
|
|
140
|
+
log.info(`tool done: ${event.toolName} session=${ctx.sessionKey ?? '-'} ok (${event.durationMs ?? 0}ms)`);
|
|
122
141
|
}
|
|
123
142
|
});
|
|
124
143
|
// ---- Diagnostic commands ----
|
package/package.json
CHANGED
package/src/card/builder.d.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* different agent response states (thinking, streaming, complete, confirm).
|
|
9
9
|
*/
|
|
10
10
|
import type { FooterSessionMetrics } from './reply-dispatcher-types';
|
|
11
|
+
import { type ToolUseDisplayStep } from './tool-use-display';
|
|
11
12
|
/**
|
|
12
13
|
* Element ID used for the streaming text area in cards. The CardKit
|
|
13
14
|
* `cardElement.content()` API targets this element for typewriter-effect
|
|
@@ -15,12 +16,6 @@ import type { FooterSessionMetrics } from './reply-dispatcher-types';
|
|
|
15
16
|
*/
|
|
16
17
|
export declare const STREAMING_ELEMENT_ID = "streaming_content";
|
|
17
18
|
export declare const REASONING_ELEMENT_ID = "reasoning_content";
|
|
18
|
-
export interface ToolCallInfo {
|
|
19
|
-
name: string;
|
|
20
|
-
status: 'running' | 'complete' | 'error';
|
|
21
|
-
args?: Record<string, unknown>;
|
|
22
|
-
result?: string;
|
|
23
|
-
}
|
|
24
19
|
export interface CardElement {
|
|
25
20
|
tag: string;
|
|
26
21
|
[key: string]: unknown;
|
|
@@ -76,6 +71,13 @@ export declare function formatReasoningDuration(ms: number): {
|
|
|
76
71
|
zh: string;
|
|
77
72
|
en: string;
|
|
78
73
|
};
|
|
74
|
+
/**
|
|
75
|
+
* Format tool-use duration into a human-readable i18n pair.
|
|
76
|
+
*/
|
|
77
|
+
export declare function formatToolUseDuration(ms: number): {
|
|
78
|
+
zh: string;
|
|
79
|
+
en: string;
|
|
80
|
+
};
|
|
79
81
|
/**
|
|
80
82
|
* Format milliseconds into a human-readable duration string.
|
|
81
83
|
*/
|
|
@@ -108,7 +110,13 @@ export declare function buildCardContent(state: CardState, data?: {
|
|
|
108
110
|
text?: string;
|
|
109
111
|
reasoningText?: string;
|
|
110
112
|
reasoningElapsedMs?: number;
|
|
111
|
-
|
|
113
|
+
toolUseSteps?: ToolUseDisplayStep[];
|
|
114
|
+
toolUseTitleSuffix?: {
|
|
115
|
+
zh: string;
|
|
116
|
+
en: string;
|
|
117
|
+
};
|
|
118
|
+
toolUseElapsedMs?: number;
|
|
119
|
+
showToolUse?: boolean;
|
|
112
120
|
confirmData?: ConfirmData;
|
|
113
121
|
elapsedMs?: number;
|
|
114
122
|
isError?: boolean;
|
|
@@ -127,4 +135,18 @@ export declare function buildCardContent(state: CardState, data?: {
|
|
|
127
135
|
* Convert an old-format FeishuCard to CardKit JSON 2.0 format.
|
|
128
136
|
* JSON 2.0 uses `body.elements` instead of top-level `elements`.
|
|
129
137
|
*/
|
|
138
|
+
/**
|
|
139
|
+
* Build the initial CardKit 2.0 streaming card with a loading icon.
|
|
140
|
+
* Optionally includes a tool-use pending panel above the streaming area.
|
|
141
|
+
*/
|
|
142
|
+
export declare function buildStreamingThinkingCard(showToolUse?: boolean): Record<string, unknown>;
|
|
143
|
+
/**
|
|
144
|
+
* Build a CardKit 2.0 card for the pre-answer streaming phase.
|
|
145
|
+
* Used both for the initial card and for live updates during tool calls.
|
|
146
|
+
*/
|
|
147
|
+
export declare function buildStreamingPreAnswerCard(params: {
|
|
148
|
+
steps?: ToolUseDisplayStep[];
|
|
149
|
+
elapsedMs?: number;
|
|
150
|
+
showToolUse?: boolean;
|
|
151
|
+
}): Record<string, unknown>;
|
|
130
152
|
export declare function toCardKit2(card: FeishuCard): Record<string, unknown>;
|
package/src/card/builder.js
CHANGED
|
@@ -13,12 +13,16 @@ exports.REASONING_ELEMENT_ID = exports.STREAMING_ELEMENT_ID = void 0;
|
|
|
13
13
|
exports.splitReasoningText = splitReasoningText;
|
|
14
14
|
exports.stripReasoningTags = stripReasoningTags;
|
|
15
15
|
exports.formatReasoningDuration = formatReasoningDuration;
|
|
16
|
+
exports.formatToolUseDuration = formatToolUseDuration;
|
|
16
17
|
exports.formatElapsed = formatElapsed;
|
|
17
18
|
exports.compactNumber = compactNumber;
|
|
18
19
|
exports.formatFooterRuntimeSegments = formatFooterRuntimeSegments;
|
|
19
20
|
exports.buildCardContent = buildCardContent;
|
|
21
|
+
exports.buildStreamingThinkingCard = buildStreamingThinkingCard;
|
|
22
|
+
exports.buildStreamingPreAnswerCard = buildStreamingPreAnswerCard;
|
|
20
23
|
exports.toCardKit2 = toCardKit2;
|
|
21
24
|
const markdown_style_1 = require("./markdown-style.js");
|
|
25
|
+
const tool_use_display_1 = require("./tool-use-display.js");
|
|
22
26
|
// ---------------------------------------------------------------------------
|
|
23
27
|
// Constants
|
|
24
28
|
// ---------------------------------------------------------------------------
|
|
@@ -124,6 +128,13 @@ function formatReasoningDuration(ms) {
|
|
|
124
128
|
const d = formatElapsed(ms);
|
|
125
129
|
return { zh: `思考了 ${d}`, en: `Thought for ${d}` };
|
|
126
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Format tool-use duration into a human-readable i18n pair.
|
|
133
|
+
*/
|
|
134
|
+
function formatToolUseDuration(ms) {
|
|
135
|
+
const d = formatElapsed(ms);
|
|
136
|
+
return { zh: `执行耗时 ${d}`, en: `Tool use for ${d}` };
|
|
137
|
+
}
|
|
127
138
|
/**
|
|
128
139
|
* Format milliseconds into a human-readable duration string.
|
|
129
140
|
*/
|
|
@@ -243,15 +254,23 @@ function buildCardContent(state, data = {}) {
|
|
|
243
254
|
case 'thinking':
|
|
244
255
|
return buildThinkingCard();
|
|
245
256
|
case 'streaming':
|
|
246
|
-
return buildStreamingCard(data.text ?? '',
|
|
257
|
+
return buildStreamingCard(data.text ?? '', {
|
|
258
|
+
reasoningText: data.reasoningText,
|
|
259
|
+
showToolUse: data.showToolUse,
|
|
260
|
+
toolUseSteps: data.toolUseSteps,
|
|
261
|
+
toolUseTitleSuffix: data.toolUseTitleSuffix,
|
|
262
|
+
});
|
|
247
263
|
case 'complete':
|
|
248
264
|
return buildCompleteCard({
|
|
249
265
|
text: data.text ?? '',
|
|
250
|
-
toolCalls: data.toolCalls ?? [],
|
|
251
266
|
elapsedMs: data.elapsedMs,
|
|
252
267
|
isError: data.isError,
|
|
253
268
|
reasoningText: data.reasoningText,
|
|
254
269
|
reasoningElapsedMs: data.reasoningElapsedMs,
|
|
270
|
+
toolUseSteps: data.toolUseSteps,
|
|
271
|
+
toolUseTitleSuffix: data.toolUseTitleSuffix,
|
|
272
|
+
toolUseElapsedMs: data.toolUseElapsedMs,
|
|
273
|
+
showToolUse: data.showToolUse,
|
|
255
274
|
isAborted: data.isAborted,
|
|
256
275
|
footer: data.footer,
|
|
257
276
|
footerMetrics: data.footerMetrics,
|
|
@@ -277,8 +296,18 @@ function buildThinkingCard() {
|
|
|
277
296
|
],
|
|
278
297
|
};
|
|
279
298
|
}
|
|
280
|
-
function buildStreamingCard(partialText,
|
|
299
|
+
function buildStreamingCard(partialText, params = {}) {
|
|
300
|
+
const { showToolUse = true, toolUseSteps, toolUseTitleSuffix, reasoningText } = params;
|
|
281
301
|
const elements = [];
|
|
302
|
+
const hasToolUse = Boolean(toolUseSteps?.length);
|
|
303
|
+
if (showToolUse) {
|
|
304
|
+
elements.push(hasToolUse
|
|
305
|
+
? buildToolUsePanel({
|
|
306
|
+
toolUseSteps,
|
|
307
|
+
titleSuffix: toolUseTitleSuffix,
|
|
308
|
+
})
|
|
309
|
+
: buildStreamingToolUsePendingPanel());
|
|
310
|
+
}
|
|
282
311
|
if (!partialText && reasoningText) {
|
|
283
312
|
// Reasoning phase: show reasoning content in notation style
|
|
284
313
|
elements.push({
|
|
@@ -298,26 +327,21 @@ function buildStreamingCard(partialText, toolCalls, reasoningText) {
|
|
|
298
327
|
content: (0, markdown_style_1.optimizeMarkdownStyle)(partialText),
|
|
299
328
|
});
|
|
300
329
|
}
|
|
301
|
-
// Tool calls in progress
|
|
302
|
-
if (toolCalls.length > 0) {
|
|
303
|
-
const toolLines = toolCalls.map((tc) => {
|
|
304
|
-
const statusIcon = tc.status === 'running' ? '\ud83d\udd04' : tc.status === 'complete' ? '\u2705' : '\u274c';
|
|
305
|
-
return `${statusIcon} ${tc.name} - ${tc.status}`;
|
|
306
|
-
});
|
|
307
|
-
elements.push({
|
|
308
|
-
tag: 'markdown',
|
|
309
|
-
content: toolLines.join('\n'),
|
|
310
|
-
text_size: 'notation',
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
330
|
return {
|
|
314
331
|
config: { wide_screen_mode: true, update_multi: true, locales: ['zh_cn', 'en_us'] },
|
|
315
332
|
elements,
|
|
316
333
|
};
|
|
317
334
|
}
|
|
318
335
|
function buildCompleteCard(params) {
|
|
319
|
-
const { text,
|
|
336
|
+
const { text, elapsedMs, isError, reasoningText, reasoningElapsedMs, toolUseSteps, toolUseTitleSuffix, toolUseElapsedMs, showToolUse = true, isAborted, footer, footerMetrics, } = params;
|
|
320
337
|
const elements = [];
|
|
338
|
+
if (showToolUse) {
|
|
339
|
+
elements.push(buildToolUsePanel({
|
|
340
|
+
toolUseSteps,
|
|
341
|
+
toolUseElapsedMs,
|
|
342
|
+
titleSuffix: toolUseTitleSuffix,
|
|
343
|
+
}));
|
|
344
|
+
}
|
|
321
345
|
// Collapsible reasoning panel (before main content)
|
|
322
346
|
if (reasoningText) {
|
|
323
347
|
const dur = reasoningElapsedMs ? formatReasoningDuration(reasoningElapsedMs) : null;
|
|
@@ -361,18 +385,6 @@ function buildCompleteCard(params) {
|
|
|
361
385
|
tag: 'markdown',
|
|
362
386
|
content: (0, markdown_style_1.optimizeMarkdownStyle)(text),
|
|
363
387
|
});
|
|
364
|
-
// Tool calls summary
|
|
365
|
-
if (toolCalls.length > 0) {
|
|
366
|
-
const toolSummaryLines = toolCalls.map((tc) => {
|
|
367
|
-
const statusIcon = tc.status === 'complete' ? '\u2705' : '\u274c';
|
|
368
|
-
return `${statusIcon} **${tc.name}** - ${tc.status}`;
|
|
369
|
-
});
|
|
370
|
-
elements.push({
|
|
371
|
-
tag: 'markdown',
|
|
372
|
-
content: toolSummaryLines.join('\n'),
|
|
373
|
-
text_size: 'notation',
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
388
|
// Footer meta-info: split into two lines for readability.
|
|
377
389
|
// Line 1 (primary): status · elapsed · model
|
|
378
390
|
// Line 2 (detail): tokens · cache · context
|
|
@@ -396,7 +408,7 @@ function buildCompleteCard(params) {
|
|
|
396
408
|
if (footerZhLines.length > 0) {
|
|
397
409
|
elements.push(...buildFooter(footerZhLines.join('\n'), footerEnLines.join('\n'), isError));
|
|
398
410
|
}
|
|
399
|
-
// Use the answer text
|
|
411
|
+
// Use the answer text as the feed preview summary.
|
|
400
412
|
// Strip markdown syntax so the preview reads as plain text.
|
|
401
413
|
const summaryText = text.replace(/[*_`#>[\]()~]/g, '').trim();
|
|
402
414
|
const summary = summaryText ? { content: summaryText.slice(0, 120) } : undefined;
|
|
@@ -486,6 +498,102 @@ function buildConfirmCard(confirmData) {
|
|
|
486
498
|
* Convert an old-format FeishuCard to CardKit JSON 2.0 format.
|
|
487
499
|
* JSON 2.0 uses `body.elements` instead of top-level `elements`.
|
|
488
500
|
*/
|
|
501
|
+
/**
|
|
502
|
+
* Build the initial CardKit 2.0 streaming card with a loading icon.
|
|
503
|
+
* Optionally includes a tool-use pending panel above the streaming area.
|
|
504
|
+
*/
|
|
505
|
+
function buildStreamingThinkingCard(showToolUse = true) {
|
|
506
|
+
return buildStreamingPreAnswerCard({ showToolUse });
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Build a CardKit 2.0 card for the pre-answer streaming phase.
|
|
510
|
+
* Used both for the initial card and for live updates during tool calls.
|
|
511
|
+
*/
|
|
512
|
+
function buildStreamingPreAnswerCard(params) {
|
|
513
|
+
const { steps, elapsedMs, showToolUse = true } = params;
|
|
514
|
+
const hasSteps = Boolean(steps?.length);
|
|
515
|
+
const elements = [];
|
|
516
|
+
if (showToolUse) {
|
|
517
|
+
elements.push(hasSteps ? buildStreamingToolUseActivePanel({ steps: steps, elapsedMs }) : buildStreamingToolUsePendingPanel());
|
|
518
|
+
}
|
|
519
|
+
elements.push({
|
|
520
|
+
tag: 'markdown',
|
|
521
|
+
content: '',
|
|
522
|
+
text_align: 'left',
|
|
523
|
+
text_size: 'normal_v2',
|
|
524
|
+
margin: '0px 0px 0px 0px',
|
|
525
|
+
element_id: exports.STREAMING_ELEMENT_ID,
|
|
526
|
+
});
|
|
527
|
+
elements.push({
|
|
528
|
+
tag: 'markdown',
|
|
529
|
+
content: ' ',
|
|
530
|
+
icon: {
|
|
531
|
+
tag: 'custom_icon',
|
|
532
|
+
img_key: 'img_v3_02vb_496bec09-4b43-4773-ad6b-0cdd103cd2bg',
|
|
533
|
+
size: '16px 16px',
|
|
534
|
+
},
|
|
535
|
+
element_id: 'loading_icon',
|
|
536
|
+
});
|
|
537
|
+
return {
|
|
538
|
+
schema: '2.0',
|
|
539
|
+
config: {
|
|
540
|
+
streaming_mode: true,
|
|
541
|
+
locales: ['zh_cn', 'en_us'],
|
|
542
|
+
summary: {
|
|
543
|
+
content: 'Processing...',
|
|
544
|
+
i18n_content: { zh_cn: '处理中...', en_us: 'Processing...' },
|
|
545
|
+
},
|
|
546
|
+
},
|
|
547
|
+
body: { elements },
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Build the collapsible panel for the active pre-answer phase.
|
|
552
|
+
* Used by buildStreamingPreAnswerCard when at least one step exists.
|
|
553
|
+
*/
|
|
554
|
+
function buildStreamingToolUseActivePanel(params) {
|
|
555
|
+
const { steps, elapsedMs } = params;
|
|
556
|
+
const enParts = ['Tool use'];
|
|
557
|
+
const zhParts = ['工具执行'];
|
|
558
|
+
if (steps.length > 0) {
|
|
559
|
+
enParts.push(`${steps.length} step${steps.length === 1 ? '' : 's'}`);
|
|
560
|
+
zhParts.push(`${steps.length} 步`);
|
|
561
|
+
}
|
|
562
|
+
if (elapsedMs != null && elapsedMs > 0) {
|
|
563
|
+
const d = formatElapsed(elapsedMs);
|
|
564
|
+
enParts.push(`(${d})`);
|
|
565
|
+
zhParts.push(`(${d})`);
|
|
566
|
+
}
|
|
567
|
+
return {
|
|
568
|
+
tag: 'collapsible_panel',
|
|
569
|
+
expanded: true,
|
|
570
|
+
header: {
|
|
571
|
+
title: {
|
|
572
|
+
tag: 'plain_text',
|
|
573
|
+
content: `🛠️ ${enParts.join(' · ')}`,
|
|
574
|
+
i18n_content: {
|
|
575
|
+
zh_cn: `🛠️ ${zhParts.join(' · ')}`,
|
|
576
|
+
en_us: `🛠️ ${enParts.join(' · ')}`,
|
|
577
|
+
},
|
|
578
|
+
text_color: 'grey',
|
|
579
|
+
text_size: 'notation',
|
|
580
|
+
},
|
|
581
|
+
vertical_align: 'center',
|
|
582
|
+
icon: {
|
|
583
|
+
tag: 'standard_icon',
|
|
584
|
+
token: 'down-small-ccm_outlined',
|
|
585
|
+
color: 'grey',
|
|
586
|
+
size: '16px 16px',
|
|
587
|
+
},
|
|
588
|
+
icon_position: 'right',
|
|
589
|
+
icon_expanded_angle: -180,
|
|
590
|
+
},
|
|
591
|
+
border: { color: 'grey', corner_radius: '5px' },
|
|
592
|
+
vertical_spacing: '8px',
|
|
593
|
+
padding: '8px 8px 8px 8px',
|
|
594
|
+
elements: steps.map(buildToolUseStepElement),
|
|
595
|
+
};
|
|
596
|
+
}
|
|
489
597
|
function toCardKit2(card) {
|
|
490
598
|
const result = {
|
|
491
599
|
schema: '2.0',
|
|
@@ -496,3 +604,107 @@ function toCardKit2(card) {
|
|
|
496
604
|
result.header = card.header;
|
|
497
605
|
return result;
|
|
498
606
|
}
|
|
607
|
+
function buildStreamingToolUsePendingPanel() {
|
|
608
|
+
return {
|
|
609
|
+
tag: 'collapsible_panel',
|
|
610
|
+
expanded: false,
|
|
611
|
+
header: {
|
|
612
|
+
title: {
|
|
613
|
+
tag: 'plain_text',
|
|
614
|
+
content: '🛠️ Tool use pending',
|
|
615
|
+
i18n_content: {
|
|
616
|
+
zh_cn: '🛠️ 等待工具执行',
|
|
617
|
+
en_us: '🛠️ Tool use pending',
|
|
618
|
+
},
|
|
619
|
+
text_color: 'grey',
|
|
620
|
+
text_size: 'notation',
|
|
621
|
+
},
|
|
622
|
+
vertical_align: 'center',
|
|
623
|
+
icon: {
|
|
624
|
+
tag: 'standard_icon',
|
|
625
|
+
token: 'down-small-ccm_outlined',
|
|
626
|
+
color: 'grey',
|
|
627
|
+
size: '16px 16px',
|
|
628
|
+
},
|
|
629
|
+
icon_position: 'right',
|
|
630
|
+
icon_expanded_angle: -180,
|
|
631
|
+
},
|
|
632
|
+
border: { color: 'grey', corner_radius: '5px' },
|
|
633
|
+
vertical_spacing: '8px',
|
|
634
|
+
padding: '8px 8px 8px 8px',
|
|
635
|
+
elements: [],
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
function buildToolUsePanel(params) {
|
|
639
|
+
const { toolUseSteps = [], toolUseElapsedMs, titleSuffix } = params;
|
|
640
|
+
const duration = toolUseElapsedMs ? formatToolUseDuration(toolUseElapsedMs) : null;
|
|
641
|
+
const zhTitleParts = [duration?.zh ?? '工具执行'];
|
|
642
|
+
const enTitleParts = [duration?.en ?? 'Tool use'];
|
|
643
|
+
if (titleSuffix) {
|
|
644
|
+
zhTitleParts.push(titleSuffix.zh);
|
|
645
|
+
enTitleParts.push(titleSuffix.en);
|
|
646
|
+
}
|
|
647
|
+
const stepElements = toolUseSteps.length > 0 ? toolUseSteps.map((step) => buildToolUseStepElement(step)) : [buildToolUsePlaceholder()];
|
|
648
|
+
return {
|
|
649
|
+
tag: 'collapsible_panel',
|
|
650
|
+
expanded: false,
|
|
651
|
+
header: {
|
|
652
|
+
title: {
|
|
653
|
+
tag: 'plain_text',
|
|
654
|
+
content: `🛠️ ${enTitleParts.join(' · ')}`,
|
|
655
|
+
i18n_content: {
|
|
656
|
+
zh_cn: `🛠️ ${zhTitleParts.join(' · ')}`,
|
|
657
|
+
en_us: `🛠️ ${enTitleParts.join(' · ')}`,
|
|
658
|
+
},
|
|
659
|
+
text_color: 'grey',
|
|
660
|
+
text_size: 'notation',
|
|
661
|
+
},
|
|
662
|
+
vertical_align: 'center',
|
|
663
|
+
icon: {
|
|
664
|
+
tag: 'standard_icon',
|
|
665
|
+
token: 'down-small-ccm_outlined',
|
|
666
|
+
color: 'grey',
|
|
667
|
+
size: '16px 16px',
|
|
668
|
+
},
|
|
669
|
+
icon_position: 'right',
|
|
670
|
+
icon_expanded_angle: -180,
|
|
671
|
+
},
|
|
672
|
+
border: { color: 'grey', corner_radius: '5px' },
|
|
673
|
+
vertical_spacing: '8px',
|
|
674
|
+
padding: '8px 8px 8px 8px',
|
|
675
|
+
elements: stepElements,
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
function buildToolUseStepElement(step) {
|
|
679
|
+
return {
|
|
680
|
+
tag: 'div',
|
|
681
|
+
icon: {
|
|
682
|
+
tag: 'standard_icon',
|
|
683
|
+
token: step.iconToken,
|
|
684
|
+
color: 'grey',
|
|
685
|
+
},
|
|
686
|
+
text: {
|
|
687
|
+
tag: 'plain_text',
|
|
688
|
+
content: step.detail ? `${step.title}\n${step.detail}` : step.title,
|
|
689
|
+
text_color: 'grey',
|
|
690
|
+
text_size: 'notation',
|
|
691
|
+
},
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
function buildToolUsePlaceholder(labels) {
|
|
695
|
+
const zh = labels?.zh ?? '暂无工具步骤';
|
|
696
|
+
const en = labels?.en ?? tool_use_display_1.EMPTY_TOOL_USE_PLACEHOLDER;
|
|
697
|
+
return {
|
|
698
|
+
tag: 'div',
|
|
699
|
+
text: {
|
|
700
|
+
tag: 'plain_text',
|
|
701
|
+
content: en,
|
|
702
|
+
i18n_content: {
|
|
703
|
+
zh_cn: zh,
|
|
704
|
+
en_us: en,
|
|
705
|
+
},
|
|
706
|
+
text_color: 'grey',
|
|
707
|
+
text_size: 'notation',
|
|
708
|
+
},
|
|
709
|
+
};
|
|
710
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Shared utilities for the reasoning display subsystem.
|
|
6
|
+
*/
|
|
7
|
+
export declare function normalizeToolName(name?: string): string;
|
|
8
|
+
export declare function truncateText(value: string, maxLength: number): string;
|
|
9
|
+
export declare function redactInlineSecrets(value: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Sanitize tool params for safe logging.
|
|
12
|
+
* Logs only param key names (no values) to avoid leaking sensitive data.
|
|
13
|
+
*/
|
|
14
|
+
export declare function sanitizeParamsForLog(params?: Record<string, unknown>): string;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*
|
|
6
|
+
* Shared utilities for the reasoning display subsystem.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.normalizeToolName = normalizeToolName;
|
|
10
|
+
exports.truncateText = truncateText;
|
|
11
|
+
exports.redactInlineSecrets = redactInlineSecrets;
|
|
12
|
+
exports.sanitizeParamsForLog = sanitizeParamsForLog;
|
|
13
|
+
function normalizeToolName(name) {
|
|
14
|
+
return name?.trim().toLowerCase() ?? '';
|
|
15
|
+
}
|
|
16
|
+
function truncateText(value, maxLength) {
|
|
17
|
+
if (value.length <= maxLength)
|
|
18
|
+
return value;
|
|
19
|
+
return `${value.slice(0, maxLength - 3)}...`;
|
|
20
|
+
}
|
|
21
|
+
const INLINE_ASSIGNMENT_RE = /(^|[\s"'`])([A-Za-z_][A-Za-z0-9_]*)(=(?:"[^"]*"|'[^']*'|[^\s"'`]+))/g;
|
|
22
|
+
const AUTH_HEADER_SECRET_RE = /(Authorization\s*:\s*(?:Bearer|Basic|Token)\s+)([^'"\s]+)/gi;
|
|
23
|
+
const QUOTED_HEADER_ARG_RE = /((?:^|[\s"'`])(?:-H|--header)\s+)(['"])([A-Za-z0-9_-]+)(\s*:\s*)([^'"]*)(\2)/gi;
|
|
24
|
+
const UNQUOTED_HEADER_ARG_RE = /((?:^|[\s"'`])(?:-H|--header)\s+)([A-Za-z0-9_-]+)(\s*:\s*)([^\s"'`]+)/gi;
|
|
25
|
+
const SECRET_FLAG_RE = /((?:^|[\s"'`]))(--?[A-Za-z0-9][A-Za-z0-9-]*)(=|\s+)(?:"([^"]*)"|'([^']*)'|([^\s"'`]+))/g;
|
|
26
|
+
const SENSITIVE_NAME_RE = /token|secret|password|api[_-]?key|authorization|cookie|credential|bearer|session[_-]?id|client[_-]?secret|access[_-]?key/i;
|
|
27
|
+
function redactInlineSecrets(value) {
|
|
28
|
+
return value
|
|
29
|
+
.replace(INLINE_ASSIGNMENT_RE, (match, prefix, key) => isSensitiveName(key) ? `${prefix}${key}=[redacted]` : match)
|
|
30
|
+
.replace(AUTH_HEADER_SECRET_RE, '$1[redacted]')
|
|
31
|
+
.replace(QUOTED_HEADER_ARG_RE, (match, prefix, quote, name, separator) => shouldRedactHeaderValue(name) ? `${prefix}${quote}${name}${separator}[redacted]${quote}` : match)
|
|
32
|
+
.replace(UNQUOTED_HEADER_ARG_RE, (match, prefix, name, separator) => shouldRedactHeaderValue(name) ? `${prefix}${name}${separator}[redacted]` : match)
|
|
33
|
+
.replace(SECRET_FLAG_RE, (match, prefix, flag, separator, doubleQuoted, singleQuoted, bare) => {
|
|
34
|
+
const normalizedFlag = flag.replace(/^-+/, '');
|
|
35
|
+
if (!isSensitiveName(normalizedFlag))
|
|
36
|
+
return match;
|
|
37
|
+
const redactedValue = doubleQuoted !== undefined
|
|
38
|
+
? '"[redacted]"'
|
|
39
|
+
: singleQuoted !== undefined
|
|
40
|
+
? "'[redacted]'"
|
|
41
|
+
: bare !== undefined
|
|
42
|
+
? '[redacted]'
|
|
43
|
+
: '[redacted]';
|
|
44
|
+
return `${prefix}${flag}${separator}${redactedValue}`;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function isSensitiveName(value) {
|
|
48
|
+
return SENSITIVE_NAME_RE.test(value);
|
|
49
|
+
}
|
|
50
|
+
function shouldRedactHeaderValue(name) {
|
|
51
|
+
return !/^authorization$/i.test(name) && isSensitiveName(name);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Sanitize tool params for safe logging.
|
|
55
|
+
* Logs only param key names (no values) to avoid leaking sensitive data.
|
|
56
|
+
*/
|
|
57
|
+
function sanitizeParamsForLog(params) {
|
|
58
|
+
if (!params || typeof params !== 'object')
|
|
59
|
+
return '';
|
|
60
|
+
const keys = Object.keys(params);
|
|
61
|
+
if (keys.length === 0)
|
|
62
|
+
return '{}';
|
|
63
|
+
return `{${keys.join(',')}}`;
|
|
64
|
+
}
|
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
* reply-dispatcher.ts, streaming-card-controller.ts, flush-controller.ts,
|
|
9
9
|
* and unavailable-guard.ts.
|
|
10
10
|
*/
|
|
11
|
-
import type { ClawdbotConfig
|
|
11
|
+
import type { ClawdbotConfig } from 'openclaw/plugin-sdk';
|
|
12
|
+
import type { ReplyDispatcher } from 'openclaw/plugin-sdk/reply-runtime';
|
|
12
13
|
import type { FeishuFooterConfig } from '../core/types';
|
|
14
|
+
import type { ToolUseDisplayConfig } from './tool-use-config';
|
|
13
15
|
export declare const CARD_PHASES: {
|
|
14
16
|
readonly idle: "idle";
|
|
15
17
|
readonly creating: "creating";
|
|
@@ -38,11 +40,17 @@ export interface ReasoningState {
|
|
|
38
40
|
reasoningElapsedMs: number;
|
|
39
41
|
isReasoningPhase: boolean;
|
|
40
42
|
}
|
|
43
|
+
export interface ToolUseState {
|
|
44
|
+
startedAt: number | null;
|
|
45
|
+
elapsedMs: number;
|
|
46
|
+
isActive: boolean;
|
|
47
|
+
}
|
|
41
48
|
export interface StreamingTextState {
|
|
42
49
|
accumulatedText: string;
|
|
43
50
|
completedText: string;
|
|
44
51
|
streamingPrefix: string;
|
|
45
52
|
lastPartialText: string;
|
|
53
|
+
lastFlushedText: string;
|
|
46
54
|
}
|
|
47
55
|
export interface CardKitState {
|
|
48
56
|
cardKitCardId: string | null;
|
|
@@ -65,6 +73,7 @@ export declare const THROTTLE_CONSTANTS: {
|
|
|
65
73
|
readonly PATCH_MS: 1500;
|
|
66
74
|
readonly LONG_GAP_THRESHOLD_MS: 2000;
|
|
67
75
|
readonly BATCH_AFTER_GAP_MS: 300;
|
|
76
|
+
readonly REASONING_STATUS_MS: 1500;
|
|
68
77
|
};
|
|
69
78
|
export declare const EMPTY_REPLY_FALLBACK_TEXT = "Done.";
|
|
70
79
|
export interface CreateFeishuReplyDispatcherParams {
|
|
@@ -81,20 +90,7 @@ export interface CreateFeishuReplyDispatcherParams {
|
|
|
81
90
|
skipTyping?: boolean;
|
|
82
91
|
/** When true, replies are sent into the thread instead of main chat. */
|
|
83
92
|
replyInThread?: boolean;
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Manual mirror of the SDK-internal ReplyDispatcher type
|
|
87
|
-
* (from openclaw/plugin-sdk auto-reply/reply/reply-dispatcher.d.ts).
|
|
88
|
-
*
|
|
89
|
-
* Must be kept in sync when the SDK updates the dispatcher signature.
|
|
90
|
-
*/
|
|
91
|
-
export interface ReplyDispatcher {
|
|
92
|
-
sendToolResult: (payload: ReplyPayload) => boolean;
|
|
93
|
-
sendBlockReply: (payload: ReplyPayload) => boolean;
|
|
94
|
-
sendFinalReply: (payload: ReplyPayload) => boolean;
|
|
95
|
-
waitForIdle: () => Promise<void>;
|
|
96
|
-
getQueuedCounts: () => Record<string, number>;
|
|
97
|
-
markComplete: () => void;
|
|
93
|
+
toolUseDisplay: ToolUseDisplayConfig;
|
|
98
94
|
}
|
|
99
95
|
/**
|
|
100
96
|
* The structured return type of createFeishuReplyDispatcher.
|
|
@@ -128,5 +124,6 @@ export interface StreamingCardDeps {
|
|
|
128
124
|
chatId: string;
|
|
129
125
|
replyToMessageId: string | undefined;
|
|
130
126
|
replyInThread: boolean | undefined;
|
|
127
|
+
toolUseDisplay: ToolUseDisplayConfig;
|
|
131
128
|
resolvedFooter: Required<FeishuFooterConfig>;
|
|
132
129
|
}
|