@masterxinxin/lark-coding-agent-bridge 0.0.0-stage → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,817 @@
1
+ // src/card/mask-email.ts
2
+ var EMAIL_RE = /([A-Za-z0-9._%+-]+)@((?:[A-Za-z0-9-]+\.)+[A-Za-z]{2,})/g;
3
+ function maskEmails(text) {
4
+ return text.replace(EMAIL_RE, "$1[at]$2");
5
+ }
6
+ function deepMaskEmails(value) {
7
+ if (typeof value === "string") return maskEmails(value);
8
+ if (Array.isArray(value)) return value.map((v) => deepMaskEmails(v));
9
+ if (value && typeof value === "object") {
10
+ const out = {};
11
+ for (const [key, val] of Object.entries(value)) out[key] = deepMaskEmails(val);
12
+ return out;
13
+ }
14
+ return value;
15
+ }
16
+
17
+ // src/card/theme.ts
18
+ var CARD_SPINNER_FRAMES = [
19
+ "\u280B",
20
+ "\u2819",
21
+ "\u2839",
22
+ "\u2838",
23
+ "\u283C",
24
+ "\u2834",
25
+ "\u2826",
26
+ "\u2827",
27
+ "\u2807",
28
+ "\u280F"
29
+ ];
30
+ function spinnerFrame(now, frameMs = 500) {
31
+ return CARD_SPINNER_FRAMES[Math.floor(now / frameMs) % CARD_SPINNER_FRAMES.length];
32
+ }
33
+ function cardHeader(state, agentName = "Agent", now = Date.now()) {
34
+ switch (state.terminal) {
35
+ case "done":
36
+ return { template: "green", title: `\u2705 ${agentName} \u5DF2\u5B8C\u6210` };
37
+ case "error":
38
+ return { template: "red", title: `\u26A0\uFE0F ${agentName} \u6267\u884C\u5931\u8D25` };
39
+ case "interrupted":
40
+ return { template: "grey", title: `\u23F9 ${agentName} \u5DF2\u4E2D\u65AD` };
41
+ case "idle_timeout":
42
+ return {
43
+ template: "orange",
44
+ title: `\u23F1 ${agentName} \u5DF2\u8D85\u65F6\uFF08${state.idleTimeoutMinutes ?? 0} \u5206\u949F\u65E0\u54CD\u5E94\uFF09`
45
+ };
46
+ default: {
47
+ const lead = spinnerFrame(now);
48
+ if (state.footer === "tool_running") {
49
+ return { template: "wathet", title: `${lead} ${agentName} \u6B63\u5728\u8C03\u7528\u5DE5\u5177` };
50
+ }
51
+ if (state.footer === "streaming") {
52
+ return { template: "indigo", title: `${lead} ${agentName} \u6B63\u5728\u56DE\u590D` };
53
+ }
54
+ return { template: "blue", title: `${lead} ${agentName} \u6B63\u5728\u601D\u8003` };
55
+ }
56
+ }
57
+ }
58
+ var TOOL_COLOR_RULES = [
59
+ { match: /^(bash|shell)$/i, color: "orange" },
60
+ { match: /^(read|write|edit|multiedit|notebookedit)$/i, color: "blue" },
61
+ { match: /^(grep|glob|ast_grep_search|search)$/i, color: "turquoise" },
62
+ { match: /^(webfetch|websearch)$/i, color: "wathet" },
63
+ { match: /^(task|agent)$/i, color: "purple" },
64
+ { match: /^skill$/i, color: "violet" }
65
+ ];
66
+ function toolBorderColor(tool) {
67
+ if (tool.status === "error") return "red";
68
+ for (const rule of TOOL_COLOR_RULES) {
69
+ if (rule.match.test(tool.name)) return rule.color;
70
+ }
71
+ return "grey";
72
+ }
73
+
74
+ // src/card/tool-render.ts
75
+ var HEADER_SUMMARY_MAX = 80;
76
+ var BODY_FIELD_MAX = 600;
77
+ var OUTPUT_MAX = 1200;
78
+ var BODY_TOTAL_MAX = 2500;
79
+ function toolHeaderText(tool) {
80
+ const icon = tool.status === "done" ? "\u2705" : tool.status === "error" ? "\u274C" : "\u23F3";
81
+ const summary = pickHeaderSummary(tool) || summarizeInput(tool.name, tool.input);
82
+ return summary ? `${icon} **${tool.name}** \u2014 ${summary}` : `${icon} **${tool.name}**`;
83
+ }
84
+ function pickHeaderSummary(tool) {
85
+ if (!tool.title) return "";
86
+ const oneLine = tool.title.replace(/\s+/g, " ").trim();
87
+ return oneLine.length > HEADER_SUMMARY_MAX ? `${oneLine.slice(0, HEADER_SUMMARY_MAX)}\u2026` : oneLine;
88
+ }
89
+ function toolBodyMd(tool) {
90
+ const parts = [];
91
+ const inputMd = renderInput(tool);
92
+ if (inputMd) parts.push(inputMd);
93
+ if (tool.output) {
94
+ const truncated = truncate(tool.output, OUTPUT_MAX);
95
+ if (tool.status === "error") {
96
+ parts.push(`**Error**
97
+ \`\`\`
98
+ ${truncated}
99
+ \`\`\``);
100
+ } else if (tool.name.toLowerCase() === "bash") {
101
+ parts.push(renderBashOutput(truncated));
102
+ } else {
103
+ parts.push(`**Output**
104
+ \`\`\`
105
+ ${truncated}
106
+ \`\`\``);
107
+ }
108
+ } else if (tool.status === "running") {
109
+ parts.push("_\u8FD0\u884C\u4E2D\u2026_");
110
+ }
111
+ const body = parts.join("\n\n");
112
+ if (body.length <= BODY_TOTAL_MAX) return body;
113
+ return `${body.slice(0, BODY_TOTAL_MAX)}\u2026
114
+
115
+ _\uFF08body \u5DF2\u622A\u65AD,\u5B8C\u6574\u5185\u5BB9\u67E5 \`/doctor\` \u6216\u65E5\u5FD7\uFF09_`;
116
+ }
117
+ function summarizeInput(name, input) {
118
+ if (!input || typeof input !== "object") return "";
119
+ const rec = input;
120
+ const pick = (keys, max = HEADER_SUMMARY_MAX) => {
121
+ const list = typeof keys === "string" ? [keys] : keys;
122
+ for (const key of list) {
123
+ const v = rec[key];
124
+ if (typeof v !== "string" || !v) continue;
125
+ const oneLine = v.replace(/\s+/g, " ").trim();
126
+ return oneLine.length > max ? `${oneLine.slice(0, max)}\u2026` : oneLine;
127
+ }
128
+ return "";
129
+ };
130
+ switch (name.toLowerCase()) {
131
+ case "bash":
132
+ return pick("command");
133
+ case "read":
134
+ case "edit":
135
+ case "write":
136
+ case "notebookedit":
137
+ return shortenPath(pick(["file_path", "filePath", "path"]));
138
+ case "grep":
139
+ case "ast_grep_search": {
140
+ const pat = pick("pattern", 40);
141
+ const path = pick(["path", "paths"], 30);
142
+ return path ? `${pat} in ${shortenPath(path)}` : pat;
143
+ }
144
+ case "glob":
145
+ return pick("pattern");
146
+ case "webfetch":
147
+ return pick("url");
148
+ case "websearch":
149
+ return pick("query", 60);
150
+ case "agent":
151
+ case "task":
152
+ return pick("description") || pick(["subagent_type", "subagentType"]);
153
+ case "skill":
154
+ return pick(["skill", "name", "description"]);
155
+ case "todowrite":
156
+ return summarizeTodos(rec.todos);
157
+ default:
158
+ return pick("command") || pick(["file_path", "filePath", "path"]) || pick("query") || pick("url") || pick("description");
159
+ }
160
+ }
161
+ function summarizeTodos(value) {
162
+ if (!Array.isArray(value)) return "";
163
+ const total = value.length;
164
+ if (total === 0) return "";
165
+ const inProgress = value.find(
166
+ (t) => t && typeof t === "object" && t.status === "in_progress"
167
+ );
168
+ const current = inProgress && typeof inProgress.content === "string" ? inProgress.content : "";
169
+ const head = current ? current.replace(/\s+/g, " ").trim() : "";
170
+ const headTrim = head.length > 50 ? `${head.slice(0, 50)}\u2026` : head;
171
+ return headTrim ? `${headTrim} (${total} todos)` : `${total} todos`;
172
+ }
173
+ function renderInput(tool) {
174
+ const input = tool.input;
175
+ if (!input || typeof input !== "object") return "";
176
+ const rec = input;
177
+ const str = (keys) => {
178
+ const list = typeof keys === "string" ? [keys] : keys;
179
+ for (const key of list) {
180
+ const v = rec[key];
181
+ if (typeof v === "string" && v) return v;
182
+ }
183
+ return "";
184
+ };
185
+ switch (tool.name.toLowerCase()) {
186
+ case "bash": {
187
+ const cmd = str("command");
188
+ return cmd ? `**Command**
189
+ \`\`\`bash
190
+ ${truncate(cmd, BODY_FIELD_MAX)}
191
+ \`\`\`` : "";
192
+ }
193
+ case "read":
194
+ case "edit":
195
+ case "write":
196
+ case "notebookedit": {
197
+ const fp = str(["file_path", "filePath", "path"]);
198
+ return fp ? `**File** \`${fp}\`` : "";
199
+ }
200
+ case "grep":
201
+ case "ast_grep_search": {
202
+ const lines = [];
203
+ const pat = str("pattern");
204
+ const path = str(["path", "paths"]);
205
+ if (pat) lines.push(`**Pattern** \`${pat}\``);
206
+ if (path) lines.push(`**Path** \`${path}\``);
207
+ return lines.join("\n");
208
+ }
209
+ case "webfetch":
210
+ return str("url") ? `**URL** ${str("url")}` : "";
211
+ case "websearch":
212
+ return str("query") ? `**Query** \`${truncate(str("query"), BODY_FIELD_MAX)}\`` : "";
213
+ case "todowrite": {
214
+ const todos = rec.todos;
215
+ if (!Array.isArray(todos) || todos.length === 0) return "";
216
+ const lines = todos.map((t) => {
217
+ if (!t || typeof t !== "object") return "- ?";
218
+ const row = t;
219
+ const status = typeof row.status === "string" ? row.status : "pending";
220
+ const content = typeof row.content === "string" ? row.content : "";
221
+ const mark = status === "completed" ? "\u2705" : status === "in_progress" ? "\u25B6" : "\u2610";
222
+ return `${mark} ${content}`;
223
+ });
224
+ return `**Todos**
225
+ ${truncate(lines.join("\n"), BODY_FIELD_MAX)}`;
226
+ }
227
+ default:
228
+ return "";
229
+ }
230
+ }
231
+ function renderBashOutput(out) {
232
+ return `**Output**
233
+ \`\`\`
234
+ ${out}
235
+ \`\`\``;
236
+ }
237
+ function shortenPath(p) {
238
+ return p;
239
+ }
240
+ function truncate(s, max) {
241
+ return s.length > max ? `${s.slice(0, max)}\u2026` : s;
242
+ }
243
+
244
+ // src/card/run-renderer.ts
245
+ var REASONING_MAX = 1500;
246
+ var COLLAPSE_TOOL_THRESHOLD = 3;
247
+ function renderCard(state, options = {}) {
248
+ const now = options.now ?? Date.now();
249
+ const elements = [];
250
+ const header = cardHeader(state, options.agentName, now);
251
+ if (options.headerBanner) {
252
+ elements.push(noteMd(options.headerBanner));
253
+ }
254
+ if (state.reasoning.content) {
255
+ elements.push(reasoningPanel(state.reasoning.content, state.reasoning.active));
256
+ }
257
+ for (const group of groupBlocks(state.blocks)) {
258
+ if (group.kind === "text") {
259
+ if (group.content.trim()) {
260
+ elements.push(markdown(group.content));
261
+ }
262
+ } else {
263
+ elements.push(...renderToolGroup(group.tools, state.terminal !== "running"));
264
+ }
265
+ }
266
+ if (state.terminal === "interrupted") {
267
+ elements.push(noteMd("_\u23F9 \u5DF2\u88AB\u4E2D\u65AD_"));
268
+ } else if (state.terminal === "idle_timeout") {
269
+ const mins = state.idleTimeoutMinutes ?? 0;
270
+ elements.push(noteMd(`_\u23F1 ${mins} \u5206\u949F\u65E0\u54CD\u5E94,\u5DF2\u81EA\u52A8\u7EC8\u6B62_`));
271
+ } else if (state.terminal === "error" && state.errorMsg) {
272
+ elements.push(noteMd(`\u26A0\uFE0F agent \u5931\u8D25\uFF1A${state.errorMsg}`));
273
+ } else if (state.terminal === "done" && elements.length === 0) {
274
+ elements.push(noteMd("_\uFF08\u672A\u8FD4\u56DE\u5185\u5BB9\uFF09_"));
275
+ }
276
+ elements.push(footerMd(state, options, now));
277
+ if (state.terminal === "running") {
278
+ elements.push(stopButton(options));
279
+ }
280
+ return deepMaskEmails({
281
+ schema: "2.0",
282
+ header: {
283
+ title: { tag: "plain_text", content: header.title },
284
+ template: header.template
285
+ },
286
+ config: {
287
+ streaming_mode: state.terminal === "running",
288
+ summary: { content: summaryText(state) }
289
+ },
290
+ body: { elements }
291
+ });
292
+ }
293
+ function* groupBlocks(blocks) {
294
+ let toolBuf = [];
295
+ for (const b of blocks) {
296
+ if (b.kind === "tool") {
297
+ toolBuf.push(b.tool);
298
+ } else {
299
+ if (toolBuf.length > 0) {
300
+ yield { kind: "tools", tools: toolBuf };
301
+ toolBuf = [];
302
+ }
303
+ yield { kind: "text", content: b.content };
304
+ }
305
+ }
306
+ if (toolBuf.length > 0) yield { kind: "tools", tools: toolBuf };
307
+ }
308
+ function renderToolGroup(tools, finalized) {
309
+ if (tools.length === 0) return [];
310
+ if (tools.length < COLLAPSE_TOOL_THRESHOLD) {
311
+ return tools.map((t) => toolPanel(t, false));
312
+ }
313
+ if (finalized) {
314
+ return [collapsedToolSummary(tools, true)];
315
+ }
316
+ const prior = tools.slice(0, -1);
317
+ const latest = tools[tools.length - 1];
318
+ const out = [];
319
+ if (prior.length > 0) out.push(collapsedToolSummary(prior, false));
320
+ if (latest) out.push(toolPanel(latest, true));
321
+ return out;
322
+ }
323
+ function reasoningPanel(content, active2) {
324
+ const title = active2 ? "\u{1F9E0} **\u601D\u8003\u4E2D**" : "\u{1F9E0} **\u601D\u8003\u5B8C\u6210\uFF0C\u70B9\u51FB\u67E5\u770B**";
325
+ return collapsiblePanel({
326
+ title,
327
+ expanded: active2,
328
+ border: "grey",
329
+ body: truncate2(content, REASONING_MAX)
330
+ });
331
+ }
332
+ function toolPanel(tool, expanded) {
333
+ return collapsiblePanel({
334
+ title: toolHeaderText(tool),
335
+ expanded,
336
+ border: toolBorderColor(tool),
337
+ body: toolBodyMd(tool) || "_\u65E0\u8F93\u51FA_"
338
+ });
339
+ }
340
+ function collapsedToolSummary(tools, finalized) {
341
+ const suffix = finalized ? "\uFF08\u5DF2\u7ED3\u675F\uFF09" : "";
342
+ const title = `\u2615 **${tools.length} \u4E2A\u5DE5\u5177\u8C03\u7528${suffix}**`;
343
+ const headerList = tools.map((t) => `- ${toolHeaderText(t)}`).join("\n");
344
+ return {
345
+ tag: "collapsible_panel",
346
+ expanded: false,
347
+ header: panelHeader(title),
348
+ border: { color: "blue", corner_radius: "5px" },
349
+ vertical_spacing: "8px",
350
+ padding: "8px 8px 8px 8px",
351
+ elements: [{ tag: "markdown", content: headerList, text_size: "notation" }]
352
+ };
353
+ }
354
+ function collapsiblePanel(opts) {
355
+ return {
356
+ tag: "collapsible_panel",
357
+ expanded: opts.expanded,
358
+ header: panelHeader(opts.title),
359
+ border: { color: opts.border, corner_radius: "5px" },
360
+ vertical_spacing: "8px",
361
+ padding: "8px 8px 8px 8px",
362
+ elements: [{ tag: "markdown", content: opts.body, text_size: "notation" }]
363
+ };
364
+ }
365
+ function panelHeader(titleMd) {
366
+ return {
367
+ title: { tag: "markdown", content: titleMd },
368
+ vertical_align: "center",
369
+ icon: { tag: "standard_icon", token: "down-small-ccm_outlined", size: "16px 16px" },
370
+ icon_position: "follow_text",
371
+ icon_expanded_angle: -180
372
+ };
373
+ }
374
+ function markdown(content) {
375
+ return { tag: "markdown", content };
376
+ }
377
+ function noteMd(content) {
378
+ return { tag: "markdown", content, text_size: "notation" };
379
+ }
380
+ function stopButton(options) {
381
+ const value = { cmd: "stop" };
382
+ if (options.signCallback) {
383
+ value.__bridge_cb = true;
384
+ value.bridge_token = options.signCallback("stop");
385
+ }
386
+ return {
387
+ tag: "button",
388
+ text: { tag: "plain_text", content: "\u23F9 \u7EC8\u6B62" },
389
+ type: "danger",
390
+ behaviors: [{ type: "callback", value }]
391
+ };
392
+ }
393
+ function summaryText(state) {
394
+ if (state.terminal === "interrupted") return "\u5DF2\u4E2D\u65AD";
395
+ if (state.terminal === "idle_timeout") return "\u5DF2\u8D85\u65F6";
396
+ if (state.terminal === "error") return "\u51FA\u9519";
397
+ if (state.terminal === "done") return "\u5DF2\u5B8C\u6210";
398
+ if (state.footer === "tool_running") return "\u6B63\u5728\u8C03\u7528\u5DE5\u5177";
399
+ if (state.footer === "streaming") return "\u6B63\u5728\u8F93\u51FA";
400
+ return "\u601D\u8003\u4E2D";
401
+ }
402
+ function footerMd(state, options, now) {
403
+ const chips = [`\`${summaryText(state)}\``];
404
+ const elapsed = elapsedText(options, now);
405
+ if (elapsed) chips.push(`\`\u23F1 ${elapsed}\``);
406
+ const toolCount = state.blocks.filter((b) => b.kind === "tool").length;
407
+ if (toolCount > 0) chips.push(`\`\u{1F527} ${toolCount} \u6B21\u5DE5\u5177\``);
408
+ if (state.model) chips.push(`\`\u{1F9E9} ${truncate2(state.model, 48)}\``);
409
+ const lines = [chips.join(" \xB7 ")];
410
+ const usage = usageText(state);
411
+ if (usage) lines.push(usage);
412
+ return noteMd(lines.join("\n"));
413
+ }
414
+ function elapsedText(options, now) {
415
+ if (options.startedAt === void 0) return null;
416
+ const delta = now - options.startedAt;
417
+ if (!Number.isFinite(delta)) return null;
418
+ return formatDuration(Math.max(0, delta));
419
+ }
420
+ function formatDuration(ms) {
421
+ const sec = Math.round(ms / 100) / 10;
422
+ if (sec < 60) return Number.isInteger(sec) ? `${sec}s` : `${sec.toFixed(1)}s`;
423
+ const wholeSec = Math.round(sec);
424
+ const minutes = Math.floor(wholeSec / 60);
425
+ const seconds = wholeSec % 60;
426
+ if (minutes < 60) return seconds === 0 ? `${minutes}m` : `${minutes}m${seconds}s`;
427
+ const hours = Math.floor(minutes / 60);
428
+ const remMinutes = minutes % 60;
429
+ return remMinutes === 0 ? `${hours}h` : `${hours}h${remMinutes}m`;
430
+ }
431
+ function usageText(state) {
432
+ const usage = state.usage;
433
+ if (!usage) return null;
434
+ const parts = [];
435
+ if (usage.inputTokens !== void 0 && usage.outputTokens !== void 0) {
436
+ parts.push(`\u2191 ${usage.inputTokens} \u2193 ${usage.outputTokens}`);
437
+ } else if (usage.inputTokens !== void 0) {
438
+ parts.push(`\u2191 ${usage.inputTokens}`);
439
+ } else if (usage.outputTokens !== void 0) {
440
+ parts.push(`\u2193 ${usage.outputTokens}`);
441
+ }
442
+ if (usage.cachedInputTokens !== void 0) parts.push(`\u7F13\u5B58 ${usage.cachedInputTokens}`);
443
+ if (usage.costUsd !== void 0) parts.push(`$${usage.costUsd.toFixed(4)}`);
444
+ if (parts.length === 0) return null;
445
+ return parts.map((part) => `\`${part}\``).join(" \xB7 ");
446
+ }
447
+ function truncate2(s, max) {
448
+ return s.length > max ? `${s.slice(0, max)}\u2026` : s;
449
+ }
450
+
451
+ // src/card/text-renderer.ts
452
+ function renderText(state) {
453
+ const parts = [];
454
+ for (const block of state.blocks) {
455
+ const piece = renderBlock(block);
456
+ if (piece) parts.push(piece);
457
+ }
458
+ if (state.terminal === "interrupted") {
459
+ parts.push("_\u23F9 \u5DF2\u88AB\u4E2D\u65AD_");
460
+ } else if (state.terminal === "idle_timeout") {
461
+ const mins = state.idleTimeoutMinutes ?? 0;
462
+ parts.push(`_\u23F1 ${mins} \u5206\u949F\u65E0\u54CD\u5E94,\u5DF2\u81EA\u52A8\u7EC8\u6B62_`);
463
+ } else if (state.terminal === "error" && state.errorMsg) {
464
+ parts.push(`\u26A0\uFE0F agent \u5931\u8D25:${state.errorMsg}`);
465
+ } else if (state.terminal === "running" && state.footer) {
466
+ parts.push(footerLine(state.footer));
467
+ }
468
+ return maskEmails(parts.join("\n\n"));
469
+ }
470
+ function renderBlock(block) {
471
+ if (block.kind === "text") {
472
+ return block.content.trim();
473
+ }
474
+ return toolLine(block.tool);
475
+ }
476
+ function toolLine(tool) {
477
+ return `> ${toolHeaderText(tool)}`;
478
+ }
479
+ function footerLine(status) {
480
+ if (status === "thinking") return "_\u{1F9E0} \u6B63\u5728\u601D\u8003\u2026_";
481
+ if (status === "tool_running") return "_\u{1F9F0} \u6B63\u5728\u8C03\u7528\u5DE5\u5177\u2026_";
482
+ return "_\u270D\uFE0F \u6B63\u5728\u8F93\u51FA\u2026_";
483
+ }
484
+
485
+ // src/card/run-state.ts
486
+ var initialState = {
487
+ blocks: [],
488
+ reasoning: { content: "", active: false },
489
+ footer: "thinking",
490
+ terminal: "running"
491
+ };
492
+ function closeStreamingText(blocks) {
493
+ return blocks.map(
494
+ (b) => b.kind === "text" && b.streaming ? { ...b, streaming: false } : b
495
+ );
496
+ }
497
+ function reduce(state, evt) {
498
+ switch (evt.type) {
499
+ case "text": {
500
+ const last = state.blocks[state.blocks.length - 1];
501
+ if (last && last.kind === "text" && last.streaming) {
502
+ const next = { ...last, content: last.content + evt.delta };
503
+ return {
504
+ ...state,
505
+ blocks: [...state.blocks.slice(0, -1), next],
506
+ reasoning: { ...state.reasoning, active: false },
507
+ footer: "streaming"
508
+ };
509
+ }
510
+ return {
511
+ ...state,
512
+ blocks: [...state.blocks, { kind: "text", content: evt.delta, streaming: true }],
513
+ reasoning: { ...state.reasoning, active: false },
514
+ footer: "streaming"
515
+ };
516
+ }
517
+ case "final_text":
518
+ return { ...state, finalText: evt.content };
519
+ case "thinking": {
520
+ return {
521
+ ...state,
522
+ reasoning: { content: state.reasoning.content + evt.delta, active: true },
523
+ footer: "thinking"
524
+ };
525
+ }
526
+ case "tool_use": {
527
+ const existingIdx = state.blocks.findIndex(
528
+ (b) => b.kind === "tool" && b.tool.id === evt.id
529
+ );
530
+ if (existingIdx >= 0) {
531
+ const existing = state.blocks[existingIdx];
532
+ const updated = {
533
+ ...existing.tool,
534
+ name: evt.name,
535
+ input: evt.input,
536
+ ...evt.title !== void 0 ? { title: evt.title } : {}
537
+ };
538
+ const blocks = [...state.blocks];
539
+ blocks[existingIdx] = { kind: "tool", tool: updated };
540
+ return { ...state, blocks };
541
+ }
542
+ const tool = {
543
+ id: evt.id,
544
+ name: evt.name,
545
+ input: evt.input,
546
+ status: "running",
547
+ ...evt.title !== void 0 ? { title: evt.title } : {}
548
+ };
549
+ return {
550
+ ...state,
551
+ blocks: [...closeStreamingText(state.blocks), { kind: "tool", tool }],
552
+ reasoning: { ...state.reasoning, active: false },
553
+ footer: "tool_running"
554
+ };
555
+ }
556
+ case "tool_result": {
557
+ const blocks = state.blocks.map((b) => {
558
+ if (b.kind !== "tool" || b.tool.id !== evt.id) return b;
559
+ return {
560
+ ...b,
561
+ tool: {
562
+ ...b.tool,
563
+ status: evt.isError ? "error" : "done",
564
+ output: evt.output
565
+ }
566
+ };
567
+ });
568
+ return { ...state, blocks };
569
+ }
570
+ case "error": {
571
+ const terminal = evt.terminationReason === "interrupted" ? "interrupted" : evt.terminationReason === "timeout" ? "idle_timeout" : "error";
572
+ return {
573
+ ...state,
574
+ terminal,
575
+ errorMsg: terminal === "error" ? evt.message : state.errorMsg,
576
+ footer: null
577
+ };
578
+ }
579
+ case "done": {
580
+ const terminal = evt.terminationReason === "interrupted" ? "interrupted" : evt.terminationReason === "timeout" ? "idle_timeout" : "done";
581
+ return {
582
+ ...state,
583
+ blocks: closeStreamingText(state.blocks),
584
+ reasoning: { ...state.reasoning, active: false },
585
+ terminal,
586
+ footer: null
587
+ };
588
+ }
589
+ case "system": {
590
+ return evt.model ? { ...state, model: evt.model } : state;
591
+ }
592
+ case "usage": {
593
+ return { ...state, usage: mergeUsage(state.usage, evt) };
594
+ }
595
+ default:
596
+ return state;
597
+ }
598
+ }
599
+ function markInterrupted(state) {
600
+ return {
601
+ ...state,
602
+ blocks: closeStreamingText(state.blocks),
603
+ reasoning: { ...state.reasoning, active: false },
604
+ terminal: "interrupted",
605
+ footer: null
606
+ };
607
+ }
608
+ function finalizeIfRunning(state) {
609
+ if (state.terminal !== "running") return state;
610
+ return {
611
+ ...state,
612
+ blocks: closeStreamingText(state.blocks),
613
+ reasoning: { ...state.reasoning, active: false },
614
+ terminal: "done",
615
+ footer: null
616
+ };
617
+ }
618
+ var USAGE_KEYS = [
619
+ "inputTokens",
620
+ "outputTokens",
621
+ "cachedInputTokens",
622
+ "reasoningOutputTokens",
623
+ "costUsd"
624
+ ];
625
+ function mergeUsage(prev, evt) {
626
+ const merged = { ...prev };
627
+ for (const key of USAGE_KEYS) {
628
+ const increment = evt[key];
629
+ if (typeof increment !== "number" || !Number.isFinite(increment)) continue;
630
+ merged[key] = (merged[key] ?? 0) + increment;
631
+ }
632
+ return merged;
633
+ }
634
+
635
+ // src/core/logger.ts
636
+ import { AsyncLocalStorage } from "async_hooks";
637
+ import { createWriteStream, mkdirSync } from "fs";
638
+ import { open, readdir, rm, stat } from "fs/promises";
639
+ import { join } from "path";
640
+
641
+ // src/core/telemetry.ts
642
+ var noop = {
643
+ emit() {
644
+ },
645
+ recordError() {
646
+ },
647
+ recordMetric() {
648
+ },
649
+ flush() {
650
+ },
651
+ close() {
652
+ }
653
+ };
654
+ var active = noop;
655
+ function telemetry() {
656
+ return active;
657
+ }
658
+
659
+ // src/core/logger.ts
660
+ var DEFAULT_RETENTION_DAYS = Math.max(
661
+ 1,
662
+ Number(process.env.LARK_CHANNEL_LOG_DAYS ?? 30) || 30
663
+ );
664
+ var als = new AsyncLocalStorage();
665
+ var RAW_PAYLOAD_KEYS = /* @__PURE__ */ new Set([
666
+ "prompt",
667
+ "stdout",
668
+ "stderr",
669
+ "env",
670
+ "environment",
671
+ "proxy"
672
+ ]);
673
+ var RESOURCE_ID_KEYS = /* @__PURE__ */ new Set(["fileKey", "sourceFileKey"]);
674
+ var ID_KEYS = /* @__PURE__ */ new Set([
675
+ "chatId",
676
+ "senderId",
677
+ "sender",
678
+ "openId",
679
+ "operatorId",
680
+ "userId",
681
+ "msgId",
682
+ "messageId",
683
+ "sourceMessageId",
684
+ "sessionId",
685
+ "threadId",
686
+ "docToken",
687
+ "fileToken",
688
+ "fileKey",
689
+ "sourceFileKey",
690
+ "commentId",
691
+ "rootCommentId",
692
+ "replyId",
693
+ "reactionId",
694
+ "scope",
695
+ "appId"
696
+ ]);
697
+ var MAX_LOG_STRING_CHARS = 4096;
698
+ var CREDENTIAL_JSON_FIELD_RE = /("(?:secret|app_secret|appSecret|token|access_token|tenant_access_token|app_access_token|authorization)"\s*:\s*")[^"]*(")/gi;
699
+ var ESCAPED_CREDENTIAL_JSON_FIELD_RE = /(\\\"(?:secret|app_secret|appSecret|token|access_token|tenant_access_token|app_access_token|authorization)\\\"\s*:\s*\\\")[^\\]*(\\\")/gi;
700
+ var RESOURCE_JSON_FIELD_RE = /("(?:fileKey|sourceFileKey|file_key|source_file_key|imageKey|image_key|mediaKey|media_key)"\s*:\s*")[^"]*(")/gi;
701
+ var ESCAPED_RESOURCE_JSON_FIELD_RE = /(\\\"(?:fileKey|sourceFileKey|file_key|source_file_key|imageKey|image_key|mediaKey|media_key)\\\"\s*:\s*\\\")[^\\]*(\\\")/gi;
702
+ var EXTERNAL_SANITIZE = { redactIds: true };
703
+ function sanitizeLogValue(key, value, options = EXTERNAL_SANITIZE) {
704
+ const normalizedKey = key.startsWith("_") ? key.slice(1) : key;
705
+ if (value === void 0) return void 0;
706
+ if (RAW_PAYLOAD_KEYS.has(normalizedKey)) return "[REDACTED]";
707
+ if (/token|secret|authorization/i.test(normalizedKey)) return "[REDACTED]";
708
+ if (/attachment.*path|media.*path|^(cwd|cwdRealpath|path|absPath)$/i.test(normalizedKey)) {
709
+ return "[REDACTED_PATH]";
710
+ }
711
+ if (RESOURCE_ID_KEYS.has(normalizedKey)) return "[REDACTED_RESOURCE]";
712
+ if (options.redactIds && ID_KEYS.has(normalizedKey)) return redactId(value);
713
+ if (Array.isArray(value)) {
714
+ return value.map((item) => sanitizeLogValue(key, item, options));
715
+ }
716
+ if (value && typeof value === "object") {
717
+ const nested = {};
718
+ for (const [nestedKey, nestedValue] of Object.entries(value)) {
719
+ nested[nestedKey] = sanitizeLogValue(nestedKey, nestedValue, options);
720
+ }
721
+ return nested;
722
+ }
723
+ if (typeof value === "string") {
724
+ const redacted = redactDiagnosticText(value);
725
+ if (redacted.length > MAX_LOG_STRING_CHARS) {
726
+ return `${redacted.slice(0, MAX_LOG_STRING_CHARS)}...[truncated]`;
727
+ }
728
+ return redacted;
729
+ }
730
+ return value;
731
+ }
732
+ function redactId(value) {
733
+ if (typeof value !== "string") return value;
734
+ if (value.length <= 6) return value;
735
+ return `...${value.slice(-6)}`;
736
+ }
737
+ function redactDiagnosticText(text) {
738
+ let out = redactJsonCredentialText(text);
739
+ out = redactResourceText(out);
740
+ out = out.replace(
741
+ /\b(Authorization\s*[:=]\s*Bearer\s+)[A-Za-z0-9._\-+/=]+/gi,
742
+ "$1[REDACTED]"
743
+ );
744
+ out = out.replace(/\b(Bearer\s+)[A-Za-z0-9._\-+/=]+/g, "$1[REDACTED]");
745
+ out = out.replace(
746
+ /\b(access_token|tenant_access_token|app_access_token|app_secret|appSecret|secret|token|doc_token|file_token|authorization)=([^&\s"',}]+)/gi,
747
+ "$1=[REDACTED]"
748
+ );
749
+ out = out.replace(
750
+ /(^|[\s"'=])((?:\/(?:Users|home|tmp|var|private|Volumes|opt|workspace|workspaces|mnt|app|srv|root|data)\/[^\s"',)]+))/g,
751
+ "$1[REDACTED_PATH]"
752
+ );
753
+ out = out.replace(/(^|[\s"'=])(~\/[^\s"',)]+)/g, "$1[REDACTED_PATH]");
754
+ out = out.replace(/[A-Za-z]:\\[^\s"',)]+/g, "[REDACTED_PATH]");
755
+ return out;
756
+ }
757
+ function redactJsonCredentialText(text) {
758
+ return text.replace(CREDENTIAL_JSON_FIELD_RE, "$1[REDACTED]$2").replace(ESCAPED_CREDENTIAL_JSON_FIELD_RE, "$1[REDACTED]$2");
759
+ }
760
+ function redactResourceText(text) {
761
+ return text.replace(RESOURCE_JSON_FIELD_RE, "$1[REDACTED_RESOURCE]$2").replace(ESCAPED_RESOURCE_JSON_FIELD_RE, "$1[REDACTED_RESOURCE]$2").replace(
762
+ /<\s*(?:file|image|img|audio|video|media|folder)\b[^>]*\bkey\s*=\s*["'][^"']+["'][^>]*>/gi,
763
+ "[REDACTED_RESOURCE]"
764
+ ).replace(/!?\[[^\]]*]\((?:file|img|image|media)_[^)]+\)/gi, "[REDACTED_RESOURCE]").replace(
765
+ /\b(?:file|img|image|media)_(?:v\d+_)?[A-Za-z0-9][A-Za-z0-9._-]{8,}\b/g,
766
+ "[REDACTED_RESOURCE]"
767
+ );
768
+ }
769
+ function reportMetric(name, value, tags) {
770
+ try {
771
+ telemetry().recordMetric(name, value, sanitizeMetricTags(tags));
772
+ } catch {
773
+ }
774
+ }
775
+ function reportError(err, ctx) {
776
+ try {
777
+ telemetry().recordError(sanitizeTelemetryError(err), sanitizeTelemetryContext(ctx));
778
+ } catch {
779
+ }
780
+ }
781
+ function sanitizeMetricTags(tags) {
782
+ if (!tags) return void 0;
783
+ const out = {};
784
+ for (const [key, value] of Object.entries(tags)) {
785
+ const sanitized = sanitizeLogValue(key, value);
786
+ out[key] = typeof sanitized === "string" ? sanitized : JSON.stringify(sanitized);
787
+ }
788
+ return out;
789
+ }
790
+ function sanitizeTelemetryContext(ctx) {
791
+ if (!ctx) return void 0;
792
+ const out = {};
793
+ for (const [key, value] of Object.entries(ctx)) {
794
+ out[key] = sanitizeLogValue(key, value);
795
+ }
796
+ return out;
797
+ }
798
+ function sanitizeTelemetryError(err) {
799
+ if (err instanceof Error) {
800
+ return {
801
+ name: err.name,
802
+ message: sanitizeLogValue("err", err.message),
803
+ ...err.stack ? { stack: sanitizeLogValue("stack", err.stack) } : {}
804
+ };
805
+ }
806
+ return sanitizeLogValue("err", err);
807
+ }
808
+ export {
809
+ finalizeIfRunning,
810
+ initialState,
811
+ markInterrupted,
812
+ reduce,
813
+ renderCard,
814
+ renderText,
815
+ reportError,
816
+ reportMetric
817
+ };