@fathippo/fathippo-context-engine 0.1.1

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.
@@ -0,0 +1,659 @@
1
+ import { sanitizeCognitiveText, isShareEligible } from "./sanitize.js";
2
+ const CODING_KEYWORDS = [
3
+ "bug",
4
+ "error",
5
+ "fix",
6
+ "debug",
7
+ "implement",
8
+ "build",
9
+ "create",
10
+ "refactor",
11
+ "function",
12
+ "class",
13
+ "api",
14
+ "endpoint",
15
+ "database",
16
+ "query",
17
+ "test",
18
+ "deploy",
19
+ "config",
20
+ "install",
21
+ "code",
22
+ "script",
23
+ ];
24
+ const SUCCESS_KEYWORDS = ["fixed", "works", "working", "resolved", "passing", "tests pass", "done"];
25
+ const FAILURE_KEYWORDS = ["failed", "still broken", "not working", "error persists", "blocked", "stuck"];
26
+ export function shouldCaptureCodingTrace(messages) {
27
+ if (messages.length < 4) {
28
+ return false;
29
+ }
30
+ const combined = messages.map(getMessageText).join(" ").toLowerCase();
31
+ return CODING_KEYWORDS.some((keyword) => combined.includes(keyword));
32
+ }
33
+ export function buildStructuredTrace(params) {
34
+ if (!shouldCaptureCodingTrace(params.messages)) {
35
+ return null;
36
+ }
37
+ const firstUser = params.messages.find((message) => message.role === "user");
38
+ if (!firstUser) {
39
+ return null;
40
+ }
41
+ const problem = sanitizeCognitiveText(getMessageText(firstUser).slice(0, 600));
42
+ const reasoning = sanitizeCognitiveText(extractReasoning(params.messages));
43
+ if (!problem || reasoning.length < 40) {
44
+ return null;
45
+ }
46
+ const automatedSignals = extractAutomatedSignals(params.messages, params.toolsUsed ?? []);
47
+ const heuristicOutcome = detectHeuristicOutcome(params.messages);
48
+ const automatedOutcome = detectAutomatedOutcome(params.messages, automatedSignals);
49
+ const context = extractContext(params.messages, params.filesModified ?? [], automatedSignals);
50
+ const solution = heuristicOutcome === "success" ? sanitizeCognitiveText(extractLastAssistant(params.messages).slice(0, 1400)) : undefined;
51
+ const durationMs = Math.max(0, params.endTime - params.startTime);
52
+ if (durationMs < 10_000) {
53
+ return null;
54
+ }
55
+ const shareText = [problem, reasoning, solution ?? "", ...(context.errorMessages ?? [])].join("\n");
56
+ const languages = inferLanguages(params.filesModified ?? []);
57
+ const verificationCommands = extractVerificationCommands(automatedSignals.toolResults);
58
+ const retryCount = estimateRetryCount(automatedSignals.toolCalls, automatedSignals.toolResults);
59
+ const resolutionKind = determineResolutionKind(automatedSignals);
60
+ const diffSummary = buildDiffSummary(params.filesModified ?? [], languages);
61
+ return {
62
+ sessionId: params.sessionId,
63
+ type: detectProblemType(params.messages),
64
+ problem,
65
+ context,
66
+ reasoning,
67
+ approaches: summarizeApproaches(reasoning),
68
+ solution: solution || undefined,
69
+ outcome: automatedOutcome === "failed" ? "failed" : heuristicOutcome,
70
+ heuristicOutcome,
71
+ automatedOutcome,
72
+ automatedSignals: {
73
+ ...automatedSignals,
74
+ automatedOutcome: automatedOutcome ?? null,
75
+ errorMessages: context.errorMessages ?? [],
76
+ verificationCommands,
77
+ retryCount,
78
+ resolutionKind,
79
+ },
80
+ errorMessage: context.errorMessages?.[0],
81
+ toolsUsed: automatedSignals.toolsUsed,
82
+ toolCalls: automatedSignals.toolCalls,
83
+ toolResults: automatedSignals.toolResults,
84
+ verificationCommands,
85
+ retryCount,
86
+ repoSignals: {
87
+ filesModified: params.filesModified ?? [],
88
+ languages,
89
+ diffSummary,
90
+ workspaceRoot: params.workspaceRoot,
91
+ },
92
+ resolutionKind,
93
+ filesModified: params.filesModified ?? [],
94
+ durationMs,
95
+ sanitized: true,
96
+ sanitizedAt: new Date().toISOString(),
97
+ shareEligible: isShareEligible(shareText),
98
+ };
99
+ }
100
+ function inferLanguages(filesModified) {
101
+ const languages = new Set();
102
+ for (const file of filesModified) {
103
+ const extension = file.split(".").pop()?.toLowerCase();
104
+ if (extension === "ts" || extension === "tsx")
105
+ languages.add("typescript");
106
+ if (extension === "js" || extension === "jsx")
107
+ languages.add("javascript");
108
+ if (extension === "py")
109
+ languages.add("python");
110
+ if (extension === "go")
111
+ languages.add("go");
112
+ if (extension === "rs")
113
+ languages.add("rust");
114
+ if (extension === "java")
115
+ languages.add("java");
116
+ if (extension === "rb")
117
+ languages.add("ruby");
118
+ if (extension === "sql")
119
+ languages.add("sql");
120
+ if (extension === "md")
121
+ languages.add("markdown");
122
+ if (extension === "json")
123
+ languages.add("json");
124
+ if (extension === "yml" || extension === "yaml")
125
+ languages.add("yaml");
126
+ if (extension === "sh")
127
+ languages.add("shell");
128
+ }
129
+ return [...languages];
130
+ }
131
+ function buildDiffSummary(filesModified, languages) {
132
+ if (filesModified.length === 0) {
133
+ return "No file modifications detected";
134
+ }
135
+ const languageSummary = languages.length > 0 ? ` across ${languages.join(", ")}` : "";
136
+ return `${filesModified.length} files modified${languageSummary}`;
137
+ }
138
+ function extractVerificationCommands(toolResults) {
139
+ return toolResults
140
+ .filter((signal) => signal.success === true && (signal.category === "test" || signal.category === "build" || signal.category === "lint"))
141
+ .map((signal) => signal.command)
142
+ .filter((value) => Boolean(value))
143
+ .slice(0, 6);
144
+ }
145
+ function estimateRetryCount(toolCalls, toolResults) {
146
+ const commandCounts = new Map();
147
+ for (const signal of [...toolCalls, ...toolResults]) {
148
+ const key = signal.command ?? signal.toolName;
149
+ commandCounts.set(key, (commandCounts.get(key) ?? 0) + 1);
150
+ }
151
+ let retries = toolResults.filter((signal) => signal.success === false).length;
152
+ for (const count of commandCounts.values()) {
153
+ if (count > 1) {
154
+ retries += count - 1;
155
+ }
156
+ }
157
+ return retries;
158
+ }
159
+ function determineResolutionKind(automatedSignals) {
160
+ if (automatedSignals.testSignals.passed > 0) {
161
+ return "tests_passed";
162
+ }
163
+ if (automatedSignals.buildSignals.passed > 0) {
164
+ return "build_passed";
165
+ }
166
+ if (automatedSignals.lintSignals.passed > 0) {
167
+ return "lint_passed";
168
+ }
169
+ if (automatedSignals.commandsFailed > 0 ||
170
+ automatedSignals.testSignals.failed > 0 ||
171
+ automatedSignals.buildSignals.failed > 0 ||
172
+ automatedSignals.lintSignals.failed > 0) {
173
+ return "failed";
174
+ }
175
+ return "manual_only";
176
+ }
177
+ function detectProblemType(messages) {
178
+ const text = messages.map(getMessageText).join(" ").toLowerCase();
179
+ if (/debug|bug|error|fix|broken/.test(text))
180
+ return "debugging";
181
+ if (/refactor|cleanup|reorganize/.test(text))
182
+ return "refactoring";
183
+ if (/review|audit|inspect/.test(text))
184
+ return "reviewing";
185
+ if (/config|setup|install|deploy/.test(text))
186
+ return "configuring";
187
+ return "building";
188
+ }
189
+ function detectHeuristicOutcome(messages) {
190
+ const recent = messages.slice(-5).map(getMessageText).join(" ").toLowerCase();
191
+ const success = SUCCESS_KEYWORDS.filter((keyword) => recent.includes(keyword)).length;
192
+ const failure = FAILURE_KEYWORDS.filter((keyword) => recent.includes(keyword)).length;
193
+ if (success > failure && success > 0)
194
+ return "success";
195
+ if (failure > success && failure > 0)
196
+ return "failed";
197
+ return "partial";
198
+ }
199
+ function detectAutomatedOutcome(messages, automatedSignals) {
200
+ if (automatedSignals.commandsFailed > 0 ||
201
+ automatedSignals.testSignals.failed > 0 ||
202
+ automatedSignals.buildSignals.failed > 0 ||
203
+ automatedSignals.lintSignals.failed > 0 ||
204
+ automatedSignals.installSignals.failed > 0) {
205
+ if (automatedSignals.commandsSucceeded === 0 &&
206
+ automatedSignals.testSignals.passed === 0 &&
207
+ automatedSignals.buildSignals.passed === 0 &&
208
+ automatedSignals.lintSignals.passed === 0) {
209
+ return "failed";
210
+ }
211
+ }
212
+ if (automatedSignals.testSignals.passed > 0 ||
213
+ automatedSignals.buildSignals.passed > 0 ||
214
+ automatedSignals.lintSignals.passed > 0 ||
215
+ (automatedSignals.commandsSucceeded > 0 && automatedSignals.commandsFailed === 0)) {
216
+ if (automatedSignals.commandsFailed === 0 &&
217
+ automatedSignals.testSignals.failed === 0 &&
218
+ automatedSignals.buildSignals.failed === 0 &&
219
+ automatedSignals.lintSignals.failed === 0 &&
220
+ automatedSignals.installSignals.failed === 0) {
221
+ return "success";
222
+ }
223
+ }
224
+ const recent = messages.slice(-8).map(getMessageText).join(" ").toLowerCase();
225
+ if (/tests? pass|build succeeded|compiled successfully|all checks passed/.test(recent)) {
226
+ return "success";
227
+ }
228
+ if (/tests? failed|build failed|compilation failed|command failed|exit code [1-9]/.test(recent)) {
229
+ return "failed";
230
+ }
231
+ return undefined;
232
+ }
233
+ function extractReasoning(messages) {
234
+ const segments = [];
235
+ for (const message of messages) {
236
+ if (message.role !== "assistant") {
237
+ continue;
238
+ }
239
+ const content = message.content;
240
+ if (Array.isArray(content)) {
241
+ for (const block of content) {
242
+ if (typeof block === "object" && block !== null && "thinking" in block && typeof block.thinking === "string") {
243
+ segments.push(block.thinking);
244
+ }
245
+ }
246
+ }
247
+ }
248
+ if (segments.length === 0) {
249
+ return messages
250
+ .filter((message) => message.role === "assistant")
251
+ .map(getMessageText)
252
+ .filter(Boolean)
253
+ .join("\n\n")
254
+ .slice(0, 4000);
255
+ }
256
+ return segments.join("\n\n").slice(0, 4000);
257
+ }
258
+ function extractContext(messages, filesModified, automatedSignals) {
259
+ const text = messages.map(getMessageText).join(" ");
260
+ const techPatterns = {
261
+ typescript: /typescript|\.tsx?|tsc/i,
262
+ javascript: /javascript|\.jsx?|node/i,
263
+ react: /react|jsx|useState|useEffect/i,
264
+ nextjs: /next\.js|app router/i,
265
+ turso: /turso|libsql/i,
266
+ postgres: /postgres|psql|pg_/i,
267
+ python: /python|\.py|pip/i,
268
+ docker: /docker|container/i,
269
+ git: /\bgit\b|commit|branch|merge/i,
270
+ };
271
+ const technologies = Object.entries(techPatterns)
272
+ .filter(([, pattern]) => pattern.test(text))
273
+ .map(([technology]) => technology);
274
+ const errorMessages = Array.from(text.matchAll(/(?:TypeError|ReferenceError|SyntaxError|Error|Cannot\s+[^\n.]+)/gi), (match) => sanitizeCognitiveText(match[0].slice(0, 240))).slice(0, 5);
275
+ for (const toolError of automatedSignals.errorMessages) {
276
+ if (errorMessages.length >= 5) {
277
+ break;
278
+ }
279
+ if (!errorMessages.includes(toolError)) {
280
+ errorMessages.push(toolError);
281
+ }
282
+ }
283
+ const fileHints = filesModified.filter(Boolean);
284
+ if (fileHints.length > 0 && !technologies.includes("git")) {
285
+ technologies.push("git");
286
+ }
287
+ return {
288
+ technologies,
289
+ errorMessages: errorMessages.length > 0 ? errorMessages : undefined,
290
+ };
291
+ }
292
+ function extractAutomatedSignals(messages, fallbackToolsUsed) {
293
+ const toolsUsed = new Set(fallbackToolsUsed.filter(Boolean));
294
+ const toolUseById = new Map();
295
+ const toolCalls = [];
296
+ const toolResults = [];
297
+ const errorMessages = [];
298
+ let commandsSucceeded = 0;
299
+ let commandsFailed = 0;
300
+ let toolCallCount = 0;
301
+ let toolResultCount = 0;
302
+ const testSignals = { passed: 0, failed: 0 };
303
+ const buildSignals = { passed: 0, failed: 0 };
304
+ const lintSignals = { passed: 0, failed: 0 };
305
+ const installSignals = { passed: 0, failed: 0 };
306
+ const runSignals = { passed: 0, failed: 0 };
307
+ let hadToolErrors = false;
308
+ for (const message of messages) {
309
+ const rawMessage = message;
310
+ const messageToolName = readString(rawMessage.toolName) ?? readString(rawMessage.name);
311
+ if (messageToolName) {
312
+ toolsUsed.add(messageToolName);
313
+ }
314
+ if (isToolResultMessage(rawMessage)) {
315
+ toolResultCount += 1;
316
+ const signal = extractToolSignalFromContainer(rawMessage, messageToolName ?? "unknown", toolUseById);
317
+ if (signal) {
318
+ toolResults.push(signal);
319
+ toolsUsed.add(signal.toolName);
320
+ applySignalSummary(signal, {
321
+ testSignals,
322
+ buildSignals,
323
+ lintSignals,
324
+ installSignals,
325
+ runSignals,
326
+ errorMessages,
327
+ onCommandSuccess: () => {
328
+ commandsSucceeded += 1;
329
+ },
330
+ onCommandFailure: () => {
331
+ commandsFailed += 1;
332
+ hadToolErrors = true;
333
+ },
334
+ onToolError: () => {
335
+ hadToolErrors = true;
336
+ },
337
+ });
338
+ }
339
+ }
340
+ const content = rawMessage.content;
341
+ if (!Array.isArray(content)) {
342
+ continue;
343
+ }
344
+ for (const block of content) {
345
+ if (!block || typeof block !== "object") {
346
+ continue;
347
+ }
348
+ const rawBlock = block;
349
+ const blockType = readString(rawBlock.type);
350
+ if (blockType === "tool_use") {
351
+ toolCallCount += 1;
352
+ const toolName = readString(rawBlock.name) ?? messageToolName ?? "unknown";
353
+ const command = extractCommandText(rawBlock);
354
+ const toolUseId = readString(rawBlock.id) ?? readString(rawBlock.tool_use_id) ?? readString(rawBlock.toolUseId);
355
+ if (toolUseId) {
356
+ toolUseById.set(toolUseId, { toolName, command });
357
+ }
358
+ toolsUsed.add(toolName);
359
+ toolCalls.push({
360
+ toolName,
361
+ category: classifyToolSignal(toolName, command),
362
+ command,
363
+ });
364
+ }
365
+ if (blockType === "tool_result") {
366
+ toolResultCount += 1;
367
+ const signal = extractToolSignalFromContainer(rawBlock, messageToolName ?? "unknown", toolUseById);
368
+ if (signal) {
369
+ toolResults.push(signal);
370
+ toolsUsed.add(signal.toolName);
371
+ applySignalSummary(signal, {
372
+ testSignals,
373
+ buildSignals,
374
+ lintSignals,
375
+ installSignals,
376
+ runSignals,
377
+ errorMessages,
378
+ onCommandSuccess: () => {
379
+ commandsSucceeded += 1;
380
+ },
381
+ onCommandFailure: () => {
382
+ commandsFailed += 1;
383
+ hadToolErrors = true;
384
+ },
385
+ onToolError: () => {
386
+ hadToolErrors = true;
387
+ },
388
+ });
389
+ }
390
+ }
391
+ }
392
+ }
393
+ return {
394
+ toolsUsed: [...toolsUsed],
395
+ toolCallCount,
396
+ toolResultCount,
397
+ commandsSucceeded,
398
+ commandsFailed,
399
+ testSignals,
400
+ buildSignals,
401
+ lintSignals,
402
+ installSignals,
403
+ runSignals,
404
+ toolCalls: toolCalls.slice(0, 12),
405
+ toolResults: toolResults.slice(0, 12),
406
+ errorMessages: errorMessages.slice(0, 5),
407
+ strongestFailure: errorMessages[0],
408
+ strongestSuccess: summarizeStrongestSuccess(testSignals, buildSignals, lintSignals, installSignals, runSignals),
409
+ hadToolErrors,
410
+ };
411
+ }
412
+ function isToolResultMessage(raw) {
413
+ return raw.role === "toolResult" || raw.type === "toolResult" || raw.role === "tool";
414
+ }
415
+ function extractToolSignalFromContainer(raw, fallbackToolName, toolUseById) {
416
+ const toolUseId = readString(raw.tool_use_id) ?? readString(raw.toolUseId);
417
+ const toolRef = toolUseId ? toolUseById.get(toolUseId) : undefined;
418
+ const toolName = readString(raw.toolName) ??
419
+ readString(raw.name) ??
420
+ toolRef?.toolName ??
421
+ fallbackToolName;
422
+ const command = extractCommandText(raw) ?? toolRef?.command;
423
+ const outputText = readTextualPayload(raw.content) || readTextualPayload(raw.output) || readTextualPayload(raw.result);
424
+ const stderrText = readTextualPayload(raw.stderr) || readTextualPayload(raw.error);
425
+ const combinedText = [outputText, stderrText].filter(Boolean).join("\n").trim();
426
+ const exitCode = readNumber(raw.exitCode) ?? readNumber(raw.exit_code);
427
+ const explicitSuccess = readBoolean(raw.success);
428
+ const explicitError = readBoolean(raw.is_error);
429
+ const success = explicitSuccess ?? inferCommandSuccess(exitCode, explicitError, combinedText);
430
+ return {
431
+ toolName,
432
+ category: classifyToolSignal(toolName, command),
433
+ command,
434
+ success,
435
+ exitCode,
436
+ isError: explicitError ?? (success === false),
437
+ outputSnippet: outputText ? sanitizeCognitiveText(outputText.slice(0, 240)) : undefined,
438
+ errorSnippet: stderrText
439
+ ? sanitizeCognitiveText(stderrText.slice(0, 240))
440
+ : inferErrorSnippet(combinedText),
441
+ };
442
+ }
443
+ function applySignalSummary(signal, sinks) {
444
+ if (signal.success === true) {
445
+ sinks.onCommandSuccess();
446
+ }
447
+ if (signal.success === false) {
448
+ sinks.onCommandFailure();
449
+ }
450
+ if (signal.isError) {
451
+ sinks.onToolError();
452
+ }
453
+ if (signal.category === "test") {
454
+ if (signal.success === false) {
455
+ sinks.testSignals.failed += 1;
456
+ }
457
+ else if (signal.success === true) {
458
+ sinks.testSignals.passed += 1;
459
+ }
460
+ }
461
+ if (signal.category === "build") {
462
+ if (signal.success === false) {
463
+ sinks.buildSignals.failed += 1;
464
+ }
465
+ else if (signal.success === true) {
466
+ sinks.buildSignals.passed += 1;
467
+ }
468
+ }
469
+ if (signal.category === "lint") {
470
+ if (signal.success === false) {
471
+ sinks.lintSignals.failed += 1;
472
+ }
473
+ else if (signal.success === true) {
474
+ sinks.lintSignals.passed += 1;
475
+ }
476
+ }
477
+ if (signal.category === "install") {
478
+ if (signal.success === false) {
479
+ sinks.installSignals.failed += 1;
480
+ }
481
+ else if (signal.success === true) {
482
+ sinks.installSignals.passed += 1;
483
+ }
484
+ }
485
+ if (signal.category === "run") {
486
+ if (signal.success === false) {
487
+ sinks.runSignals.failed += 1;
488
+ }
489
+ else if (signal.success === true) {
490
+ sinks.runSignals.passed += 1;
491
+ }
492
+ }
493
+ const errorSnippet = signal.errorSnippet || inferErrorSnippet(signal.outputSnippet ?? "");
494
+ if (errorSnippet && !sinks.errorMessages.includes(errorSnippet) && sinks.errorMessages.length < 5) {
495
+ sinks.errorMessages.push(errorSnippet);
496
+ }
497
+ }
498
+ function summarizeStrongestSuccess(testSignals, buildSignals, lintSignals, installSignals, runSignals) {
499
+ if (testSignals.passed > 0) {
500
+ return "Tests passed";
501
+ }
502
+ if (buildSignals.passed > 0) {
503
+ return "Build succeeded";
504
+ }
505
+ if (lintSignals.passed > 0) {
506
+ return "Lint/checks passed";
507
+ }
508
+ if (installSignals.passed > 0) {
509
+ return "Dependencies installed successfully";
510
+ }
511
+ if (runSignals.passed > 0) {
512
+ return "Command completed successfully";
513
+ }
514
+ return undefined;
515
+ }
516
+ function classifyToolSignal(toolName, command) {
517
+ const text = `${toolName} ${command ?? ""}`.toLowerCase();
518
+ if (/test|jest|vitest|pytest|go test|cargo test|npm test|pnpm test|bun test/.test(text))
519
+ return "test";
520
+ if (/build|compile|tsc|next build|vite build|cargo build/.test(text))
521
+ return "build";
522
+ if (/lint|eslint|ruff|check|tsc --noemit/.test(text))
523
+ return "lint";
524
+ if (/install|npm i|npm install|pnpm install|yarn install|pip install|brew install/.test(text))
525
+ return "install";
526
+ if (/edit|write|apply_patch|create file|replace/.test(text))
527
+ return "edit";
528
+ if (/search|rg|grep|find|ls|cat|sed/.test(text))
529
+ return "search";
530
+ if (/run|node |python |bash |sh |zsh |npm run|pnpm |yarn /.test(text))
531
+ return "run";
532
+ return "unknown";
533
+ }
534
+ function extractCommandText(raw) {
535
+ const direct = readString(raw.command) ??
536
+ readString(raw.cmd) ??
537
+ readString(raw.input);
538
+ if (direct) {
539
+ return sanitizeCognitiveText(direct.slice(0, 240));
540
+ }
541
+ const input = raw.input;
542
+ if (input && typeof input === "object" && !Array.isArray(input)) {
543
+ const command = readString(input.command) ??
544
+ readString(input.cmd) ??
545
+ readString(input.shell_command);
546
+ if (command) {
547
+ return sanitizeCognitiveText(command.slice(0, 240));
548
+ }
549
+ }
550
+ return undefined;
551
+ }
552
+ function inferCommandSuccess(exitCode, explicitError, text) {
553
+ if (typeof explicitError === "boolean") {
554
+ return !explicitError;
555
+ }
556
+ if (typeof exitCode === "number") {
557
+ return exitCode === 0;
558
+ }
559
+ const lower = text.toLowerCase();
560
+ if (/all checks passed|tests? pass|build succeeded|compiled successfully|done in [\d.]+s/.test(lower)) {
561
+ return true;
562
+ }
563
+ if (/tests? failed|build failed|compilation failed|command failed|error:|exit code [1-9]/.test(lower)) {
564
+ return false;
565
+ }
566
+ return undefined;
567
+ }
568
+ function inferErrorSnippet(text) {
569
+ if (!text) {
570
+ return undefined;
571
+ }
572
+ const match = text.match(/(?:TypeError|ReferenceError|SyntaxError|Error|Cannot\s+[^\n.]+|Failed[^\n.]*)/i);
573
+ if (!match) {
574
+ return undefined;
575
+ }
576
+ return sanitizeCognitiveText(match[0].slice(0, 240));
577
+ }
578
+ function readTextualPayload(value) {
579
+ if (typeof value === "string") {
580
+ return value;
581
+ }
582
+ if (Array.isArray(value)) {
583
+ return value
584
+ .map((entry) => {
585
+ if (typeof entry === "string") {
586
+ return entry;
587
+ }
588
+ if (entry && typeof entry === "object") {
589
+ return (readString(entry.text) ??
590
+ readString(entry.content) ??
591
+ readString(entry.output) ??
592
+ "");
593
+ }
594
+ return "";
595
+ })
596
+ .filter(Boolean)
597
+ .join("\n");
598
+ }
599
+ if (value && typeof value === "object") {
600
+ return (readString(value.text) ??
601
+ readString(value.content) ??
602
+ readString(value.output) ??
603
+ readString(value.stderr) ??
604
+ readString(value.stdout) ??
605
+ "");
606
+ }
607
+ return "";
608
+ }
609
+ function readString(value) {
610
+ return typeof value === "string" && value.trim() ? value : undefined;
611
+ }
612
+ function readNumber(value) {
613
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
614
+ }
615
+ function readBoolean(value) {
616
+ return typeof value === "boolean" ? value : undefined;
617
+ }
618
+ function summarizeApproaches(reasoning) {
619
+ const parts = reasoning
620
+ .split(/\n+|\. /)
621
+ .map((part) => part.trim())
622
+ .filter((part) => part.length > 20)
623
+ .slice(0, 4);
624
+ return parts.map((part, index) => ({
625
+ description: part,
626
+ result: index === parts.length - 1 ? "worked" : "partial",
627
+ learnings: part.length > 140 ? part.slice(0, 140) : undefined,
628
+ }));
629
+ }
630
+ function extractLastAssistant(messages) {
631
+ const assistant = [...messages].reverse().find((message) => message.role === "assistant");
632
+ return assistant ? getMessageText(assistant) : "";
633
+ }
634
+ export function getMessageText(message) {
635
+ const content = message.content;
636
+ if (typeof content === "string") {
637
+ return content;
638
+ }
639
+ const text = message.text;
640
+ if (typeof text === "string") {
641
+ return text;
642
+ }
643
+ if (Array.isArray(content)) {
644
+ return content
645
+ .map((block) => {
646
+ if (typeof block === "string") {
647
+ return block;
648
+ }
649
+ if (block && typeof block === "object" && "text" in block && typeof block.text === "string") {
650
+ return block.text;
651
+ }
652
+ return "";
653
+ })
654
+ .filter(Boolean)
655
+ .join("\n");
656
+ }
657
+ return "";
658
+ }
659
+ //# sourceMappingURL=trace-capture.js.map