@saltpub/xiaojiu-agent-bridge 0.1.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,681 @@
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/tool-render.ts
18
+ var HEADER_SUMMARY_MAX = 80;
19
+ var BODY_FIELD_MAX = 600;
20
+ var OUTPUT_MAX = 1200;
21
+ var BODY_TOTAL_MAX = 2500;
22
+ var FILE_FIELDS = ["file_path", "path", "filePath"];
23
+ function toolHeaderText(tool) {
24
+ const name = displayToolName(tool.name);
25
+ const icon = tool.status === "done" ? "\u2705" : tool.status === "error" ? "\u274C" : "\u23F3";
26
+ const summary = summarizeInput(name, tool.input);
27
+ return summary ? `${icon} **${name}** \u2014 ${summary}` : `${icon} **${name}**`;
28
+ }
29
+ function toolBodyMd(tool) {
30
+ const name = displayToolName(tool.name);
31
+ const parts = [];
32
+ const inputMd = renderInput(name, tool.input);
33
+ if (inputMd) parts.push(inputMd);
34
+ if (tool.output) {
35
+ const truncated = truncate(tool.output, OUTPUT_MAX);
36
+ if (tool.status === "error") {
37
+ parts.push(`**Error**
38
+ ${markdownCodeBlock(truncated)}`);
39
+ } else if (name === "Bash") {
40
+ parts.push(renderBashOutput(truncated));
41
+ } else {
42
+ parts.push(`**Output**
43
+ ${markdownCodeBlock(truncated)}`);
44
+ }
45
+ } else if (tool.status === "running") {
46
+ parts.push("_\u8FD0\u884C\u4E2D\u2026_");
47
+ }
48
+ const body = parts.join("\n\n");
49
+ if (body.length <= BODY_TOTAL_MAX) return body;
50
+ const marker = "\u2026\n\n_\uFF08body \u5DF2\u622A\u65AD,\u5B8C\u6574\u5185\u5BB9\u67E5 `/doctor` \u6216\u65E5\u5FD7\uFF09_";
51
+ return `${body.slice(0, Math.max(0, BODY_TOTAL_MAX - marker.length))}${marker}`;
52
+ }
53
+ function markdownCodeBlock(content, language = "") {
54
+ const longestFence = Math.max(
55
+ 2,
56
+ ...Array.from(content.matchAll(/`+/g), (match) => match[0].length)
57
+ );
58
+ const fence = "`".repeat(longestFence + 1);
59
+ const body = content.endsWith("\n") ? content : `${content}
60
+ `;
61
+ return `${fence}${language}
62
+ ${body}${fence}`;
63
+ }
64
+ function displayToolName(name) {
65
+ const normalized = name.trim().toLowerCase().replace(/[\s-]+/g, "_");
66
+ switch (normalized) {
67
+ case "bash":
68
+ case "shell":
69
+ case "command":
70
+ case "command_execution":
71
+ return "Bash";
72
+ case "read":
73
+ return "Read";
74
+ case "write":
75
+ return "Write";
76
+ case "edit":
77
+ return "Edit";
78
+ case "notebook_edit":
79
+ case "notebookedit":
80
+ return "NotebookEdit";
81
+ case "grep":
82
+ case "rg":
83
+ return "Grep";
84
+ case "glob":
85
+ return "Glob";
86
+ case "web_fetch":
87
+ case "webfetch":
88
+ return "WebFetch";
89
+ case "web_search":
90
+ case "websearch":
91
+ return "WebSearch";
92
+ case "agent":
93
+ return "Agent";
94
+ case "task":
95
+ return "Task";
96
+ default:
97
+ return name;
98
+ }
99
+ }
100
+ function summarizeInput(name, input) {
101
+ if (input === null || input === void 0) return "";
102
+ const rec = input && typeof input === "object" ? input : void 0;
103
+ const pick = (keys, max = HEADER_SUMMARY_MAX) => rec ? firstString(rec, keys, max) : "";
104
+ switch (name) {
105
+ case "Bash":
106
+ return pick(["command"]);
107
+ case "Read":
108
+ case "Edit":
109
+ case "Write":
110
+ case "NotebookEdit":
111
+ return pick(FILE_FIELDS);
112
+ case "Grep": {
113
+ const pat = pick(["pattern"], 40);
114
+ const path = pick(["path"], 30);
115
+ return path ? `${pat} in ${path}` : pat;
116
+ }
117
+ case "Glob": {
118
+ const pattern = pick(["pattern"], 50);
119
+ const path = pick(["path"], 25);
120
+ return path ? `${pattern} in ${path}` : pattern;
121
+ }
122
+ case "WebFetch":
123
+ return pick(["url"]);
124
+ case "WebSearch":
125
+ return pick(["query"], 60);
126
+ case "Agent":
127
+ case "Task":
128
+ return pick(["description"]) || pick(["subagent_type"]);
129
+ default:
130
+ return summarizeUnknownInput(input);
131
+ }
132
+ }
133
+ function renderInput(name, input) {
134
+ if (!input || typeof input !== "object") return "";
135
+ const rec = input;
136
+ switch (name) {
137
+ case "Bash": {
138
+ const cmd = firstString(rec, ["command"], BODY_FIELD_MAX);
139
+ return cmd ? `**Command**
140
+ ${markdownCodeBlock(cmd, "bash")}` : "";
141
+ }
142
+ case "Read":
143
+ case "Edit":
144
+ case "Write":
145
+ case "NotebookEdit": {
146
+ const filePath = firstString(rec, FILE_FIELDS, BODY_FIELD_MAX);
147
+ return filePath ? `**File** \`${filePath}\`` : "";
148
+ }
149
+ case "Grep": {
150
+ const lines = [];
151
+ const pattern = firstString(rec, ["pattern"], BODY_FIELD_MAX);
152
+ const path = firstString(rec, ["path"], BODY_FIELD_MAX);
153
+ if (pattern) lines.push(`**Pattern** \`${pattern}\``);
154
+ if (path) lines.push(`**Path** \`${path}\``);
155
+ return lines.join("\n");
156
+ }
157
+ case "Glob": {
158
+ const lines = [];
159
+ const pattern = firstString(rec, ["pattern"], BODY_FIELD_MAX);
160
+ const path = firstString(rec, ["path"], BODY_FIELD_MAX);
161
+ if (pattern) lines.push(`**Pattern** \`${pattern}\``);
162
+ if (path) lines.push(`**Path** \`${path}\``);
163
+ return lines.join("\n");
164
+ }
165
+ case "WebFetch": {
166
+ const url = firstString(rec, ["url"], BODY_FIELD_MAX);
167
+ return url ? `**URL** ${url}` : "";
168
+ }
169
+ case "WebSearch": {
170
+ const query = firstString(rec, ["query"], BODY_FIELD_MAX);
171
+ return query ? `**Query** \`${query}\`` : "";
172
+ }
173
+ default:
174
+ return "";
175
+ }
176
+ }
177
+ function firstString(rec, keys, max) {
178
+ for (const key of keys) {
179
+ const value = rec[key];
180
+ if (typeof value !== "string") continue;
181
+ const oneLine = value.replace(/\s+/g, " ").trim();
182
+ if (!oneLine) continue;
183
+ return truncate(oneLine, max);
184
+ }
185
+ return "";
186
+ }
187
+ function summarizeUnknownInput(input) {
188
+ let serialized;
189
+ try {
190
+ const seen = /* @__PURE__ */ new WeakSet();
191
+ serialized = JSON.stringify(input, (_key, value) => {
192
+ if (typeof value === "bigint") return String(value);
193
+ if (!value || typeof value !== "object") return value;
194
+ if (seen.has(value)) return "[Circular]";
195
+ seen.add(value);
196
+ return value;
197
+ }) ?? "";
198
+ } catch {
199
+ return "";
200
+ }
201
+ return truncate(serialized.replace(/\s+/g, " ").trim(), HEADER_SUMMARY_MAX);
202
+ }
203
+ function renderBashOutput(out) {
204
+ return `**Output**
205
+ ${markdownCodeBlock(out)}`;
206
+ }
207
+ function truncate(s, max) {
208
+ return s.length > max ? `${s.slice(0, max)}\u2026` : s;
209
+ }
210
+
211
+ // src/card/xiaojiu-shell.ts
212
+ function xiaojiuCard(title, elements, options = {}) {
213
+ return {
214
+ config: {},
215
+ i18n_header: {
216
+ zh_cn: {
217
+ title: { tag: "plain_text", content: title },
218
+ ...options.subtitle ? { subtitle: { tag: "plain_text", content: options.subtitle } } : {},
219
+ ...options.template ? { template: options.template } : {}
220
+ }
221
+ },
222
+ i18n_elements: {
223
+ zh_cn: elements
224
+ }
225
+ };
226
+ }
227
+ function xiaojiuMarkdown(content) {
228
+ return { tag: "markdown", content };
229
+ }
230
+ function xiaojiuNote(content) {
231
+ return {
232
+ tag: "note",
233
+ elements: [{ tag: "plain_text", content }]
234
+ };
235
+ }
236
+ function xiaojiuStopButton(options = {}) {
237
+ const value = { cmd: "stop" };
238
+ if (options.signCallback) {
239
+ value.__bridge_cb = true;
240
+ value.bridge_token = options.signCallback("stop");
241
+ }
242
+ return xiaojiuAction([
243
+ {
244
+ tag: "button",
245
+ name: "stop",
246
+ text: { tag: "plain_text", content: "\u23F9 \u7EC8\u6B62" },
247
+ type: "danger",
248
+ ...options.disabled ? { disabled: true } : {},
249
+ value
250
+ }
251
+ ]);
252
+ }
253
+ function xiaojiuAction(actions) {
254
+ return { tag: "action", actions };
255
+ }
256
+
257
+ // src/card/run-renderer.ts
258
+ var REASONING_MAX = 1500;
259
+ var COLLAPSE_TOOL_THRESHOLD = 3;
260
+ function renderCard(state, options = {}) {
261
+ const elements = [];
262
+ if (state.reasoning.content) {
263
+ const heading = state.reasoning.active ? "\u{1F9E0} **\u601D\u8003\u4E2D**" : "\u{1F9E0} **\u601D\u8003\u5B8C\u6210**";
264
+ elements.push(sectionMd(heading, truncate2(state.reasoning.content, REASONING_MAX)));
265
+ }
266
+ for (const group of groupBlocks(state.blocks)) {
267
+ if (group.kind === "text") {
268
+ if (group.content.trim()) {
269
+ elements.push(xiaojiuMarkdown(group.content));
270
+ }
271
+ } else {
272
+ elements.push(...renderToolGroup(group.tools, state.terminal !== "running"));
273
+ }
274
+ }
275
+ if (state.terminal === "interrupted") {
276
+ elements.push(xiaojiuNote("\u5DF2\u4E2D\u65AD"));
277
+ } else if (state.terminal === "idle_timeout") {
278
+ const mins = state.idleTimeoutMinutes ?? 0;
279
+ elements.push(xiaojiuNote(`${mins} \u5206\u949F\u65E0\u54CD\u5E94,\u5DF2\u81EA\u52A8\u7EC8\u6B62`));
280
+ } else if (state.terminal === "error" && state.errorMsg) {
281
+ elements.push(xiaojiuNote(`agent \u5931\u8D25\uFF1A${state.errorMsg}`));
282
+ } else if (state.terminal === "done" && elements.length === 0) {
283
+ elements.push(xiaojiuNote("\u672A\u8FD4\u56DE\u5185\u5BB9"));
284
+ }
285
+ if (state.terminal === "running") {
286
+ if (state.footer) elements.push(xiaojiuMarkdown(footerStatusText(state.footer)));
287
+ elements.push(xiaojiuStopButton({ ...options, disabled: options.stopRequested }));
288
+ }
289
+ return deepMaskEmails(xiaojiuCard(summaryText(state), elements));
290
+ }
291
+ function* groupBlocks(blocks) {
292
+ let toolBuf = [];
293
+ for (const b of blocks) {
294
+ if (b.kind === "tool") {
295
+ toolBuf.push(b.tool);
296
+ } else {
297
+ if (toolBuf.length > 0) {
298
+ yield { kind: "tools", tools: toolBuf };
299
+ toolBuf = [];
300
+ }
301
+ yield { kind: "text", content: b.content };
302
+ }
303
+ }
304
+ if (toolBuf.length > 0) yield { kind: "tools", tools: toolBuf };
305
+ }
306
+ function renderToolGroup(tools, finalized) {
307
+ if (tools.length === 0) return [];
308
+ if (tools.length < COLLAPSE_TOOL_THRESHOLD) {
309
+ return tools.map((t) => toolSection(t));
310
+ }
311
+ if (finalized) {
312
+ return [collapsedToolSummary(tools, true)];
313
+ }
314
+ const prior = tools.slice(0, -1);
315
+ const latest = tools[tools.length - 1];
316
+ const out = [];
317
+ if (prior.length > 0) out.push(collapsedToolSummary(prior, false));
318
+ if (latest) out.push(toolSection(latest));
319
+ return out;
320
+ }
321
+ function collapsedToolSummary(tools, finalized) {
322
+ const suffix = finalized ? "\uFF08\u5DF2\u7ED3\u675F\uFF09" : "";
323
+ const title = `\u2615 **${tools.length} \u4E2A\u5DE5\u5177\u8C03\u7528${suffix}**`;
324
+ const headerList = tools.map((t) => `- ${toolHeaderText(t)}`).join("\n");
325
+ return sectionMd(title, headerList);
326
+ }
327
+ function toolSection(tool) {
328
+ return sectionMd(toolHeaderText(tool), toolBodyMd(tool) || "\u65E0\u8F93\u51FA");
329
+ }
330
+ function sectionMd(title, body) {
331
+ return xiaojiuMarkdown(`${title}
332
+
333
+ ${body}`);
334
+ }
335
+ function footerStatusText(status) {
336
+ if (status === "thinking") return "\u{1F9E0} \u6B63\u5728\u601D\u8003";
337
+ if (status === "tool_running") return "\u{1F9F0} \u6B63\u5728\u8C03\u7528\u5DE5\u5177";
338
+ return "\u270D\uFE0F \u6B63\u5728\u8F93\u51FA";
339
+ }
340
+ function summaryText(state) {
341
+ if (state.terminal === "interrupted") return "\u5DF2\u4E2D\u65AD";
342
+ if (state.terminal === "idle_timeout") return "\u5DF2\u8D85\u65F6";
343
+ if (state.terminal === "error") return "\u51FA\u9519";
344
+ if (state.terminal === "done") return "\u5DF2\u5B8C\u6210";
345
+ if (state.footer === "tool_running") return "\u6B63\u5728\u8C03\u7528\u5DE5\u5177";
346
+ if (state.footer === "streaming") return "\u6B63\u5728\u8F93\u51FA";
347
+ return "\u601D\u8003\u4E2D";
348
+ }
349
+ function truncate2(s, max) {
350
+ return s.length > max ? `${s.slice(0, max)}\u2026` : s;
351
+ }
352
+
353
+ // src/card/text-renderer.ts
354
+ function renderText(state) {
355
+ const parts = [];
356
+ for (const block of state.blocks) {
357
+ const piece = renderBlock(block);
358
+ if (piece) parts.push(piece);
359
+ }
360
+ if (state.terminal === "interrupted") {
361
+ parts.push("_\u23F9 \u5DF2\u88AB\u4E2D\u65AD_");
362
+ } else if (state.terminal === "idle_timeout") {
363
+ const mins = state.idleTimeoutMinutes ?? 0;
364
+ parts.push(`_\u23F1 ${mins} \u5206\u949F\u65E0\u54CD\u5E94,\u5DF2\u81EA\u52A8\u7EC8\u6B62_`);
365
+ } else if (state.terminal === "error" && state.errorMsg) {
366
+ parts.push(`\u26A0\uFE0F agent \u5931\u8D25:${state.errorMsg}`);
367
+ } else if (state.terminal === "running" && state.footer) {
368
+ parts.push(footerLine(state.footer));
369
+ }
370
+ return maskEmails(parts.join("\n\n"));
371
+ }
372
+ function renderBlock(block) {
373
+ if (block.kind === "text") {
374
+ return block.content.trim();
375
+ }
376
+ return toolLine(block.tool);
377
+ }
378
+ function toolLine(tool) {
379
+ return `> ${toolHeaderText(tool)}`;
380
+ }
381
+ function footerLine(status) {
382
+ if (status === "thinking") return "_\u{1F9E0} \u6B63\u5728\u601D\u8003\u2026_";
383
+ if (status === "tool_running") return "_\u{1F9F0} \u6B63\u5728\u8C03\u7528\u5DE5\u5177\u2026_";
384
+ return "_\u270D\uFE0F \u6B63\u5728\u8F93\u51FA\u2026_";
385
+ }
386
+
387
+ // src/card/run-state.ts
388
+ var initialState = {
389
+ blocks: [],
390
+ reasoning: { content: "", active: false },
391
+ footer: "thinking",
392
+ terminal: "running"
393
+ };
394
+ function closeStreamingText(blocks) {
395
+ return blocks.map(
396
+ (b) => b.kind === "text" && b.streaming ? { ...b, streaming: false } : b
397
+ );
398
+ }
399
+ function reduce(state, evt) {
400
+ switch (evt.type) {
401
+ case "text": {
402
+ const last = state.blocks[state.blocks.length - 1];
403
+ if (last && last.kind === "text" && last.streaming) {
404
+ const next = { ...last, content: last.content + evt.delta };
405
+ return {
406
+ ...state,
407
+ blocks: [...state.blocks.slice(0, -1), next],
408
+ reasoning: { ...state.reasoning, active: false },
409
+ footer: "streaming"
410
+ };
411
+ }
412
+ return {
413
+ ...state,
414
+ blocks: [...state.blocks, { kind: "text", content: evt.delta, streaming: true }],
415
+ reasoning: { ...state.reasoning, active: false },
416
+ footer: "streaming"
417
+ };
418
+ }
419
+ case "final_text":
420
+ return { ...state, finalText: evt.content };
421
+ case "thinking": {
422
+ return {
423
+ ...state,
424
+ reasoning: { content: state.reasoning.content + evt.delta, active: true },
425
+ footer: "thinking"
426
+ };
427
+ }
428
+ case "tool_use": {
429
+ const tool = {
430
+ id: evt.id,
431
+ name: evt.name,
432
+ input: evt.input,
433
+ status: "running"
434
+ };
435
+ return {
436
+ ...state,
437
+ blocks: [...closeStreamingText(state.blocks), { kind: "tool", tool }],
438
+ reasoning: { ...state.reasoning, active: false },
439
+ footer: "tool_running"
440
+ };
441
+ }
442
+ case "tool_result": {
443
+ const blocks = state.blocks.map((b) => {
444
+ if (b.kind !== "tool" || b.tool.id !== evt.id) return b;
445
+ return {
446
+ ...b,
447
+ tool: {
448
+ ...b.tool,
449
+ status: evt.isError ? "error" : "done",
450
+ output: evt.output
451
+ }
452
+ };
453
+ });
454
+ return { ...state, blocks };
455
+ }
456
+ case "error": {
457
+ const terminal = evt.terminationReason === "interrupted" ? "interrupted" : evt.terminationReason === "timeout" ? "idle_timeout" : "error";
458
+ return {
459
+ ...state,
460
+ terminal,
461
+ errorMsg: terminal === "error" ? evt.message : state.errorMsg,
462
+ footer: null
463
+ };
464
+ }
465
+ case "done": {
466
+ const terminal = evt.terminationReason === "interrupted" ? "interrupted" : evt.terminationReason === "timeout" ? "idle_timeout" : "done";
467
+ return {
468
+ ...state,
469
+ blocks: closeStreamingText(state.blocks),
470
+ reasoning: { ...state.reasoning, active: false },
471
+ terminal,
472
+ footer: null
473
+ };
474
+ }
475
+ default:
476
+ return state;
477
+ }
478
+ }
479
+ function markInterrupted(state) {
480
+ return {
481
+ ...state,
482
+ blocks: closeStreamingText(state.blocks),
483
+ reasoning: { ...state.reasoning, active: false },
484
+ terminal: "interrupted",
485
+ footer: null
486
+ };
487
+ }
488
+ function finalizeIfRunning(state) {
489
+ if (state.terminal !== "running") return state;
490
+ return {
491
+ ...state,
492
+ blocks: closeStreamingText(state.blocks),
493
+ reasoning: { ...state.reasoning, active: false },
494
+ terminal: "done",
495
+ footer: null
496
+ };
497
+ }
498
+
499
+ // src/core/logger.ts
500
+ import { AsyncLocalStorage } from "async_hooks";
501
+ import { createWriteStream, mkdirSync } from "fs";
502
+ import { open, readdir, rm, stat } from "fs/promises";
503
+ import { join } from "path";
504
+
505
+ // src/core/telemetry.ts
506
+ var noop = {
507
+ emit() {
508
+ },
509
+ recordError() {
510
+ },
511
+ recordMetric() {
512
+ },
513
+ flush() {
514
+ },
515
+ close() {
516
+ }
517
+ };
518
+ var active = noop;
519
+ function telemetry() {
520
+ return active;
521
+ }
522
+
523
+ // src/core/logger.ts
524
+ var DEFAULT_RETENTION_DAYS = Math.max(
525
+ 1,
526
+ Number(process.env.XIAOJIU_BRIDGE_LOG_DAYS ?? 30) || 30
527
+ );
528
+ var als = new AsyncLocalStorage();
529
+ var RAW_PAYLOAD_KEYS = /* @__PURE__ */ new Set([
530
+ "prompt",
531
+ "stdout",
532
+ "stderr",
533
+ "env",
534
+ "environment",
535
+ "proxy"
536
+ ]);
537
+ var RESOURCE_ID_KEYS = /* @__PURE__ */ new Set(["fileKey", "sourceFileKey"]);
538
+ var ID_KEYS = /* @__PURE__ */ new Set([
539
+ "chatId",
540
+ "senderId",
541
+ "sender",
542
+ "openId",
543
+ "operatorId",
544
+ "userId",
545
+ "msgId",
546
+ "messageId",
547
+ "sourceMessageId",
548
+ "sessionId",
549
+ "threadId",
550
+ "docToken",
551
+ "fileToken",
552
+ "fileKey",
553
+ "sourceFileKey",
554
+ "commentId",
555
+ "rootCommentId",
556
+ "replyId",
557
+ "reactionId",
558
+ "scope",
559
+ "appId"
560
+ ]);
561
+ var MAX_LOG_STRING_CHARS = 4096;
562
+ var CREDENTIAL_JSON_FIELD_RE = /("(?:secret|app_secret|appSecret|token|access_token|tenant_access_token|app_access_token|authorization)"\s*:\s*")[^"]*(")/gi;
563
+ var ESCAPED_CREDENTIAL_JSON_FIELD_RE = /(\\\"(?:secret|app_secret|appSecret|token|access_token|tenant_access_token|app_access_token|authorization)\\\"\s*:\s*\\\")[^\\]*(\\\")/gi;
564
+ var RESOURCE_JSON_FIELD_RE = /("(?:fileKey|sourceFileKey|file_key|source_file_key|imageKey|image_key|mediaKey|media_key)"\s*:\s*")[^"]*(")/gi;
565
+ var ESCAPED_RESOURCE_JSON_FIELD_RE = /(\\\"(?:fileKey|sourceFileKey|file_key|source_file_key|imageKey|image_key|mediaKey|media_key)\\\"\s*:\s*\\\")[^\\]*(\\\")/gi;
566
+ var EXTERNAL_SANITIZE = { redactIds: true };
567
+ function sanitizeLogValue(key, value, options = EXTERNAL_SANITIZE) {
568
+ const normalizedKey = key.startsWith("_") ? key.slice(1) : key;
569
+ if (value === void 0) return void 0;
570
+ if (RAW_PAYLOAD_KEYS.has(normalizedKey)) return "[REDACTED]";
571
+ if (/token|secret|authorization/i.test(normalizedKey)) return "[REDACTED]";
572
+ if (/attachment.*path|media.*path|^(cwd|cwdRealpath|path|absPath)$/i.test(normalizedKey)) {
573
+ return "[REDACTED_PATH]";
574
+ }
575
+ if (RESOURCE_ID_KEYS.has(normalizedKey)) return "[REDACTED_RESOURCE]";
576
+ if (options.redactIds && ID_KEYS.has(normalizedKey)) return redactId(value);
577
+ if (Array.isArray(value)) {
578
+ return value.map((item) => sanitizeLogValue(key, item, options));
579
+ }
580
+ if (value && typeof value === "object") {
581
+ const nested = {};
582
+ for (const [nestedKey, nestedValue] of Object.entries(value)) {
583
+ nested[nestedKey] = sanitizeLogValue(nestedKey, nestedValue, options);
584
+ }
585
+ return nested;
586
+ }
587
+ if (typeof value === "string") {
588
+ const redacted = redactDiagnosticText(value);
589
+ if (redacted.length > MAX_LOG_STRING_CHARS) {
590
+ return `${redacted.slice(0, MAX_LOG_STRING_CHARS)}...[truncated]`;
591
+ }
592
+ return redacted;
593
+ }
594
+ return value;
595
+ }
596
+ function redactId(value) {
597
+ if (typeof value !== "string") return value;
598
+ if (value.length <= 6) return value;
599
+ return `...${value.slice(-6)}`;
600
+ }
601
+ function redactDiagnosticText(text) {
602
+ let out = redactJsonCredentialText(text);
603
+ out = redactResourceText(out);
604
+ out = out.replace(
605
+ /\b(Authorization\s*[:=]\s*Bearer\s+)[A-Za-z0-9._\-+/=]+/gi,
606
+ "$1[REDACTED]"
607
+ );
608
+ out = out.replace(/\b(Bearer\s+)[A-Za-z0-9._\-+/=]+/g, "$1[REDACTED]");
609
+ out = out.replace(
610
+ /\b(access_token|tenant_access_token|app_access_token|app_secret|appSecret|secret|token|doc_token|file_token|authorization)=([^&\s"',}]+)/gi,
611
+ "$1=[REDACTED]"
612
+ );
613
+ out = out.replace(
614
+ /(^|[\s"'=])((?:\/(?:Users|home|tmp|var|private|Volumes|opt|workspace|workspaces|mnt|app|srv|root|data)\/[^\s"',)]+))/g,
615
+ "$1[REDACTED_PATH]"
616
+ );
617
+ out = out.replace(/(^|[\s"'=])(~\/[^\s"',)]+)/g, "$1[REDACTED_PATH]");
618
+ out = out.replace(/[A-Za-z]:\\[^\s"',)]+/g, "[REDACTED_PATH]");
619
+ return out;
620
+ }
621
+ function redactJsonCredentialText(text) {
622
+ return text.replace(CREDENTIAL_JSON_FIELD_RE, "$1[REDACTED]$2").replace(ESCAPED_CREDENTIAL_JSON_FIELD_RE, "$1[REDACTED]$2");
623
+ }
624
+ function redactResourceText(text) {
625
+ return text.replace(RESOURCE_JSON_FIELD_RE, "$1[REDACTED_RESOURCE]$2").replace(ESCAPED_RESOURCE_JSON_FIELD_RE, "$1[REDACTED_RESOURCE]$2").replace(
626
+ /<\s*(?:file|image|img|audio|video|media|folder)\b[^>]*\bkey\s*=\s*["'][^"']+["'][^>]*>/gi,
627
+ "[REDACTED_RESOURCE]"
628
+ ).replace(/!?\[[^\]]*]\((?:file|img|image|media)_[^)]+\)/gi, "[REDACTED_RESOURCE]").replace(
629
+ /\b(?:file|img|image|media)_(?:v\d+_)?[A-Za-z0-9][A-Za-z0-9._-]{8,}\b/g,
630
+ "[REDACTED_RESOURCE]"
631
+ );
632
+ }
633
+ function reportMetric(name, value, tags) {
634
+ try {
635
+ telemetry().recordMetric(name, value, sanitizeMetricTags(tags));
636
+ } catch {
637
+ }
638
+ }
639
+ function reportError(err, ctx) {
640
+ try {
641
+ telemetry().recordError(sanitizeTelemetryError(err), sanitizeTelemetryContext(ctx));
642
+ } catch {
643
+ }
644
+ }
645
+ function sanitizeMetricTags(tags) {
646
+ if (!tags) return void 0;
647
+ const out = {};
648
+ for (const [key, value] of Object.entries(tags)) {
649
+ const sanitized = sanitizeLogValue(key, value);
650
+ out[key] = typeof sanitized === "string" ? sanitized : JSON.stringify(sanitized);
651
+ }
652
+ return out;
653
+ }
654
+ function sanitizeTelemetryContext(ctx) {
655
+ if (!ctx) return void 0;
656
+ const out = {};
657
+ for (const [key, value] of Object.entries(ctx)) {
658
+ out[key] = sanitizeLogValue(key, value);
659
+ }
660
+ return out;
661
+ }
662
+ function sanitizeTelemetryError(err) {
663
+ if (err instanceof Error) {
664
+ return {
665
+ name: err.name,
666
+ message: sanitizeLogValue("err", err.message),
667
+ ...err.stack ? { stack: sanitizeLogValue("stack", err.stack) } : {}
668
+ };
669
+ }
670
+ return sanitizeLogValue("err", err);
671
+ }
672
+ export {
673
+ finalizeIfRunning,
674
+ initialState,
675
+ markInterrupted,
676
+ reduce,
677
+ renderCard,
678
+ renderText,
679
+ reportError,
680
+ reportMetric
681
+ };