ubume 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.
Files changed (182) hide show
  1. package/README.md +88 -0
  2. package/bin/codexa-local-harness-bridge.js +3 -0
  3. package/bin/codexa.js +28 -0
  4. package/bin/ubume-local-harness-bridge.js +325 -0
  5. package/bin/ubume.js +398 -0
  6. package/package.json +66 -0
  7. package/src/app.tsx +5759 -0
  8. package/src/commands/handler.ts +889 -0
  9. package/src/config/appVersion.ts +69 -0
  10. package/src/config/buildInfo.ts +3 -0
  11. package/src/config/launchArgs.ts +196 -0
  12. package/src/config/layeredConfig.ts +853 -0
  13. package/src/config/legacyEnv.ts +16 -0
  14. package/src/config/persistence.ts +377 -0
  15. package/src/config/runtimeConfig.ts +558 -0
  16. package/src/config/settings.ts +405 -0
  17. package/src/config/toml-serialize.ts +109 -0
  18. package/src/config/trustStore.ts +84 -0
  19. package/src/config/updateCheckCache.ts +85 -0
  20. package/src/core/README.md +55 -0
  21. package/src/core/agent/loop.ts +464 -0
  22. package/src/core/agent/protocol.ts +345 -0
  23. package/src/core/agent/tools.ts +423 -0
  24. package/src/core/auth/codexAuth.ts +359 -0
  25. package/src/core/codex/codexExecArgs.ts +166 -0
  26. package/src/core/codex/codexLaunch.ts +163 -0
  27. package/src/core/codex/codexPrompt.ts +429 -0
  28. package/src/core/debug/inputDebug.ts +51 -0
  29. package/src/core/debug/localStreamDebug.ts +50 -0
  30. package/src/core/debug/modelStateDebug.ts +35 -0
  31. package/src/core/executables/antigravityExecutable.ts +48 -0
  32. package/src/core/executables/claudeExecutable.ts +63 -0
  33. package/src/core/executables/codexExecutable.ts +160 -0
  34. package/src/core/executables/executableResolver.ts +164 -0
  35. package/src/core/executables/geminiExecutable.ts +78 -0
  36. package/src/core/models/codexCapabilities.ts +97 -0
  37. package/src/core/models/codexModelCapabilities.ts +624 -0
  38. package/src/core/models/codexModelsCacheSeed.ts +153 -0
  39. package/src/core/models/modelSpecs.ts +303 -0
  40. package/src/core/models/providerModelCache.ts +94 -0
  41. package/src/core/perf/profiler.ts +125 -0
  42. package/src/core/perf/renderDebug.ts +398 -0
  43. package/src/core/process/CommandRunner.ts +280 -0
  44. package/src/core/process/processValidation.ts +111 -0
  45. package/src/core/providerLauncher/launcher.ts +220 -0
  46. package/src/core/providerLauncher/registry.ts +354 -0
  47. package/src/core/providerLauncher/types.ts +95 -0
  48. package/src/core/providerLauncher/workspaceConfig.ts +487 -0
  49. package/src/core/providerRuntime/anthropic.ts +580 -0
  50. package/src/core/providerRuntime/antigravity.ts +500 -0
  51. package/src/core/providerRuntime/capabilityProfile.ts +383 -0
  52. package/src/core/providerRuntime/claudeCodeDiscovery.ts +724 -0
  53. package/src/core/providerRuntime/claudeCodeDiscoveryDebug.ts +55 -0
  54. package/src/core/providerRuntime/codexaCupy.ts +97 -0
  55. package/src/core/providerRuntime/codexaNative.ts +425 -0
  56. package/src/core/providerRuntime/contextMetadata.ts +397 -0
  57. package/src/core/providerRuntime/gemini.ts +789 -0
  58. package/src/core/providerRuntime/lmstudio.ts +118 -0
  59. package/src/core/providerRuntime/local.ts +770 -0
  60. package/src/core/providerRuntime/localHarness/runtime.ts +1090 -0
  61. package/src/core/providerRuntime/localOutputBudget.ts +17 -0
  62. package/src/core/providerRuntime/mistralVibe.ts +667 -0
  63. package/src/core/providerRuntime/models.ts +175 -0
  64. package/src/core/providerRuntime/reasoning.ts +20 -0
  65. package/src/core/providerRuntime/registry.ts +286 -0
  66. package/src/core/providerRuntime/types.ts +142 -0
  67. package/src/core/providerRuntime/unsloth.ts +216 -0
  68. package/src/core/providers/codexJsonStream.ts +305 -0
  69. package/src/core/providers/codexSubprocess.ts +378 -0
  70. package/src/core/providers/codexTranscript.ts +695 -0
  71. package/src/core/providers/openaiNative.ts +13 -0
  72. package/src/core/providers/registry.ts +21 -0
  73. package/src/core/providers/types.ts +91 -0
  74. package/src/core/shared/attachments.ts +101 -0
  75. package/src/core/shared/cleanupFastFail.ts +67 -0
  76. package/src/core/shared/clipboard.ts +24 -0
  77. package/src/core/shared/clipboardImage.ts +111 -0
  78. package/src/core/shared/githubDiagnostics.ts +222 -0
  79. package/src/core/shared/hollowResponseFormat.ts +39 -0
  80. package/src/core/terminal/clearFrameBoundary.ts +852 -0
  81. package/src/core/terminal/frameLock.ts +110 -0
  82. package/src/core/terminal/inkRenderReset.ts +123 -0
  83. package/src/core/terminal/startupClear.ts +20 -0
  84. package/src/core/terminal/terminalCapabilities.ts +100 -0
  85. package/src/core/terminal/terminalControl.ts +169 -0
  86. package/src/core/terminal/terminalSanitize.ts +147 -0
  87. package/src/core/terminal/terminalTitle.ts +400 -0
  88. package/src/core/version/channel.ts +27 -0
  89. package/src/core/version/packageManager.ts +119 -0
  90. package/src/core/version/updateCheck.ts +203 -0
  91. package/src/core/workspace/appData.ts +107 -0
  92. package/src/core/workspace/conversationStore.ts +335 -0
  93. package/src/core/workspace/launchContext.ts +259 -0
  94. package/src/core/workspace/planStorage.ts +135 -0
  95. package/src/core/workspace/projectInstructions.ts +54 -0
  96. package/src/core/workspace/scratchDir.ts +64 -0
  97. package/src/core/workspace/workspaceActivity.ts +384 -0
  98. package/src/core/workspace/workspaceGuard.ts +377 -0
  99. package/src/core/workspace/workspaceRoot.ts +47 -0
  100. package/src/exec.ts +73 -0
  101. package/src/headless/execArgs.ts +296 -0
  102. package/src/headless/execRunner.ts +304 -0
  103. package/src/index.tsx +270 -0
  104. package/src/legacyEnvBootstrap.ts +5 -0
  105. package/src/session/appSession.ts +771 -0
  106. package/src/session/chatLifecycle.ts +994 -0
  107. package/src/session/conversation.ts +107 -0
  108. package/src/session/liveRenderScheduler.ts +214 -0
  109. package/src/session/persistedResponse.ts +93 -0
  110. package/src/session/planFlow.ts +159 -0
  111. package/src/session/planTranscript.ts +19 -0
  112. package/src/session/promptRunSchedule.ts +26 -0
  113. package/src/session/types.ts +234 -0
  114. package/src/test/runtimeTestUtils.ts +14 -0
  115. package/src/types/react-dom.d.ts +3 -0
  116. package/src/ui/chrome/ActivityBars.tsx +68 -0
  117. package/src/ui/chrome/ActivityIndicator.tsx +58 -0
  118. package/src/ui/chrome/AnimatedStatusText.tsx +69 -0
  119. package/src/ui/chrome/AppShell.tsx +474 -0
  120. package/src/ui/chrome/BottomComposer.tsx +1135 -0
  121. package/src/ui/chrome/DashCard.tsx +82 -0
  122. package/src/ui/chrome/RunFooter.tsx +65 -0
  123. package/src/ui/chrome/RuntimeStatusBar.tsx +108 -0
  124. package/src/ui/chrome/Spinner.tsx +25 -0
  125. package/src/ui/chrome/TopHeader.tsx +439 -0
  126. package/src/ui/chrome/UpdateAvailableCard.tsx +42 -0
  127. package/src/ui/chrome/busyStatusAnimation.ts +11 -0
  128. package/src/ui/input/commandNormalize.ts +66 -0
  129. package/src/ui/input/focus.ts +73 -0
  130. package/src/ui/input/imageAttachments.ts +18 -0
  131. package/src/ui/input/inputBuffer.ts +203 -0
  132. package/src/ui/input/pastedContent.ts +75 -0
  133. package/src/ui/input/rawArrowKeys.ts +28 -0
  134. package/src/ui/input/slashCommands.ts +43 -0
  135. package/src/ui/input/useStdinRawModeLease.ts +23 -0
  136. package/src/ui/layout.ts +560 -0
  137. package/src/ui/panels/AttachmentImportPanel.tsx +131 -0
  138. package/src/ui/panels/AuthPanel.tsx +149 -0
  139. package/src/ui/panels/BackendPicker.tsx +28 -0
  140. package/src/ui/panels/ModePicker.tsx +31 -0
  141. package/src/ui/panels/ModelPicker.tsx +31 -0
  142. package/src/ui/panels/ModelPickerScreen.tsx +761 -0
  143. package/src/ui/panels/ModelReasoningPicker.tsx +458 -0
  144. package/src/ui/panels/Panel.tsx +51 -0
  145. package/src/ui/panels/PermissionsPanel.tsx +78 -0
  146. package/src/ui/panels/PlanActionPicker.tsx +187 -0
  147. package/src/ui/panels/ProviderPicker.tsx +753 -0
  148. package/src/ui/panels/ProviderSetupPrompt.tsx +52 -0
  149. package/src/ui/panels/ReasoningPicker.tsx +46 -0
  150. package/src/ui/panels/ResumePicker.tsx +90 -0
  151. package/src/ui/panels/SelectionPanel.tsx +138 -0
  152. package/src/ui/panels/SettingsPanel.tsx +156 -0
  153. package/src/ui/panels/TextEntryPanel.tsx +139 -0
  154. package/src/ui/panels/ThemePicker.tsx +32 -0
  155. package/src/ui/panels/ToolApprovalPanel.tsx +46 -0
  156. package/src/ui/panels/UpdatePromptPanel.tsx +236 -0
  157. package/src/ui/panels/responsivePickerViewport.ts +64 -0
  158. package/src/ui/render/Markdown.tsx +331 -0
  159. package/src/ui/render/diffRenderer.ts +116 -0
  160. package/src/ui/render/logoVariants.ts +113 -0
  161. package/src/ui/render/modeDisplay.ts +52 -0
  162. package/src/ui/render/outputPipeline.ts +64 -0
  163. package/src/ui/render/runtimeDisplay.ts +128 -0
  164. package/src/ui/render/terminalAnswerFormat.ts +128 -0
  165. package/src/ui/render/textLayout.ts +392 -0
  166. package/src/ui/theme.tsx +274 -0
  167. package/src/ui/themeFlow.ts +41 -0
  168. package/src/ui/timeline/ActionRequiredBlock.tsx +38 -0
  169. package/src/ui/timeline/AgentBlock.tsx +130 -0
  170. package/src/ui/timeline/StaticIntroItem.tsx +54 -0
  171. package/src/ui/timeline/ThinkingBlock.tsx +100 -0
  172. package/src/ui/timeline/Timeline.tsx +1410 -0
  173. package/src/ui/timeline/TranscriptShell.tsx +302 -0
  174. package/src/ui/timeline/TurnGroup.tsx +673 -0
  175. package/src/ui/timeline/layoutListWindow.ts +145 -0
  176. package/src/ui/timeline/liveViewportWindow.ts +68 -0
  177. package/src/ui/timeline/progressEntries.ts +156 -0
  178. package/src/ui/timeline/runActivityView.ts +37 -0
  179. package/src/ui/timeline/staticTranscriptCache.ts +174 -0
  180. package/src/ui/timeline/streamCoalesce.ts +53 -0
  181. package/src/ui/timeline/timelineMeasure.ts +3273 -0
  182. package/src/ui/useThrottledValue.ts +31 -0
