@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
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*
|
|
6
|
+
* Resolution logic for Feishu tool-use display.
|
|
7
|
+
*
|
|
8
|
+
* The source of truth is OpenClaw's effective verbose state:
|
|
9
|
+
* inline `/verbose` override > session store override > config default.
|
|
10
|
+
* Feishu channel config only retains UI-level detail (`showFullPaths`).
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.resolveToolUseDisplayConfig = resolveToolUseDisplayConfig;
|
|
14
|
+
const agent_runtime_1 = require("openclaw/plugin-sdk/agent-runtime");
|
|
15
|
+
const config_runtime_1 = require("openclaw/plugin-sdk/config-runtime");
|
|
16
|
+
function resolveToolUseDisplayConfig(params) {
|
|
17
|
+
const mode = resolveEffectiveVerboseMode(params);
|
|
18
|
+
return {
|
|
19
|
+
mode,
|
|
20
|
+
showToolUse: mode !== 'off',
|
|
21
|
+
showToolResultDetails: mode === 'full',
|
|
22
|
+
showFullPaths: params.feishuCfg?.toolUseDisplay?.showFullPaths === true,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function resolveEffectiveVerboseMode(params) {
|
|
26
|
+
return (extractInlineVerboseMode(params.body) ??
|
|
27
|
+
resolveSessionVerboseMode(params.cfg, params.sessionKey, params.agentId) ??
|
|
28
|
+
normalizeToolUseMode(params.cfg.agents?.defaults?.verboseDefault) ??
|
|
29
|
+
'off');
|
|
30
|
+
}
|
|
31
|
+
function resolveSessionVerboseMode(cfg, sessionKey, agentId) {
|
|
32
|
+
try {
|
|
33
|
+
const cfgWithSession = cfg;
|
|
34
|
+
const sessionStorePath = cfgWithSession.session?.store ?? cfgWithSession.sessions?.store;
|
|
35
|
+
const storePath = (0, config_runtime_1.resolveStorePath)(sessionStorePath, { agentId });
|
|
36
|
+
const store = (0, config_runtime_1.loadSessionStore)(storePath);
|
|
37
|
+
const candidateKeys = resolveCandidateSessionKeys(cfg, sessionKey);
|
|
38
|
+
for (const candidateKey of candidateKeys) {
|
|
39
|
+
const resolved = (0, config_runtime_1.resolveSessionStoreEntry)({ store, sessionKey: candidateKey });
|
|
40
|
+
const mode = normalizeToolUseMode(resolved.existing?.verboseLevel);
|
|
41
|
+
if (mode)
|
|
42
|
+
return mode;
|
|
43
|
+
if (resolved.existing)
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function resolveCandidateSessionKeys(cfg, sessionKey) {
|
|
53
|
+
const key = sessionKey.trim().toLowerCase();
|
|
54
|
+
const defaultAgentId = (0, agent_runtime_1.resolveDefaultAgentId)(cfg);
|
|
55
|
+
const fallbackKey = key.replace(/^(agent):[^:]+:/, `$1:${defaultAgentId}:`);
|
|
56
|
+
return fallbackKey !== key ? [key, fallbackKey] : [key];
|
|
57
|
+
}
|
|
58
|
+
function extractInlineVerboseMode(body) {
|
|
59
|
+
if (!body)
|
|
60
|
+
return undefined;
|
|
61
|
+
const matches = body.matchAll(/(?:^|\s)\/(?:verbose|v)(?::|\s+)(on|off|full)\b/gi);
|
|
62
|
+
let last;
|
|
63
|
+
for (const match of matches) {
|
|
64
|
+
last = normalizeToolUseMode(match[1]);
|
|
65
|
+
}
|
|
66
|
+
return last;
|
|
67
|
+
}
|
|
68
|
+
function normalizeToolUseMode(value) {
|
|
69
|
+
if (typeof value !== 'string')
|
|
70
|
+
return undefined;
|
|
71
|
+
const normalized = value.trim().toLowerCase();
|
|
72
|
+
if (normalized === 'off' || normalized === 'on' || normalized === 'full') {
|
|
73
|
+
return normalized;
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Structured tool-use display for Lark/Feishu cards.
|
|
6
|
+
*/
|
|
7
|
+
import type { ToolUseTraceStep } from './tool-use-trace-store';
|
|
8
|
+
export interface ToolUseDisplayStep {
|
|
9
|
+
title: string;
|
|
10
|
+
detail?: string;
|
|
11
|
+
iconToken: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ToolUseDisplayResult {
|
|
14
|
+
content: string;
|
|
15
|
+
stepCount: number;
|
|
16
|
+
steps: ToolUseDisplayStep[];
|
|
17
|
+
}
|
|
18
|
+
export declare const EMPTY_TOOL_USE_PLACEHOLDER = "No tool steps available";
|
|
19
|
+
export declare function normalizeToolUseDisplay(params: {
|
|
20
|
+
traceSteps?: ToolUseTraceStep[];
|
|
21
|
+
showFullPaths?: boolean;
|
|
22
|
+
showResultDetails?: boolean;
|
|
23
|
+
}): ToolUseDisplayResult;
|
|
24
|
+
export declare function buildToolUseTitleSuffix(params: {
|
|
25
|
+
stepCount: number;
|
|
26
|
+
}): {
|
|
27
|
+
zh: string;
|
|
28
|
+
en: string;
|
|
29
|
+
};
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*
|
|
6
|
+
* Structured tool-use display for Lark/Feishu cards.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.EMPTY_TOOL_USE_PLACEHOLDER = void 0;
|
|
10
|
+
exports.normalizeToolUseDisplay = normalizeToolUseDisplay;
|
|
11
|
+
exports.buildToolUseTitleSuffix = buildToolUseTitleSuffix;
|
|
12
|
+
const reasoning_utils_1 = require("./reasoning-utils.js");
|
|
13
|
+
exports.EMPTY_TOOL_USE_PLACEHOLDER = 'No tool steps available';
|
|
14
|
+
const DEFAULT_SUMMARY_PREFERENCE = ['matched', 'code', 'quoted', 'url', 'line'];
|
|
15
|
+
const TOOL_DESCRIPTORS = [
|
|
16
|
+
{
|
|
17
|
+
aliases: ['skill'],
|
|
18
|
+
iconToken: 'app-default_outlined',
|
|
19
|
+
title: 'Load skill',
|
|
20
|
+
sanitizer: 'skill',
|
|
21
|
+
paramKeys: ['skill', 'name'],
|
|
22
|
+
summaryPatterns: [/^(?:load|use)\s+skill\s+(.+)$/i],
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
aliases: ['read', 'open'],
|
|
26
|
+
iconToken: 'file-link-text_outlined',
|
|
27
|
+
title: 'Read',
|
|
28
|
+
sanitizer: 'path',
|
|
29
|
+
paramKeys: ['file_path', 'path', 'file'],
|
|
30
|
+
summaryPatterns: [/^(?:read|open)\s+(?:file\s+)?(.+)$/i],
|
|
31
|
+
summaryPreference: ['code', 'quoted', 'matched', 'line'],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
aliases: ['write', 'edit'],
|
|
35
|
+
iconToken: 'edit_outlined',
|
|
36
|
+
title: 'Edit',
|
|
37
|
+
sanitizer: 'path',
|
|
38
|
+
paramKeys: ['file_path', 'path', 'file'],
|
|
39
|
+
summaryPatterns: [/^(?:edit|write)\s+(?:file\s+)?(.+)$/i],
|
|
40
|
+
summaryPreference: ['code', 'quoted', 'matched', 'line'],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
aliases: ['web_search', 'web-search', 'search'],
|
|
44
|
+
iconToken: 'search_outlined',
|
|
45
|
+
title: 'Search web',
|
|
46
|
+
sanitizer: 'search',
|
|
47
|
+
paramKeys: ['query', 'q'],
|
|
48
|
+
summaryPatterns: [/^(?:search\s+(?:web\s+)?(?:for|about)|query)\s+(.+)$/i],
|
|
49
|
+
summaryPreference: ['quoted', 'matched', 'line'],
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
aliases: ['web_fetch', 'web-fetch', 'fetch'],
|
|
53
|
+
iconToken: 'language_outlined',
|
|
54
|
+
title: 'Fetch web page',
|
|
55
|
+
sanitizer: 'url',
|
|
56
|
+
paramKeys: ['url'],
|
|
57
|
+
summaryPatterns: [/^(?:fetch|open)\s+(?:web\s+page\s+)?(?:from\s+)?(.+)$/i],
|
|
58
|
+
summaryPreference: ['url', 'matched', 'quoted', 'line'],
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
aliases: ['grep'],
|
|
62
|
+
iconToken: 'doc-search_outlined',
|
|
63
|
+
title: 'Search text',
|
|
64
|
+
sanitizer: 'generic',
|
|
65
|
+
detailFromParams: (params) => buildPatternDetail(params, { includeTarget: true }),
|
|
66
|
+
summaryPatterns: [/^(?:search\s+text(?:\s+by\s+pattern)?|grep)\s+(.+)$/i],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
aliases: ['glob'],
|
|
70
|
+
iconToken: 'folder_outlined',
|
|
71
|
+
title: 'Search files',
|
|
72
|
+
sanitizer: 'generic',
|
|
73
|
+
paramKeys: ['pattern'],
|
|
74
|
+
summaryPatterns: [/^(?:search\s+files(?:\s+by\s+pattern)?|glob)\s+(.+)$/i],
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
aliases: ['exec', 'bash', 'command', 'run'],
|
|
78
|
+
iconToken: 'setting_outlined',
|
|
79
|
+
title: 'Run command',
|
|
80
|
+
sanitizer: 'command',
|
|
81
|
+
paramKeys: ['description', 'command', 'script'],
|
|
82
|
+
summaryPatterns: [/^(?:run|execute)\s+(?:command|script)?\s*(.+)$/i],
|
|
83
|
+
summaryPreference: ['code', 'quoted', 'matched', 'line'],
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
aliases: ['browser', 'playwright', 'navigate'],
|
|
87
|
+
iconToken: 'browser-mac_outlined',
|
|
88
|
+
title: 'Browser',
|
|
89
|
+
sanitizer: 'url',
|
|
90
|
+
paramKeys: ['url'],
|
|
91
|
+
summaryPatterns: [/^(?:open|browse|visit|navigate\s+to)\s+(.+)$/i],
|
|
92
|
+
summaryPreference: ['url', 'quoted', 'matched', 'line'],
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
aliases: ['agent', 'task', 'spawn'],
|
|
96
|
+
iconToken: 'robot_outlined',
|
|
97
|
+
title: 'Run sub-agent',
|
|
98
|
+
sanitizer: 'generic',
|
|
99
|
+
paramKeys: ['task', 'description', 'prompt'],
|
|
100
|
+
summaryPatterns: [/^(?:run\s+sub-?agent|spawn\s+agent)\s+(.+)$/i],
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
aliases: ['check', 'determine', 'verify'],
|
|
104
|
+
iconToken: 'list-check_outlined',
|
|
105
|
+
title: 'Check',
|
|
106
|
+
sanitizer: 'generic',
|
|
107
|
+
paramKeys: ['target', 'subject', 'description'],
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
aliases: ['summarize', 'analyze', 'prepare'],
|
|
111
|
+
iconToken: 'report_outlined',
|
|
112
|
+
title: 'Analyze',
|
|
113
|
+
sanitizer: 'generic',
|
|
114
|
+
paramKeys: ['target', 'subject', 'description'],
|
|
115
|
+
},
|
|
116
|
+
];
|
|
117
|
+
function normalizeToolUseDisplay(params) {
|
|
118
|
+
const traceSteps = params.traceSteps ?? [];
|
|
119
|
+
const showFullPaths = params.showFullPaths === true;
|
|
120
|
+
const showResultDetails = params.showResultDetails === true;
|
|
121
|
+
const sources = traceSteps.map(toTraceSource);
|
|
122
|
+
const steps = sources
|
|
123
|
+
.map((source) => formatToolStep(source, { showFullPaths, showResultDetails }))
|
|
124
|
+
.filter((step) => !!step);
|
|
125
|
+
return {
|
|
126
|
+
content: steps.map((step) => (step.detail ? `- ${step.title}: ${step.detail}` : `- ${step.title}`)).join('\n'),
|
|
127
|
+
stepCount: steps.length,
|
|
128
|
+
steps,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function buildToolUseTitleSuffix(params) {
|
|
132
|
+
const { stepCount } = params;
|
|
133
|
+
return {
|
|
134
|
+
zh: `查看 ${stepCount} 个步骤`,
|
|
135
|
+
en: `Show ${stepCount} step${stepCount === 1 ? '' : 's'}`,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function toTraceSource(step) {
|
|
139
|
+
return {
|
|
140
|
+
toolName: step.toolName,
|
|
141
|
+
params: step.params,
|
|
142
|
+
result: step.result,
|
|
143
|
+
error: step.error,
|
|
144
|
+
durationMs: step.durationMs,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function formatToolStep(source, options) {
|
|
148
|
+
const descriptor = resolveToolDescriptor(source.toolName);
|
|
149
|
+
const rawDetail = (descriptor ? extractDetailFromParams(source.params, descriptor) : undefined) ??
|
|
150
|
+
(descriptor ? extractDetailFromSummary(source.summaryText, descriptor) : cleanupLine(source.summaryText ?? '')) ??
|
|
151
|
+
undefined;
|
|
152
|
+
const detail = rawDetail ? sanitizeToolDetail(descriptor?.sanitizer ?? 'generic', rawDetail, options) : undefined;
|
|
153
|
+
const title = buildToolTitle(source, descriptor, rawDetail);
|
|
154
|
+
const meta = buildStepMeta(source, descriptor, options);
|
|
155
|
+
return {
|
|
156
|
+
title,
|
|
157
|
+
detail: joinDetailParts(detail, meta),
|
|
158
|
+
iconToken: descriptor?.iconToken ?? 'setting-inter_outlined',
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function buildToolTitle(source, descriptor, rawDetail) {
|
|
162
|
+
const baseTitle = descriptor?.title === 'Read' && rawDetail && isSkillPathValue(rawDetail)
|
|
163
|
+
? 'Skill Read'
|
|
164
|
+
: (descriptor?.title ?? humanizeToolName(source.toolName ?? 'tool'));
|
|
165
|
+
const durationLabel = source.durationMs != null ? formatDurationLabel(source.durationMs) : undefined;
|
|
166
|
+
return durationLabel ? `${baseTitle} (${durationLabel})` : baseTitle;
|
|
167
|
+
}
|
|
168
|
+
function resolveToolDescriptor(toolName) {
|
|
169
|
+
const normalizedName = (0, reasoning_utils_1.normalizeToolName)(toolName);
|
|
170
|
+
return TOOL_DESCRIPTORS.find((descriptor) => descriptor.aliases.some((alias) => normalizedName === alias || normalizedName.startsWith(`${alias}_`) || normalizedName.startsWith(`${alias}-`)));
|
|
171
|
+
}
|
|
172
|
+
function extractDetailFromParams(params, descriptor) {
|
|
173
|
+
if (!params)
|
|
174
|
+
return undefined;
|
|
175
|
+
if (descriptor.detailFromParams)
|
|
176
|
+
return descriptor.detailFromParams(params);
|
|
177
|
+
for (const key of descriptor.paramKeys ?? []) {
|
|
178
|
+
const value = params[key];
|
|
179
|
+
const text = extractScalarText(value);
|
|
180
|
+
if (text)
|
|
181
|
+
return text;
|
|
182
|
+
}
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
function extractDetailFromSummary(summaryText, descriptor) {
|
|
186
|
+
if (!summaryText)
|
|
187
|
+
return undefined;
|
|
188
|
+
const lines = summaryText
|
|
189
|
+
.replace(/\r\n/g, '\n')
|
|
190
|
+
.split('\n')
|
|
191
|
+
.map((line) => cleanupLine(stripMarkdown(line)))
|
|
192
|
+
.filter((line) => line && !isNoiseLine(line));
|
|
193
|
+
for (const line of lines) {
|
|
194
|
+
const signals = buildSummarySignals(line, descriptor.summaryPatterns ?? []);
|
|
195
|
+
const detail = pickSummaryDetail(signals, descriptor.summaryPreference ?? DEFAULT_SUMMARY_PREFERENCE);
|
|
196
|
+
if (detail)
|
|
197
|
+
return detail;
|
|
198
|
+
}
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
function buildSummarySignals(line, patterns) {
|
|
202
|
+
const matched = patterns
|
|
203
|
+
.map((pattern) => line.match(pattern)?.[1]?.trim())
|
|
204
|
+
.find((value) => Boolean(value));
|
|
205
|
+
return {
|
|
206
|
+
line,
|
|
207
|
+
matched,
|
|
208
|
+
code: extractFirstCodeSpan(line),
|
|
209
|
+
quoted: extractFirstQuotedText(line),
|
|
210
|
+
url: extractFirstUrl(line),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function pickSummaryDetail(signals, preference) {
|
|
214
|
+
for (const key of preference) {
|
|
215
|
+
const value = signals[key];
|
|
216
|
+
if (value)
|
|
217
|
+
return value;
|
|
218
|
+
}
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
|
221
|
+
function buildStepMeta(source, descriptor, options) {
|
|
222
|
+
const parts = [];
|
|
223
|
+
if (source.error) {
|
|
224
|
+
parts.push(`Failed: ${source.error}`);
|
|
225
|
+
}
|
|
226
|
+
else if (options.showResultDetails) {
|
|
227
|
+
const resultDetail = buildResultDetail(source, descriptor, options);
|
|
228
|
+
if (resultDetail) {
|
|
229
|
+
parts.push(`Result: ${resultDetail}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return parts.length > 0 ? parts.join(' · ') : undefined;
|
|
233
|
+
}
|
|
234
|
+
function joinDetailParts(detail, meta) {
|
|
235
|
+
if (detail && meta)
|
|
236
|
+
return `${detail} · ${meta}`;
|
|
237
|
+
return detail ?? meta;
|
|
238
|
+
}
|
|
239
|
+
function buildResultDetail(source, descriptor, options) {
|
|
240
|
+
if (source.result == null)
|
|
241
|
+
return undefined;
|
|
242
|
+
if (descriptor && ['Read', 'Edit', 'Fetch web page', 'Browser'].includes(descriptor.title)) {
|
|
243
|
+
return undefined;
|
|
244
|
+
}
|
|
245
|
+
const raw = asDisplayText(source.result);
|
|
246
|
+
const cleaned = descriptor
|
|
247
|
+
? sanitizeToolDetail(descriptor.sanitizer, raw, options)
|
|
248
|
+
: sanitizeToolDetail('generic', raw, options);
|
|
249
|
+
return cleaned || undefined;
|
|
250
|
+
}
|
|
251
|
+
function buildPatternDetail(params, options) {
|
|
252
|
+
const pattern = extractScalarText(params.pattern);
|
|
253
|
+
const target = extractScalarText(params.glob ?? params.path ?? params.file_path);
|
|
254
|
+
if (pattern && target && options.includeTarget) {
|
|
255
|
+
return `${pattern} in ${target}`;
|
|
256
|
+
}
|
|
257
|
+
return pattern ?? target ?? undefined;
|
|
258
|
+
}
|
|
259
|
+
function extractScalarText(value) {
|
|
260
|
+
if (typeof value === 'string')
|
|
261
|
+
return value.trim() || undefined;
|
|
262
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
263
|
+
return String(value);
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
function sanitizeToolDetail(kind, value, options) {
|
|
267
|
+
const cleaned = sanitizeGenericText(value);
|
|
268
|
+
if (!cleaned)
|
|
269
|
+
return undefined;
|
|
270
|
+
switch (kind) {
|
|
271
|
+
case 'skill':
|
|
272
|
+
return (cleaned
|
|
273
|
+
.replace(/^skill\s+/i, '')
|
|
274
|
+
.replace(/[-_]+/g, ' ')
|
|
275
|
+
.trim() || 'skill');
|
|
276
|
+
case 'path':
|
|
277
|
+
return sanitizePathLike(cleaned, options);
|
|
278
|
+
case 'search':
|
|
279
|
+
return stripQuotes(cleaned);
|
|
280
|
+
case 'url':
|
|
281
|
+
return stripQuotes(cleaned).replace(/^from\s+/i, '');
|
|
282
|
+
case 'command':
|
|
283
|
+
return sanitizeCommandLike(cleaned, options);
|
|
284
|
+
case 'generic':
|
|
285
|
+
default:
|
|
286
|
+
return cleaned;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function sanitizePathLike(value, options) {
|
|
290
|
+
const cleaned = sanitizeGenericText(value)
|
|
291
|
+
.replace(/^(?:from|file|path)\s+/i, '')
|
|
292
|
+
.trim();
|
|
293
|
+
if (options.showFullPaths)
|
|
294
|
+
return cleaned;
|
|
295
|
+
const skillMatch = cleaned.match(/(?:^|\/)skills\/([^/]+)\//i);
|
|
296
|
+
if (skillMatch?.[1]) {
|
|
297
|
+
return skillMatch[1].replace(/[-_]+/g, ' ').trim() || cleaned;
|
|
298
|
+
}
|
|
299
|
+
const segments = cleaned.split(/[\\/]/).filter(Boolean);
|
|
300
|
+
return segments.at(-1) ?? cleaned;
|
|
301
|
+
}
|
|
302
|
+
function sanitizeCommandLike(value, options) {
|
|
303
|
+
const cleaned = stripQuotes(value)
|
|
304
|
+
.replace(/^(?:command|script|description)\s+/i, '')
|
|
305
|
+
.replace(/^.*?\s+->\s+/i, '')
|
|
306
|
+
.trim();
|
|
307
|
+
if (!cleaned)
|
|
308
|
+
return 'command';
|
|
309
|
+
const redacted = (0, reasoning_utils_1.redactInlineSecrets)(cleaned);
|
|
310
|
+
return options.showFullPaths ? redacted : redactCommandPaths(redacted);
|
|
311
|
+
}
|
|
312
|
+
function redactCommandPaths(command) {
|
|
313
|
+
return command
|
|
314
|
+
.split(/(\s+)/)
|
|
315
|
+
.map((segment) => {
|
|
316
|
+
if (!segment || /^\s+$/.test(segment))
|
|
317
|
+
return segment;
|
|
318
|
+
return redactCommandToken(segment);
|
|
319
|
+
})
|
|
320
|
+
.join('');
|
|
321
|
+
}
|
|
322
|
+
function redactCommandToken(token) {
|
|
323
|
+
const match = token.match(/^([("'`]*)(.*?)([)"'`,;:]*)$/);
|
|
324
|
+
if (!match)
|
|
325
|
+
return token;
|
|
326
|
+
const [, prefix, rawCore, suffix] = match;
|
|
327
|
+
const core = redactPathAssignment(rawCore);
|
|
328
|
+
return `${prefix}${core}${suffix}`;
|
|
329
|
+
}
|
|
330
|
+
function redactPathAssignment(value) {
|
|
331
|
+
const equalsIndex = value.indexOf('=');
|
|
332
|
+
if (equalsIndex > 0) {
|
|
333
|
+
const left = value.slice(0, equalsIndex + 1);
|
|
334
|
+
const right = value.slice(equalsIndex + 1);
|
|
335
|
+
return `${left}${redactStandalonePath(right)}`;
|
|
336
|
+
}
|
|
337
|
+
return redactStandalonePath(value);
|
|
338
|
+
}
|
|
339
|
+
function redactStandalonePath(value) {
|
|
340
|
+
if (/^https?:\/\//i.test(value))
|
|
341
|
+
return sanitizeUrlForDisplay(value);
|
|
342
|
+
if (!looksLikePathToken(value))
|
|
343
|
+
return value;
|
|
344
|
+
return basenameFromPath(value);
|
|
345
|
+
}
|
|
346
|
+
function sanitizeUrlForDisplay(url) {
|
|
347
|
+
try {
|
|
348
|
+
const parsed = new URL(url);
|
|
349
|
+
parsed.username = '';
|
|
350
|
+
parsed.password = '';
|
|
351
|
+
for (const key of [...parsed.searchParams.keys()]) {
|
|
352
|
+
if (/(secret|token|password|key|credential|bearer|auth)/i.test(key)) {
|
|
353
|
+
parsed.searchParams.set(key, '[redacted]');
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return parsed.toString();
|
|
357
|
+
}
|
|
358
|
+
catch {
|
|
359
|
+
return url;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
function looksLikePathToken(value) {
|
|
363
|
+
return (value.startsWith('~/') ||
|
|
364
|
+
value.startsWith('./') ||
|
|
365
|
+
value.startsWith('../') ||
|
|
366
|
+
value.startsWith('/') ||
|
|
367
|
+
value.includes('/'));
|
|
368
|
+
}
|
|
369
|
+
function basenameFromPath(value) {
|
|
370
|
+
const cleaned = value.replace(/\\/g, '/').replace(/\/+$/, '');
|
|
371
|
+
const segments = cleaned.split('/').filter(Boolean);
|
|
372
|
+
return segments.at(-1) ?? value;
|
|
373
|
+
}
|
|
374
|
+
function isSkillPathValue(value) {
|
|
375
|
+
return /(?:^|\/)skills\/[^/]+\//i.test(value);
|
|
376
|
+
}
|
|
377
|
+
function sanitizeGenericText(value) {
|
|
378
|
+
return value
|
|
379
|
+
.replace(/<[^>]+>/g, '')
|
|
380
|
+
.replace(/\s+/g, ' ')
|
|
381
|
+
.trim();
|
|
382
|
+
}
|
|
383
|
+
function cleanupLine(line) {
|
|
384
|
+
return line
|
|
385
|
+
.replace(/^[-*•]\s*/, '')
|
|
386
|
+
.replace(/^\d+[.)]\s*/, '')
|
|
387
|
+
.replace(/\s+/g, ' ')
|
|
388
|
+
.trim();
|
|
389
|
+
}
|
|
390
|
+
function stripMarkdown(line) {
|
|
391
|
+
return line
|
|
392
|
+
.replace(/`([^`]+)`/g, '$1')
|
|
393
|
+
.replace(/\*\*([^*]+)\*\*/g, '$1')
|
|
394
|
+
.replace(/\*([^*]+)\*/g, '$1')
|
|
395
|
+
.replace(/^>\s*/, '')
|
|
396
|
+
.trim();
|
|
397
|
+
}
|
|
398
|
+
function isNoiseLine(line) {
|
|
399
|
+
return /^(?:completed|complete|done|success|succeeded|running|started|finished|ok)$/i.test(line);
|
|
400
|
+
}
|
|
401
|
+
function humanizeToolName(name) {
|
|
402
|
+
const cleaned = name.replace(/[-_]+/g, ' ').trim();
|
|
403
|
+
if (!cleaned)
|
|
404
|
+
return 'Tool';
|
|
405
|
+
return cleaned.charAt(0).toUpperCase() + cleaned.slice(1);
|
|
406
|
+
}
|
|
407
|
+
function formatDurationLabel(durationMs) {
|
|
408
|
+
return durationMs < 1000 ? `${durationMs} ms` : `${(durationMs / 1000).toFixed(1)} s`;
|
|
409
|
+
}
|
|
410
|
+
function asDisplayText(value) {
|
|
411
|
+
if (typeof value === 'string')
|
|
412
|
+
return value;
|
|
413
|
+
if (value == null)
|
|
414
|
+
return '';
|
|
415
|
+
if (typeof value !== 'object')
|
|
416
|
+
return String(value);
|
|
417
|
+
try {
|
|
418
|
+
return JSON.stringify(value);
|
|
419
|
+
}
|
|
420
|
+
catch {
|
|
421
|
+
return '';
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
function stripQuotes(value) {
|
|
425
|
+
return value.replace(/^[`'"]+|[`'"]+$/g, '').trim();
|
|
426
|
+
}
|
|
427
|
+
function extractFirstCodeSpan(value) {
|
|
428
|
+
const match = value.match(/`([^`]+)`/);
|
|
429
|
+
return match?.[1]?.trim() || undefined;
|
|
430
|
+
}
|
|
431
|
+
function extractFirstQuotedText(value) {
|
|
432
|
+
const match = value.match(/["']([^"']+)["']/);
|
|
433
|
+
return match?.[1]?.trim() || undefined;
|
|
434
|
+
}
|
|
435
|
+
function extractFirstUrl(value) {
|
|
436
|
+
const match = value.match(/\bhttps?:\/\/[^\s"'`]+/i);
|
|
437
|
+
return match?.[0]?.trim() || undefined;
|
|
438
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Runtime store for structured tool-use steps.
|
|
6
|
+
*
|
|
7
|
+
* The Feishu card renderer reads from this store by session key so it can
|
|
8
|
+
* render observable, replayable tool execution without relying purely on
|
|
9
|
+
* reply payload text.
|
|
10
|
+
*/
|
|
11
|
+
export interface ToolUseTraceStep {
|
|
12
|
+
id: string;
|
|
13
|
+
seq: number;
|
|
14
|
+
toolName: string;
|
|
15
|
+
toolCallId?: string;
|
|
16
|
+
runId?: string;
|
|
17
|
+
params?: Record<string, unknown>;
|
|
18
|
+
result?: unknown;
|
|
19
|
+
error?: string;
|
|
20
|
+
durationMs?: number;
|
|
21
|
+
status: 'running' | 'success' | 'error';
|
|
22
|
+
startedAt: number;
|
|
23
|
+
finishedAt?: number;
|
|
24
|
+
}
|
|
25
|
+
export declare function startToolUseTraceRun(sessionKey: string): void;
|
|
26
|
+
export declare function clearToolUseTraceRun(sessionKey: string): void;
|
|
27
|
+
export declare function hasToolUseTraceRun(sessionKey?: string): boolean;
|
|
28
|
+
export declare function recordToolUseStart(params: {
|
|
29
|
+
sessionKey?: string;
|
|
30
|
+
toolName: string;
|
|
31
|
+
toolParams?: Record<string, unknown>;
|
|
32
|
+
toolCallId?: string;
|
|
33
|
+
runId?: string;
|
|
34
|
+
}): void;
|
|
35
|
+
export declare function recordToolUseEnd(params: {
|
|
36
|
+
sessionKey?: string;
|
|
37
|
+
toolName: string;
|
|
38
|
+
toolParams?: Record<string, unknown>;
|
|
39
|
+
toolCallId?: string;
|
|
40
|
+
runId?: string;
|
|
41
|
+
result?: unknown;
|
|
42
|
+
error?: string;
|
|
43
|
+
durationMs?: number;
|
|
44
|
+
}): void;
|
|
45
|
+
export declare function getToolUseTraceSteps(sessionKey?: string): ToolUseTraceStep[];
|
|
46
|
+
export declare function sanitizeTraceValue(value: unknown, depth?: number, context?: {
|
|
47
|
+
source?: 'params' | 'result' | 'generic';
|
|
48
|
+
key?: string;
|
|
49
|
+
}): unknown;
|
|
50
|
+
/** @internal — test-only helper to reset module-level state between test cases. */
|
|
51
|
+
export declare function _resetForTesting(): void;
|