@mono-agent/agent-runtime 0.6.0 → 0.6.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mono-agent/agent-runtime",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "Agent runtime supporting Claude SDK, Claude CLI, Codex CLI, and PI SDK out of the box",
5
5
  "type": "module",
6
6
  "license": "GPL-3.0-only",
@@ -18,8 +18,6 @@ import {
18
18
  writeToolImpl,
19
19
  } from "./index.js";
20
20
  import {
21
- createFileEditToolResultEvent,
22
- createFileEditToolUseEvent,
23
21
  fileChangeSummary,
24
22
  readFileChangeSnapshot,
25
23
  statsForCompletedChange,
@@ -165,19 +163,18 @@ function compactRawMcpResult(out) {
165
163
  };
166
164
  }
167
165
 
168
- /**
169
- * @param {any} change
170
- * @param {{status?: any, before?: any, after?: any, error?: any}} [options]
171
- */
172
- function fileEditPayload(change, { status, before, after, error } = {}) {
166
+ function writeFileChangeDetails(path, before, after) {
167
+ const change = {
168
+ path,
169
+ kind: before && before.exists ? "update" : "add",
170
+ };
173
171
  const lineStats = statsForCompletedChange(change, before, after);
174
172
  const completedChange = lineStats ? { ...change, line_stats: lineStats } : change;
175
173
  const summary = fileChangeSummary([completedChange]);
176
174
  return {
175
+ status: "completed",
177
176
  changes: [completedChange],
178
- status,
179
177
  ...(summary ? { summary } : {}),
180
- ...(error ? { error } : {}),
181
178
  };
182
179
  }
183
180
 
@@ -262,24 +259,8 @@ function createBuiltinTool(name, label, description, parameters, execute, { cwd,
262
259
  if (name === "Bash" && toolPolicy?.bashReadOnly && !isReadOnlyShellCommand(normalized.command)) {
263
260
  throw new Error("Error: Planning shell policy allows only read-only inspection commands.");
264
261
  }
265
- const isFileEdit = name === "Write" || name === "Edit";
266
- let editState = null;
267
- if (isFileEdit && normalized.file_path) {
268
- const before = readFileChangeSnapshot(normalized.file_path);
269
- editState = {
270
- path: normalized.file_path,
271
- before,
272
- change: {
273
- path: normalized.file_path,
274
- kind: name === "Write" && before && !before.exists ? "add" : "update",
275
- },
276
- };
277
- onEvent?.(createFileEditToolUseEvent(`file_edit:${toolCallId}`, {
278
- changes: [editState.change],
279
- status: "in_progress",
280
- }));
281
- }
282
-
262
+ const shouldTrackWrite = name === "Write" && typeof normalized.file_path === "string" && normalized.file_path.length > 0;
263
+ const beforeWrite = shouldTrackWrite ? readFileChangeSnapshot(normalized.file_path) : null;
283
264
  const raw = await execute(normalized, { signal, sandboxPolicy, sandboxEngine, ctx });
284
265
  // Image reads (e.g. Read on a .png) come back as a structured image
285
266
  // result so vision models see pixels; emit an image content block and let
@@ -288,22 +269,12 @@ function createBuiltinTool(name, label, description, parameters, execute, { cwd,
288
269
  return imageResult(raw.data, raw.mimeType, { tool: name, params: normalized });
289
270
  }
290
271
  const text = toolText(raw);
291
- if (isFileEdit && editState) {
292
- const failed = isErrorText(text);
293
- const after = readFileChangeSnapshot(editState.path);
294
- onEvent?.(createFileEditToolResultEvent(
295
- `file_edit:${toolCallId}`,
296
- fileEditPayload(editState.change, {
297
- status: failed ? "failed" : "completed",
298
- before: editState.before,
299
- after,
300
- error: failed ? text : null,
301
- }),
302
- { isError: failed },
303
- ));
304
- }
305
272
  if (isErrorText(text)) throw new Error(text);
306
- return textResult(text, { tool: name, params: normalized });
273
+ const details = { tool: name, params: normalized };
274
+ if (shouldTrackWrite) {
275
+ details.file_change = writeFileChangeDetails(normalized.file_path, beforeWrite, readFileChangeSnapshot(normalized.file_path));
276
+ }
277
+ return textResult(text, details);
307
278
  },
308
279
  };
309
280
  }
@@ -211,24 +211,3 @@ export function createFileChangePayload(raw, { cwd = process.cwd(), snapshots =
211
211
  ...(summary ? { summary } : {}),
212
212
  };
213
213
  }
214
-
215
- export function createFileEditToolUseEvent(id, payload) {
216
- return {
217
- type: "assistant",
218
- message: { content: [{ type: "tool_use", id, name: "file_edit", input: payload }] },
219
- };
220
- }
221
-
222
- export function createFileEditToolResultEvent(id, payload, { isError = false } = {}) {
223
- return {
224
- type: "user",
225
- message: {
226
- content: [{
227
- type: "tool_result",
228
- tool_use_id: id,
229
- content: payload,
230
- is_error: isError,
231
- }],
232
- },
233
- };
234
- }
@@ -1,13 +1,5 @@
1
1
  import { query } from "@anthropic-ai/claude-agent-sdk";
2
2
  import { randomUUID } from "node:crypto";
3
- import { resolve } from "node:path";
4
- import {
5
- createFileEditToolResultEvent,
6
- createFileEditToolUseEvent,
7
- fileChangeSummary,
8
- readFileChangeSnapshot,
9
- statsForCompletedChange,
10
- } from "../file-change-stats.js";
11
3
  import { formatLiveInputGuidance } from "../live-input-prompt.js";
12
4
  import { estimateCost } from "../cost.js";
13
5
  import { modelWithContextWindow } from "../runtime/context-windows.js";
@@ -209,8 +201,6 @@ function structuredOutputRejectionFromEvent(event) {
209
201
  return /structured output|required schema|did not match schema|schema violation/i.test(result) ? result : null;
210
202
  }
211
203
 
212
- const CLAUDE_FILE_EDIT_MATCHER = "Edit|Write|NotebookEdit";
213
-
214
204
  function mergeHookMatchers(existing = {}, additions = {}) {
215
205
  const merged = {};
216
206
  for (const [name, groups] of Object.entries(existing || {})) {
@@ -291,122 +281,7 @@ export function toolPayloadLimit(options) {
291
281
  return { bytes: MAX_TOOL_RESULT_BYTES, usedSettings: false };
292
282
  }
293
283
 
294
- function claudeEditPath(toolName, toolInput) {
295
- const input = objectInput(toolInput);
296
- if (toolName === "NotebookEdit") return input.notebook_path || input.file_path || "";
297
- return input.file_path || "";
298
- }
299
-
300
- function claudeEditKind(toolName, before) {
301
- if (toolName === "Write" && before && !before.exists) return "add";
302
- return "update";
303
- }
304
-
305
- function fileEditStateKey(input, toolUseID, path) {
306
- return toolUseID || input?.tool_use_id || input?.toolUseID || `${input?.tool_name || "file_edit"}:${path}`;
307
- }
308
-
309
- /**
310
- * @param {any} change
311
- * @param {{status?: any, before?: any, after?: any, error?: any}} [options]
312
- */
313
- function fileEditPayload(change, { status, before, after, error } = {}) {
314
- const lineStats = statsForCompletedChange(change, before, after);
315
- const completedChange = lineStats ? { ...change, line_stats: lineStats } : change;
316
- const summary = fileChangeSummary([completedChange]);
317
- return {
318
- changes: [completedChange],
319
- status,
320
- ...(summary ? { summary } : {}),
321
- ...(error ? { error } : {}),
322
- };
323
- }
324
-
325
- function createClaudeFileEditHooks({ cwd, emitEvent }) {
326
- const edits = new Map();
327
- const runCwd = cwd || process.cwd();
328
-
329
- function createState(input, toolUseID, { readBefore = true } = {}) {
330
- const toolName = input?.tool_name;
331
- const path = claudeEditPath(toolName, input?.tool_input);
332
- if (!path) return null;
333
- const resolvedPath = resolve(runCwd, path);
334
- const key = fileEditStateKey(input, toolUseID, resolvedPath);
335
- const before = readBefore ? readFileChangeSnapshot(resolvedPath) : null;
336
- return {
337
- key,
338
- id: `file_edit:${key}`,
339
- path: resolvedPath,
340
- change: { path: resolvedPath, kind: claudeEditKind(toolName, before) },
341
- before,
342
- started: false,
343
- };
344
- }
345
-
346
- function emitStart(state) {
347
- if (!state || state.started) return;
348
- emitEvent(createFileEditToolUseEvent(state.id, {
349
- changes: [state.change],
350
- status: "in_progress",
351
- }));
352
- state.started = true;
353
- }
354
-
355
- /**
356
- * @param {any} input
357
- * @param {any} toolUseID
358
- * @param {{status?: any, error?: any}} [options]
359
- */
360
- function complete(input, toolUseID, { status, error } = {}) {
361
- const directKey = toolUseID || input?.tool_use_id || input?.toolUseID;
362
- const fallback = createState(input, toolUseID, { readBefore: false });
363
- const state = (directKey && edits.get(directKey)) || (fallback?.key && edits.get(fallback.key)) || fallback;
364
- if (!state) return;
365
- emitStart(state);
366
- const after = readFileChangeSnapshot(state.path);
367
- const payload = fileEditPayload(state.change, {
368
- status,
369
- before: state.before,
370
- after,
371
- error,
372
- });
373
- emitEvent(createFileEditToolResultEvent(state.id, payload, { isError: status === "failed" }));
374
- edits.delete(state.key);
375
- }
376
-
377
- return {
378
- PreToolUse: [{
379
- matcher: CLAUDE_FILE_EDIT_MATCHER,
380
- hooks: [async (input, toolUseID) => {
381
- const state = createState(input, toolUseID);
382
- if (!state) return {};
383
- edits.set(state.key, state);
384
- emitStart(state);
385
- return {};
386
- }],
387
- }],
388
- PostToolUse: [{
389
- matcher: CLAUDE_FILE_EDIT_MATCHER,
390
- hooks: [async (input, toolUseID) => {
391
- complete(input, toolUseID, { status: "completed" });
392
- return {};
393
- }],
394
- }],
395
- PostToolUseFailure: [{
396
- matcher: CLAUDE_FILE_EDIT_MATCHER,
397
- hooks: [async (input, toolUseID) => {
398
- complete(input, toolUseID, {
399
- status: "failed",
400
- error: stringifyError(input?.error) || "tool failed",
401
- });
402
- return {};
403
- }],
404
- }],
405
- };
406
- }
407
-
408
284
  function createClaudeRuntimeHooks({
409
- cwd,
410
285
  emitEvent,
411
286
  persistArtifact,
412
287
  qaOutputDir,
@@ -414,7 +289,7 @@ function createClaudeRuntimeHooks({
414
289
  onToolUse,
415
290
  onToolResult,
416
291
  }) {
417
- return mergeHookMatchers(createClaudeFileEditHooks({ cwd, emitEvent }), {
292
+ return {
418
293
  PreToolUse: [{
419
294
  matcher: "*",
420
295
  hooks: [async (input) => {
@@ -469,7 +344,7 @@ function createClaudeRuntimeHooks({
469
344
  return {};
470
345
  }],
471
346
  }],
472
- });
347
+ };
473
348
  }
474
349
 
475
350
  function promptStringFromMessages(messages) {
@@ -607,7 +482,6 @@ export async function generateClaudeResponse(systemPrompt, options) {
607
482
  ...(approvalManager ? { canUseTool: createClaudeCanUseTool(approvalManager, model.model) } : {}),
608
483
  ...(nativeAgents ? { agents: nativeAgents } : {}),
609
484
  hooks: mergeHookMatchers(hooks, createClaudeRuntimeHooks({
610
- cwd,
611
485
  emitEvent,
612
486
  persistArtifact,
613
487
  qaOutputDir,
@@ -116,8 +116,7 @@ function sandboxForPermissionMode(permissionMode) {
116
116
 
117
117
  function approvalPolicyForPermissionMode(permissionMode) {
118
118
  if (permissionMode === "bypassPermissions") return "never";
119
- if (permissionMode === "plan") return "on-request";
120
- return "on-failure";
119
+ return "on-request";
121
120
  }
122
121
 
123
122
  function codexMcpConfig(mcpServers = {}) {
@@ -25,6 +25,13 @@ export function toolStartProgressText(toolName) {
25
25
  return `Running ${toolName}...`;
26
26
  }
27
27
 
28
+ function toolResultFileChange(result) {
29
+ const fileChange = result?.details?.file_change;
30
+ return fileChange && typeof fileChange === "object" && !Array.isArray(fileChange)
31
+ ? jsonSerializable(fileChange, null)
32
+ : null;
33
+ }
34
+
28
35
  /**
29
36
  * The slice of run state the stream subscriber reads and mutates. A structural
30
37
  * subset of the orchestrator's runState.
@@ -96,6 +103,7 @@ export function createStreamSubscriber(runState, { onEvent, options, toolLimits,
96
103
  });
97
104
  } else if (event.type === "tool_execution_end") {
98
105
  const resultContent = toolResultContent(event.result);
106
+ const fileChange = toolResultFileChange(event.result);
99
107
  if (!event.isError) runState.toolResultsSeen += 1;
100
108
  const startedAt = runState.toolStartTimes.get(event.toolCallId);
101
109
  if (startedAt !== undefined) {
@@ -116,6 +124,7 @@ export function createStreamSubscriber(runState, { onEvent, options, toolLimits,
116
124
  tool_use_id: event.toolCallId,
117
125
  content: resultContent,
118
126
  raw_result: compactToolRawResult(jsonSerializable(event.result, resultContent), resultContent),
127
+ ...(fileChange === null ? {} : { file_change: fileChange }),
119
128
  is_error: !!event.isError,
120
129
  }],
121
130
  },
@@ -70,22 +70,14 @@ export function normalizeCodexItemEvent(raw, context = {}) {
70
70
  if (type === "file_change") {
71
71
  const id = itemId(item, "file_change");
72
72
  const payload = fileChangePayload(raw, item, context);
73
- if (!isCompleted(raw)) {
74
- return {
75
- type: "assistant",
76
- message: { content: [{ type: "tool_use", id, name: "file_edit", input: payload }] },
77
- };
78
- }
79
73
  return {
80
- type: "user",
81
- message: {
82
- content: [{
83
- type: "tool_result",
84
- tool_use_id: id,
85
- content: item.error || payload,
86
- is_error: itemFailed(item),
87
- }],
88
- },
74
+ type: "file_change",
75
+ id,
76
+ status: payload.status,
77
+ changes: payload.changes,
78
+ ...(payload.summary ? { summary: payload.summary } : {}),
79
+ ...(item.error ? { error: item.error } : {}),
80
+ is_error: itemFailed(item),
89
81
  };
90
82
  }
91
83
 
@@ -49,27 +49,3 @@ export function createFileChangePayload(raw: any, { cwd, snapshots }?: {
49
49
  changes: any;
50
50
  status: any;
51
51
  };
52
- export function createFileEditToolUseEvent(id: any, payload: any): {
53
- type: string;
54
- message: {
55
- content: {
56
- type: string;
57
- id: any;
58
- name: string;
59
- input: any;
60
- }[];
61
- };
62
- };
63
- export function createFileEditToolResultEvent(id: any, payload: any, { isError }?: {
64
- isError?: boolean;
65
- }): {
66
- type: string;
67
- message: {
68
- content: {
69
- type: string;
70
- tool_use_id: any;
71
- content: any;
72
- is_error: boolean;
73
- }[];
74
- };
75
- };
@@ -1,12 +1,21 @@
1
1
  export function normalizeCodexItemType(type: any): any;
2
2
  export function normalizeCodexItemEvent(raw: any, context?: {}): {
3
+ is_error: boolean;
4
+ error?: any;
5
+ summary?: any;
6
+ type: string;
7
+ id: any;
8
+ status: any;
9
+ changes: any;
10
+ message?: undefined;
11
+ } | {
3
12
  type: string;
4
13
  message: {
5
14
  content: {
6
15
  type: string;
7
- tool_use_id: any;
8
- content: any;
9
- is_error: boolean;
16
+ id: any;
17
+ name: any;
18
+ input: any;
10
19
  }[];
11
20
  };
12
21
  } | {
@@ -14,9 +23,9 @@ export function normalizeCodexItemEvent(raw: any, context?: {}): {
14
23
  message: {
15
24
  content: {
16
25
  type: string;
17
- id: any;
18
- name: any;
19
- input: any;
26
+ tool_use_id: any;
27
+ content: any;
28
+ is_error: boolean;
20
29
  }[];
21
30
  };
22
31
  } | {