@@ -0,0 +1,345 @@
1
+ export type AgentToolName =
2
+ | "list_files"
3
+ | "read_file"
4
+ | "write_file"
5
+ | "apply_patch"
6
+ | "run_shell"
7
+ | "get_workspace_info";
8
+
9
+ export interface ParsedAgentToolCall {
10
+ kind: "tool_call";
11
+ id?: string | null;
12
+ name: AgentToolName;
13
+ arguments: Record<string, unknown>;
14
+ rawArguments?: string;
15
+ raw: string;
16
+ }
17
+
18
+ export interface NormalizedAgentToolCall {
19
+ id?: string | null;
20
+ name: AgentToolName;
21
+ arguments: Record<string, unknown>;
22
+ rawArguments?: string;
23
+ }
24
+
25
+ export interface MalformedOpenAiToolCall {
26
+ kind: "malformed";
27
+ id: string | null;
28
+ name: string | null;
29
+ rawArguments: string;
30
+ error: string;
31
+ }
32
+
33
+ export type OpenAiToolCallParseResult =
34
+ | { kind: "valid"; call: NormalizedAgentToolCall }
35
+ | MalformedOpenAiToolCall;
36
+
37
+ export interface OpenAiToolDefinition {
38
+ type: "function";
39
+ function: {
40
+ name: AgentToolName;
41
+ description: string;
42
+ parameters: Record<string, unknown>;
43
+ };
44
+ }
45
+
46
+ export interface MalformedAgentToolCall {
47
+ kind: "malformed_tool_call";
48
+ raw: string;
49
+ error: string;
50
+ }
51
+
52
+ export type AgentToolParseResult =
53
+ | { kind: "final"; text: string }
54
+ | ParsedAgentToolCall
55
+ | MalformedAgentToolCall;
56
+
57
+ const TOOL_CALL_PATTERN = /<tool_call>([\s\S]*?)<\/tool_call>/i;
58
+ const TOOL_CALL_OPEN_PATTERN = /<tool_call\b[^>]*>/i;
59
+
60
+ const TOOL_NAMES = new Set<AgentToolName>([
61
+ "list_files",
62
+ "read_file",
63
+ "write_file",
64
+ "apply_patch",
65
+ "run_shell",
66
+ "get_workspace_info",
67
+ ]);
68
+
69
+ function isRecord(value: unknown): value is Record<string, unknown> {
70
+ return typeof value === "object" && value !== null && !Array.isArray(value);
71
+ }
72
+
73
+ function parseJsonMaybeWithExtraBrace(raw: string): unknown {
74
+ try {
75
+ return JSON.parse(raw) as unknown;
76
+ } catch (firstError) {
77
+ const trimmed = raw.trim();
78
+ if (trimmed.endsWith("}")) {
79
+ try {
80
+ return JSON.parse(trimmed.slice(0, -1)) as unknown;
81
+ } catch {
82
+ // Return the original parse error below.
83
+ }
84
+ }
85
+ throw firstError;
86
+ }
87
+ }
88
+
89
+ function parseArguments(value: unknown): Record<string, unknown> | null {
90
+ if (value === undefined || value === null) return {};
91
+ if (isRecord(value)) return value;
92
+ if (typeof value === "string") {
93
+ try {
94
+ const parsed = JSON.parse(value) as unknown;
95
+ return isRecord(parsed) ? parsed : null;
96
+ } catch {
97
+ return null;
98
+ }
99
+ }
100
+ return null;
101
+ }
102
+
103
+ function rawArguments(value: unknown): string {
104
+ if (typeof value === "string") return value;
105
+ if (value === undefined || value === null) return "{}";
106
+ try {
107
+ return JSON.stringify(value);
108
+ } catch {
109
+ return "";
110
+ }
111
+ }
112
+
113
+ export function normalizeAgentToolCall(value: unknown): NormalizedAgentToolCall | null {
114
+ if (!isRecord(value)) return null;
115
+
116
+ const functionCall = isRecord(value.function) ? value.function : null;
117
+ const rawName = functionCall?.name ?? value.name ?? value.tool;
118
+ if (typeof rawName !== "string" || !TOOL_NAMES.has(rawName as AgentToolName)) {
119
+ return null;
120
+ }
121
+
122
+ const args = parseArguments(functionCall?.arguments ?? value.arguments ?? value.args);
123
+ if (!args) return null;
124
+
125
+ return {
126
+ id: typeof value.id === "string" && value.id.trim() ? value.id : null,
127
+ name: rawName as AgentToolName,
128
+ arguments: args,
129
+ rawArguments: rawArguments(functionCall?.arguments ?? value.arguments ?? value.args),
130
+ };
131
+ }
132
+
133
+ export function parseOpenAiToolCallsDetailed(value: unknown): OpenAiToolCallParseResult[] {
134
+ if (!Array.isArray(value)) return [];
135
+ return value.map((item) => {
136
+ const normalized = normalizeAgentToolCall(item);
137
+ if (normalized) return { kind: "valid" as const, call: normalized };
138
+ const record = isRecord(item) ? item : {};
139
+ const functionCall = isRecord(record.function) ? record.function : null;
140
+ const rawName = functionCall?.name ?? record.name ?? record.tool;
141
+ const argsValue = functionCall?.arguments ?? record.arguments ?? record.args;
142
+ const raw = rawArguments(argsValue);
143
+ const parsedArgs = parseArguments(argsValue);
144
+ const name = typeof rawName === "string" && rawName.trim() ? rawName : null;
145
+ return {
146
+ kind: "malformed" as const,
147
+ id: typeof record.id === "string" && record.id.trim() ? record.id : null,
148
+ name,
149
+ rawArguments: raw,
150
+ error: !name
151
+ ? "Tool call did not contain a function name."
152
+ : !TOOL_NAMES.has(name as AgentToolName)
153
+ ? `Unsupported tool: ${name}`
154
+ : parsedArgs === null
155
+ ? "Tool call arguments were not valid JSON."
156
+ : "Tool call could not be normalized.",
157
+ };
158
+ });
159
+ }
160
+
161
+ export function parseOpenAiToolCalls(value: unknown): NormalizedAgentToolCall[] {
162
+ return parseOpenAiToolCallsDetailed(value)
163
+ .filter((item): item is { kind: "valid"; call: NormalizedAgentToolCall } => item.kind === "valid")
164
+ .map((item) => item.call);
165
+ }
166
+
167
+ const AGENT_TOOL_DEFINITIONS: readonly OpenAiToolDefinition[] = [
168
+ {
169
+ type: "function",
170
+ function: {
171
+ name: "list_files",
172
+ description: "List files and directories inside the workspace.",
173
+ parameters: { type: "object", properties: { path: { type: "string" } }, additionalProperties: false },
174
+ },
175
+ },
176
+ {
177
+ type: "function",
178
+ function: {
179
+ name: "read_file",
180
+ description: "Read a UTF-8 text file inside the workspace.",
181
+ parameters: { type: "object", properties: { path: { type: "string" } }, required: ["path"], additionalProperties: false },
182
+ },
183
+ },
184
+ {
185
+ type: "function",
186
+ function: {
187
+ name: "write_file",
188
+ description: "Write complete UTF-8 content to a file inside the workspace.",
189
+ parameters: {
190
+ type: "object",
191
+ properties: { path: { type: "string" }, content: { type: "string" } },
192
+ required: ["path", "content"],
193
+ additionalProperties: false,
194
+ },
195
+ },
196
+ },
197
+ {
198
+ type: "function",
199
+ function: {
200
+ name: "apply_patch",
201
+ description: "Apply a Codex Begin Patch formatted patch inside the workspace.",
202
+ parameters: {
203
+ type: "object",
204
+ properties: { patch: { type: "string" }, path: { type: "string" } },
205
+ required: ["patch"],
206
+ additionalProperties: false,
207
+ },
208
+ },
209
+ },
210
+ {
211
+ type: "function",
212
+ function: {
213
+ name: "run_shell",
214
+ description: "Run a safe shell command in the workspace.",
215
+ parameters: { type: "object", properties: { command: { type: "string" } }, required: ["command"], additionalProperties: false },
216
+ },
217
+ },
218
+ {
219
+ type: "function",
220
+ function: {
221
+ name: "get_workspace_info",
222
+ description: "Return workspace and runtime policy information.",
223
+ parameters: { type: "object", properties: {}, additionalProperties: false },
224
+ },
225
+ },
226
+ ];
227
+
228
+ export function agentToolDefinitions(runIntent: "normal" | "plan" | "approved-execution" = "normal"): readonly OpenAiToolDefinition[] {
229
+ if (runIntent !== "plan") return AGENT_TOOL_DEFINITIONS;
230
+ return AGENT_TOOL_DEFINITIONS.filter((definition) =>
231
+ definition.function.name === "list_files"
232
+ || definition.function.name === "read_file"
233
+ || definition.function.name === "get_workspace_info"
234
+ );
235
+ }
236
+
237
+ function extractJsonObjectAfterToolCall(text: string): string | null {
238
+ const open = TOOL_CALL_OPEN_PATTERN.exec(text);
239
+ if (!open) return null;
240
+
241
+ const startSearch = open.index + open[0].length;
242
+ const firstBrace = text.indexOf("{", startSearch);
243
+ if (firstBrace < 0) return text.slice(startSearch).trim();
244
+
245
+ let depth = 0;
246
+ let inString = false;
247
+ let escaped = false;
248
+ for (let index = firstBrace; index < text.length; index += 1) {
249
+ const char = text[index];
250
+ if (escaped) {
251
+ escaped = false;
252
+ continue;
253
+ }
254
+ if (char === "\\") {
255
+ escaped = inString;
256
+ continue;
257
+ }
258
+ if (char === "\"") {
259
+ inString = !inString;
260
+ continue;
261
+ }
262
+ if (inString) continue;
263
+ if (char === "{") depth += 1;
264
+ if (char === "}") {
265
+ depth -= 1;
266
+ if (depth === 0) {
267
+ return text.slice(firstBrace, index + 1).trim();
268
+ }
269
+ }
270
+ }
271
+
272
+ return text.slice(firstBrace).replace(/<\/tool_call>.*/is, "").trim();
273
+ }
274
+
275
+ function parseToolCallPayload(raw: string): AgentToolParseResult {
276
+ try {
277
+ const parsed = parseJsonMaybeWithExtraBrace(raw);
278
+ const fromToolCalls = isRecord(parsed) ? parseOpenAiToolCalls(parsed.tool_calls) : [];
279
+ const normalized = fromToolCalls[0] ?? normalizeAgentToolCall(parsed);
280
+ if (!normalized) {
281
+ return { kind: "malformed_tool_call", raw, error: "Tool call JSON did not contain a supported tool call." };
282
+ }
283
+
284
+ return {
285
+ kind: "tool_call",
286
+ ...normalized,
287
+ raw,
288
+ };
289
+ } catch (error) {
290
+ return {
291
+ kind: "malformed_tool_call",
292
+ raw,
293
+ error: error instanceof Error ? error.message : String(error),
294
+ };
295
+ }
296
+ }
297
+
298
+ export function parseAgentToolCall(text: string): AgentToolParseResult {
299
+ const closedMatch = TOOL_CALL_PATTERN.exec(text);
300
+ const raw = closedMatch?.[1]?.trim() ?? extractJsonObjectAfterToolCall(text);
301
+ if (!raw) {
302
+ return TOOL_CALL_OPEN_PATTERN.test(text)
303
+ ? { kind: "malformed_tool_call", raw: "", error: "Tool call block did not contain JSON." }
304
+ : { kind: "final", text };
305
+ }
306
+
307
+ return parseToolCallPayload(raw);
308
+ }
309
+
310
+ function scalar(value: unknown): string {
311
+ if (value === undefined || value === null) return "";
312
+ if (typeof value === "string") return value;
313
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
314
+ return JSON.stringify(value);
315
+ }
316
+
317
+ function blockField(name: string, value: unknown): string[] {
318
+ return [`${name}:`, scalar(value)];
319
+ }
320
+
321
+ export function serializeToolResult(result: unknown): string {
322
+ const record = isRecord(result) ? result : {};
323
+ const tool = typeof record.tool === "string" ? record.tool : "unknown";
324
+ const lines: string[] = [`<tool_result name="${tool}">`];
325
+
326
+ lines.push(`success: ${scalar(record.success)}`);
327
+ if (record.command !== undefined) lines.push(`command: ${scalar(record.command)}`);
328
+ if (record.path !== undefined) lines.push(`path: ${scalar(record.path)}`);
329
+ if (record.paths !== undefined) lines.push(`paths: ${Array.isArray(record.paths) ? record.paths.join(", ") : scalar(record.paths)}`);
330
+ if (record.exitCode !== undefined) lines.push(`exit_code: ${scalar(record.exitCode)}`);
331
+ if (record.durationMs !== undefined) lines.push(`duration_ms: ${scalar(record.durationMs)}`);
332
+ if (record.summary !== undefined) lines.push(...blockField("summary", record.summary));
333
+ if (record.error !== undefined) lines.push(...blockField("error", record.error));
334
+
335
+ if (tool === "run_shell") {
336
+ lines.push(...blockField("stdout", record.stdout ?? record.output ?? ""));
337
+ lines.push(...blockField("stderr", record.stderr ?? ""));
338
+ } else if (record.output !== undefined) {
339
+ lines.push(...blockField("output", record.output));
340
+ }
341
+
342
+ if (record.raw !== undefined) lines.push(...blockField("raw", record.raw));
343
+ lines.push("</tool_result>");
344
+ return lines.join("\n");
345
+ }