@myagentroam/agent 0.10.1 → 0.10.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.
@@ -1,6 +1,6 @@
1
- import { codeCallEvidence, codeObservationEvidence, retainedCodeInputs } from '../../session/context-gc-observations.js';
1
+ import { codeCallEvidence, codePartialPatchCallEvidence, codeObservationEvidence, retainedCodeInputs } from '../../session/context-gc-observations.js';
2
2
  import { ContextGcCheckpointCache, contextGcBaseFromRecords, contextGcLogicalPathKey, contextGcMinimumUsageGrowth, contextGcRecordSourceHash, contextGcTargetKey, normalizeContextGcReplacements } from '../../session/context-gc-checkpoint.js';
3
- import { codeCellCompletionSequence, contextGcApplyPatchInputReplacement, contextGcCodeModeInputReplacement, contextGcCodeModeOutputReplacement, contextGcExecInputReplacement, contextGcExecOutputReplacement, contextGcPatchResultFromCompletedPayload, contextGcStaleReadReplacement, contextGcWebOutputReplacement, normalizeContextGcCodeModeInput, normalizeContextGcExecResult } from '../../session/context-gc-replacements.js';
3
+ import { codeCellCompletionSequence, contextGcApplyPatchInputReplacement, contextGcCodeModeInputReplacement, contextGcCodeModeOutputReplacement, contextGcExecInputReplacement, contextGcExecOutputReplacement, contextGcPatchResultFromCompletedPayload, contextGcPatchResultFromFailedPayload, contextGcStaleReadReplacement, contextGcWebOutputReplacement, normalizeContextGcCodeModeInput, normalizeContextGcExecResult } from '../../session/context-gc-replacements.js';
4
4
  import { contextGcSubagentNeedsRetainedPriorFinal, contextGcSubagentOutputReplacement, contextGcSubagentPriorFinalIsRetained, contextGcSubagentSnapshotIsPreciselyCovered, contextGcSubagentSnapshotIsSuperseded, contextGcSubagentSnapshotTurnId, normalizeContextGcSubagentSnapshot } from '../../session/context-gc-subagent.js';
5
5
  import { parseReadContextOutput } from '../../tools/local/read-context.js';
6
6
  import { CONTEXT_GC_TOKEN_ESTIMATOR, contextGcCacheImpact, contextGcMessageTokens, contextGcPrefixTokens, contextGcReclaimedTokens } from './gc-tokens.js';
@@ -533,23 +533,26 @@ function collectStageACandidates(input, groups, existingReplacements, existingTa
533
533
  const claimedReadItems = new Set([...existingReadReplacements.values()].flatMap((replacement) => replacement.items.map((item) => `${replacement.target.recordId}:${replacement.target.observationId ?? ''}:${item.index}`)));
534
534
  const patches = [...groups.values()]
535
535
  .flatMap((patch) => {
536
- if (patch.toolName !== 'apply_patch' ||
537
- !patch.call ||
538
- !patch.result ||
539
- patch.failed ||
540
- !patch.completed)
536
+ if (patch.toolName !== 'apply_patch' || !patch.call || !patch.result)
541
537
  return [];
542
- const result = contextGcPatchResultFromCompletedPayload(patch.completed.payload);
543
- return result ? [{ patch, result, evidenceSequence: patch.result.record.sequence }] : [];
538
+ const terminal = patch.failed ?? patch.completed;
539
+ if (!terminal)
540
+ return [];
541
+ const result = patch.failed
542
+ ? contextGcPatchResultFromFailedPayload(terminal.payload)
543
+ : contextGcPatchResultFromCompletedPayload(terminal.payload);
544
+ return result
545
+ ? [{ patch, result, terminal, evidenceSequence: patch.result.record.sequence }]
546
+ : [];
544
547
  })
545
548
  .sort((left, right) => left.evidenceSequence - right.evidenceSequence);
546
- for (const { patch, result, evidenceSequence } of patches) {
549
+ for (const { patch, result, terminal, evidenceSequence } of patches) {
547
550
  const patchCall = patch.call;
548
551
  const observedRollouts = successfulRolloutsAfter(input.records, evidenceSequence, input.modelId, input.contextWindowTokens);
549
552
  const replacements = [];
550
553
  const candidateReadItems = new Set();
551
554
  const patchTarget = targetFor(patchCall.record, patchCall.message);
552
- if (patchTarget && !existingTargets.has(contextGcTargetKey(patchTarget)))
555
+ if (!patch.failed && patchTarget && !existingTargets.has(contextGcTargetKey(patchTarget)))
553
556
  replacements.push({
554
557
  kind: 'TOOL_CALL_INPUT',
555
558
  toolName: 'apply_patch',
@@ -562,7 +565,7 @@ function collectStageACandidates(input, groups, existingReplacements, existingTa
562
565
  evidenceSequence
563
566
  });
564
567
  for (const read of reads) {
565
- if ((read.completed?.sequence ?? read.result.record.sequence) >= patch.completed.sequence)
568
+ if ((read.completed?.sequence ?? read.result.record.sequence) >= terminal.sequence)
566
569
  continue;
567
570
  const parsed = parseReadContextOutput(read.result.message.content);
568
571
  if (!parsed)
@@ -953,11 +956,6 @@ function buildToolGroups(records, messages, sources) {
953
956
  for (const [index, message] of messages.entries()) {
954
957
  if (!message.observations || message.role !== 'tool')
955
958
  continue;
956
- const outer = message.callId ? groups.get(message.callId) : undefined;
957
- if (!outer?.completed ||
958
- outer.failed ||
959
- codeCellCompletionSequence(records, outer.completed) === undefined)
960
- continue;
961
959
  const record = sources[index];
962
960
  for (const entry of message.calls ?? []) {
963
961
  if (![
@@ -969,9 +967,11 @@ function buildToolGroups(records, messages, sources) {
969
967
  'agent_message',
970
968
  'agent_cancel'
971
969
  ].includes(entry.toolName) ||
972
- message.observations.some((value) => value.type === 'tool_result' && value.source.callId === entry.callId))
970
+ (entry.status === 'completed' &&
971
+ message.observations.some((value) => value.type === 'tool_result' && value.source.callId === entry.callId)))
973
972
  continue;
974
- const evidence = codeCallEvidence(records, record, entry);
973
+ const evidence = codeCallEvidence(records, record, entry) ??
974
+ codePartialPatchCallEvidence(records, record, entry);
975
975
  if (!evidence)
976
976
  continue;
977
977
  groups.set(`execution:${evidence.callId}`, {
@@ -979,7 +979,9 @@ function buildToolGroups(records, messages, sources) {
979
979
  callId: evidence.callId,
980
980
  toolName: entry.toolName,
981
981
  aliases: [],
982
- completed: evidence.completed,
982
+ ...('completed' in evidence
983
+ ? { completed: evidence.completed }
984
+ : { failed: evidence.failed }),
983
985
  call: {
984
986
  record: evidence.call,
985
987
  message: {
@@ -84,7 +84,7 @@ export type MarAgentEvent = MarAgentEventBase & ({
84
84
  data?: CodeCellEventData | import('../tools/contracts.js').ExecResult | Omit<import('../tools/contracts.js').ExecResult, 'stdout' | 'stderr'> | import('../tools/contracts.js').ApplyPatchResult | import('../tools/contracts.js').ReadEventData | import('../tools/contracts.js').ViewImageResult | import('../subagent/contracts.js').SubagentTaskSnapshot;
85
85
  } | {
86
86
  type: 'tool.failed';
87
- data?: import('../tools/contracts.js').CodeCellEventData;
87
+ data?: CodeCellEventData | import('../tools/contracts.js').ApplyPatchPartialResult;
88
88
  toolName: string;
89
89
  callId: string;
90
90
  parentCallId?: string;
@@ -1,9 +1,9 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { posix, win32 } from 'node:path';
3
3
  import { isDeepStrictEqual } from 'node:util';
4
- import { codeCallEvidence, callsFromRecord, codeObservationEvidence, observationsFromRecord, retainedCodeInputs } from './context-gc-observations.js';
4
+ import { codeCallEvidence, codePartialPatchCallEvidence, callsFromRecord, codeObservationEvidence, observationsFromRecord, retainedCodeInputs } from './context-gc-observations.js';
5
5
  import { parseReadContextOutput } from '../tools/local/read-context.js';
6
- import { codeCellCompletionSequence, contextGcApplyPatchInputReplacement, contextGcCodeModeInputReplacement, contextGcCodeModeOutputReplacement, contextGcExecInputReplacement, contextGcExecOutputReplacement, contextGcPatchResultFromCompletedPayload, contextGcStaleReadReplacement, contextGcWebOutputReplacement, normalizeContextGcCodeModeInput, normalizeContextGcExecResult } from './context-gc-replacements.js';
6
+ import { codeCellCompletionSequence, contextGcApplyPatchInputReplacement, contextGcCodeModeInputReplacement, contextGcCodeModeOutputReplacement, contextGcExecInputReplacement, contextGcExecOutputReplacement, contextGcPatchResultFromCompletedPayload, contextGcPatchResultFromFailedPayload, contextGcStaleReadReplacement, contextGcWebOutputReplacement, normalizeContextGcCodeModeInput, normalizeContextGcExecResult } from './context-gc-replacements.js';
7
7
  import { contextGcSubagentNeedsRetainedPriorFinal, contextGcSubagentOutputReplacement, contextGcSubagentSnapshotIsPreciselyCovered, contextGcSubagentSnapshotIsSuperseded, normalizeContextGcSubagentSnapshot } from './context-gc-subagent.js';
8
8
  export const CONTEXT_GC_CHECKPOINT_VERSION = 1;
9
9
  export const CONTEXT_GC_MAX_TARGETS = 4_096;
@@ -239,6 +239,7 @@ function replacementMatchesRecords(replacement, records, recordsBySequence, work
239
239
  let source = recordsBySequence.get(replacement.target.sequence);
240
240
  if (!source || !isRecord(source.payload))
241
241
  return false;
242
+ let sourceToolSequence = source.sequence;
242
243
  if (replacement.target.observationId) {
243
244
  const selected = observationsFromRecord(source)?.find((value) => value.id === replacement.target.observationId);
244
245
  if (!selected)
@@ -247,6 +248,7 @@ function replacementMatchesRecords(replacement, records, recordsBySequence, work
247
248
  const evidence = codeObservationEvidence(records, source, selected);
248
249
  if (!evidence)
249
250
  return false;
251
+ sourceToolSequence = evidence.completed.sequence;
250
252
  source = {
251
253
  ...source,
252
254
  payload: {
@@ -273,7 +275,7 @@ function replacementMatchesRecords(replacement, records, recordsBySequence, work
273
275
  const originalItems = new Map(parsed.items.map((item) => [item.index, item]));
274
276
  return replacement.items.every((item) => {
275
277
  const original = originalItems.get(item.index);
276
- const evidence = successfulPatchEvidenceForPath(records, recordsBySequence, item.evidenceSequence, item.path, workspace, platform, item.supersededByCallId);
278
+ const evidence = appliedPatchEvidenceForPath(records, recordsBySequence, item.evidenceSequence, item.path, workspace, platform, sourceToolSequence, item.supersededByCallId);
277
279
  return (original?.pathTruncated === false &&
278
280
  original?.path === item.path &&
279
281
  evidence !== undefined &&
@@ -531,12 +533,47 @@ function modelToolResultForCall(records, call, beforeSequence) {
531
533
  record.payload.role === 'tool' &&
532
534
  record.payload.callId === callId);
533
535
  }
534
- function successfulPatchEvidenceForPath(records, recordsBySequence, evidenceSequence, path, workspace, platform, expectedCallId) {
535
- const evidence = successfulToolEvidence(records, recordsBySequence, evidenceSequence, 'apply_patch', expectedCallId);
536
- if (!evidence || !isRecord(evidence.completed.payload))
536
+ function partialPatchEvidence(records, recordsBySequence, evidenceSequence, expectedCallId) {
537
+ const result = recordsBySequence.get(evidenceSequence);
538
+ if (result?.type !== 'model.context' ||
539
+ !isRecord(result.payload) ||
540
+ result.payload.role !== 'tool' ||
541
+ typeof result.payload.callId !== 'string')
537
542
  return undefined;
538
- const result = contextGcPatchResultFromCompletedPayload(evidence.completed.payload);
539
- if (!result)
543
+ const callId = result.payload.callId;
544
+ const call = modelToolCallForCallId(records, callId, result.sequence);
545
+ if (!call ||
546
+ !isRecord(call.payload) ||
547
+ call.payload.name !== 'apply_patch' ||
548
+ (expectedCallId && callId !== expectedCallId)) {
549
+ for (const entry of callsFromRecord(result) ?? []) {
550
+ if (expectedCallId && entry.callId !== expectedCallId)
551
+ continue;
552
+ const evidence = codePartialPatchCallEvidence(records, result, entry);
553
+ if (evidence)
554
+ return evidence;
555
+ }
556
+ return undefined;
557
+ }
558
+ const failed = records.findLast((record) => record.sequence > call.sequence &&
559
+ record.sequence < result.sequence &&
560
+ record.type === 'tool.failed' &&
561
+ isRecord(record.payload) &&
562
+ record.payload.callId === callId &&
563
+ contextGcPatchResultFromFailedPayload(record.payload) !== undefined);
564
+ return failed ? { callId, call, failed, result } : undefined;
565
+ }
566
+ function appliedPatchEvidenceForPath(records, recordsBySequence, evidenceSequence, path, workspace, platform, readSequence, expectedCallId) {
567
+ const evidence = successfulToolEvidence(records, recordsBySequence, evidenceSequence, 'apply_patch', expectedCallId);
568
+ const partial = evidence
569
+ ? undefined
570
+ : partialPatchEvidence(records, recordsBySequence, evidenceSequence, expectedCallId);
571
+ const callId = evidence?.callId ?? partial?.callId;
572
+ const terminalSequence = evidence?.completed.sequence ?? partial?.failed.sequence;
573
+ const result = evidence
574
+ ? contextGcPatchResultFromCompletedPayload(evidence.completed.payload)
575
+ : contextGcPatchResultFromFailedPayload(partial?.failed.payload);
576
+ if (!result || !callId || terminalSequence === undefined || readSequence >= terminalSequence)
540
577
  return undefined;
541
578
  const expected = contextGcLogicalPathKey(path, workspace, platform);
542
579
  if (!expected)
@@ -544,7 +581,7 @@ function successfulPatchEvidenceForPath(records, recordsBySequence, evidenceSequ
544
581
  const file = result.files.find((file) => file.operation !== 'added' &&
545
582
  contextGcLogicalPathKey(file.path, workspace, platform) === expected);
546
583
  return file && file.operation !== 'added'
547
- ? { callId: evidence.callId, file: { operation: file.operation } }
584
+ ? { callId, file: { operation: file.operation } }
548
585
  : undefined;
549
586
  }
550
587
  function isSuccessfulExecLifecycleEvidence(replacement, source, records, recordsBySequence) {
@@ -7,7 +7,22 @@ export declare function codeObservationEvidence(records: readonly SessionRecord[
7
7
  callId: string;
8
8
  call: SessionRecord;
9
9
  completed: SessionRecord;
10
- result: SessionRecord;
10
+ result: {
11
+ payload: {
12
+ role: string;
13
+ callId: string;
14
+ content: string;
15
+ observationId: string;
16
+ };
17
+ schemaVersion: 1;
18
+ recordId: string;
19
+ sequence: number;
20
+ timestamp: string;
21
+ sessionId: string;
22
+ executionId?: string;
23
+ turnId?: string;
24
+ type: string;
25
+ };
11
26
  } | undefined;
12
27
  export declare function observationsFromRecord(record: SessionRecord): CodeObservation[] | undefined;
13
28
  export declare function codeCallEvidence(records: readonly SessionRecord[], result: SessionRecord, entry: CodeToolCallRecord): {
@@ -30,6 +45,13 @@ export declare function codeCallEvidence(records: readonly SessionRecord[], resu
30
45
  call: SessionRecord;
31
46
  completed: SessionRecord;
32
47
  } | undefined;
48
+ /** Failed patch calls can prove applied file units, never successful input reclamation. */
49
+ export declare function codePartialPatchCallEvidence(records: readonly SessionRecord[], result: SessionRecord, entry: CodeToolCallRecord): {
50
+ callId: string;
51
+ call: SessionRecord;
52
+ failed: SessionRecord;
53
+ result: SessionRecord;
54
+ } | undefined;
33
55
  export declare function callsFromRecord(record: SessionRecord): CodeToolCallRecord[] | undefined;
34
56
  export declare function retainedCodeInputs(records: readonly SessionRecord[], parentCallId: string): {
35
57
  callId: unknown;
@@ -1,17 +1,36 @@
1
1
  import { isDeepStrictEqual } from 'node:util';
2
- import { codeCellCompletionSequence, contextGcPatchResultFromCompletedPayload } from './context-gc-replacements.js';
2
+ import { codeCellCompletionSequence, contextGcPatchResultFromCompletedPayload, contextGcPatchResultFromFailedPayload } from './context-gc-replacements.js';
3
3
  import { parseCodeCalls, parseObservations } from '../tools/code-mode/observations.js';
4
4
  /** Resolve private observation identity to real dispatch facts, not Provider tool calls. */
5
5
  export function codeObservationEvidence(records, result, observation) {
6
6
  if (observation.outcome !== 'completed')
7
7
  return undefined;
8
+ const evidence = codeToolEvidence(records, result, observation.source, 'completed');
9
+ if (!evidence)
10
+ return undefined;
11
+ return {
12
+ callId: evidence.callId,
13
+ call: evidence.call,
14
+ completed: evidence.terminal,
15
+ result: {
16
+ ...result,
17
+ payload: {
18
+ role: 'tool',
19
+ callId: evidence.callId,
20
+ content: observation.result.content,
21
+ observationId: observation.id
22
+ }
23
+ }
24
+ };
25
+ }
26
+ function codeToolEvidence(records, result, source, outcome) {
8
27
  const started = records.findLast((record) => record.sequence < result.sequence &&
9
28
  record.type === 'tool.started' &&
10
29
  isRecord(record.payload) &&
11
- record.payload.callId === observation.source.callId &&
12
- record.payload.toolName === observation.source.toolName &&
30
+ record.payload.callId === source.callId &&
31
+ record.payload.toolName === source.toolName &&
13
32
  typeof record.payload.parentCallId === 'string' &&
14
- isDeepStrictEqual(record.payload.input, observation.source.input));
33
+ isDeepStrictEqual(record.payload.input, source.input));
15
34
  if (!started || !isRecord(started.payload))
16
35
  return undefined;
17
36
  const parentId = started.payload.parentCallId;
@@ -28,16 +47,20 @@ export function codeObservationEvidence(records, result, observation) {
28
47
  return undefined;
29
48
  const outerId = original.payload.callId;
30
49
  const outer = records.findLast((record) => record.sequence < result.sequence &&
31
- record.type === 'tool.completed' &&
50
+ (record.type === 'tool.completed' ||
51
+ (outcome === 'failed' && record.type === 'tool.failed')) &&
32
52
  isRecord(record.payload) &&
33
53
  record.payload.callId === outerId &&
34
54
  (record.payload.toolName === 'code_exec' || record.payload.toolName === 'code_exec_wait'));
35
55
  if (!outer ||
36
- codeCellCompletionSequence(records, outer) === undefined ||
37
- records.some((record) => record.sequence < result.sequence &&
38
- record.type === 'tool.failed' &&
39
- isRecord(record.payload) &&
40
- record.payload.callId === outerId))
56
+ (outcome === 'completed'
57
+ ? codeCellCompletionSequence(records, outer)
58
+ : partialPatchCellTerminalSequence(records, outer)) === undefined ||
59
+ (outcome === 'completed' &&
60
+ records.some((record) => record.sequence < result.sequence &&
61
+ record.type === 'tool.failed' &&
62
+ isRecord(record.payload) &&
63
+ record.payload.callId === outerId)))
41
64
  return undefined;
42
65
  if (original.payload.callId !== parentId) {
43
66
  const initial = records.find((record) => record.sequence < outer.sequence &&
@@ -56,37 +79,44 @@ export function codeObservationEvidence(records, result, observation) {
56
79
  }
57
80
  const completed = records.findLast((record) => record.sequence > started.sequence &&
58
81
  record.sequence < result.sequence &&
59
- record.type === 'tool.completed' &&
82
+ record.type === `tool.${outcome}` &&
60
83
  isRecord(record.payload) &&
61
- record.payload.callId === observation.source.callId &&
62
- record.payload.toolName === observation.source.toolName &&
84
+ record.payload.callId === source.callId &&
85
+ record.payload.toolName === source.toolName &&
63
86
  record.payload.parentCallId === parentId);
64
87
  if (!completed ||
65
88
  records.some((record) => record.sequence > started.sequence &&
66
89
  record.sequence < result.sequence &&
67
- record.type === 'tool.failed' &&
90
+ record.type === (outcome === 'completed' ? 'tool.failed' : 'tool.completed') &&
68
91
  isRecord(record.payload) &&
69
- record.payload.callId === observation.source.callId))
92
+ record.payload.callId === source.callId))
70
93
  return undefined;
71
94
  const call = {
72
95
  ...started,
73
96
  payload: {
74
97
  role: 'tool_call',
75
- name: observation.source.toolName,
76
- callId: observation.source.callId,
77
- arguments: observation.source.input
98
+ name: source.toolName,
99
+ callId: source.callId,
100
+ arguments: source.input
78
101
  }
79
102
  };
80
- const selected = {
81
- ...result,
82
- payload: {
83
- role: 'tool',
84
- callId: observation.source.callId,
85
- content: observation.result.content,
86
- observationId: observation.id
87
- }
88
- };
89
- return { callId: observation.source.callId, call, completed, result: selected };
103
+ return { callId: source.callId, call, terminal: completed, result };
104
+ }
105
+ function partialPatchCellTerminalSequence(records, result) {
106
+ if (!isRecord(result.payload) || !isRecord(result.payload.data))
107
+ return undefined;
108
+ const data = result.payload.data;
109
+ if (typeof data.cellId !== 'string')
110
+ return undefined;
111
+ const terminal = (status) => status === 'completed' || status === 'failed' || status === 'cancelled';
112
+ if (terminal(data.status))
113
+ return result.sequence;
114
+ if (data.status !== 'running')
115
+ return undefined;
116
+ return records.findLast((record) => record.type === 'code.cell.completed' &&
117
+ isRecord(record.payload) &&
118
+ record.payload.cellId === data.cellId &&
119
+ terminal(record.payload.status))?.sequence;
90
120
  }
91
121
  export function observationsFromRecord(record) {
92
122
  return isRecord(record.payload) ? parseObservations(record.payload.observations) : undefined;
@@ -115,6 +145,15 @@ export function codeCallEvidence(records, result, entry) {
115
145
  }
116
146
  };
117
147
  }
148
+ /** Failed patch calls can prove applied file units, never successful input reclamation. */
149
+ export function codePartialPatchCallEvidence(records, result, entry) {
150
+ if (entry.status !== 'failed' || entry.toolName !== 'apply_patch')
151
+ return undefined;
152
+ const evidence = codeToolEvidence(records, result, { callId: entry.callId, toolName: entry.toolName, input: entry.input }, 'failed');
153
+ if (!evidence || !contextGcPatchResultFromFailedPayload(evidence.terminal.payload))
154
+ return undefined;
155
+ return { callId: evidence.callId, call: evidence.call, failed: evidence.terminal, result };
156
+ }
118
157
  export function callsFromRecord(record) {
119
158
  return isRecord(record.payload) ? parseCodeCalls(record.payload.calls) : undefined;
120
159
  }
@@ -46,6 +46,7 @@ export type ContextGcExecResult = {
46
46
  readonly modelContextContent: string;
47
47
  };
48
48
  export declare function contextGcPatchResultFromCompletedPayload(payload: unknown): ContextGcPatchResult | undefined;
49
+ export declare function contextGcPatchResultFromFailedPayload(payload: unknown): ContextGcPatchResult | undefined;
49
50
  export declare function contextGcApplyPatchInputReplacement(arguments_: unknown, result: ContextGcPatchResult, evidenceSequence: number, formatVersion?: 1 | 2): string;
50
51
  export declare function contextGcStaleReadReplacement(input: {
51
52
  readonly path: string;
@@ -1,4 +1,5 @@
1
1
  import { createHash } from 'node:crypto';
2
+ import { parseApplyPatchResult, parseApplyPatchPartialResult } from '../tools/shared/patch-result.js';
2
3
  const RAW_MODEL_CONTEXT_PREVIEW_CHARACTERS = 256;
3
4
  const EXEC_INPUT_COMPLETE_CHARACTERS = 512;
4
5
  const EXEC_INPUT_PREVIEW_CHARACTERS = 256;
@@ -36,45 +37,16 @@ export function contextGcCodeModeOutputReplacement(originalContent, truncated) {
36
37
  export function contextGcPatchResultFromCompletedPayload(payload) {
37
38
  if (!isRecord(payload) || payload.type !== 'tool.completed')
38
39
  return undefined;
39
- const data = payload.data;
40
- if (!isRecord(data) || !Array.isArray(data.files) || !isRecord(data.totals))
41
- return undefined;
42
- const files = data.files.flatMap((file) => isRecord(file) &&
43
- isNonemptyString(file.path) &&
44
- (file.operation === 'added' ||
45
- file.operation === 'modified' ||
46
- file.operation === 'deleted' ||
47
- file.operation === 'moved') &&
48
- (file.targetPath === undefined || isNonemptyString(file.targetPath)) &&
49
- isNonnegativeInteger(file.addedLines) &&
50
- isNonnegativeInteger(file.deletedLines)
51
- ? [
52
- {
53
- path: file.path,
54
- ...(typeof file.targetPath === 'string' ? { targetPath: file.targetPath } : {}),
55
- operation: file.operation,
56
- addedLines: file.addedLines,
57
- deletedLines: file.deletedLines
58
- }
59
- ]
60
- : []);
61
- if (files.length !== data.files.length ||
62
- files.length === 0 ||
63
- !isNonnegativeInteger(data.totals.files) ||
64
- !isNonnegativeInteger(data.totals.addedLines) ||
65
- !isNonnegativeInteger(data.totals.deletedLines) ||
66
- data.totals.files !== files.length ||
67
- data.totals.addedLines !== files.reduce((total, file) => total + file.addedLines, 0) ||
68
- data.totals.deletedLines !== files.reduce((total, file) => total + file.deletedLines, 0))
40
+ const result = parseApplyPatchResult(payload.data);
41
+ return result?.files.length ? result : undefined;
42
+ }
43
+ export function contextGcPatchResultFromFailedPayload(payload) {
44
+ if (!isRecord(payload) ||
45
+ payload.type !== 'tool.failed' ||
46
+ payload.toolName !== 'apply_patch' ||
47
+ payload.code !== 'MAR_AGENT_PATCH_PARTIAL')
69
48
  return undefined;
70
- return {
71
- files,
72
- totals: {
73
- files: data.totals.files,
74
- addedLines: data.totals.addedLines,
75
- deletedLines: data.totals.deletedLines
76
- }
77
- };
49
+ return parseApplyPatchPartialResult(payload.data);
78
50
  }
79
51
  export function contextGcApplyPatchInputReplacement(arguments_, result, evidenceSequence, formatVersion = 2) {
80
52
  const original = typeof arguments_ === 'string' ? arguments_ : stableJson(arguments_);
@@ -2,7 +2,7 @@ import { randomUUID } from 'node:crypto';
2
2
  import { MarAgentError, describeMarAgentError } from '../../error.js';
3
3
  import { AGENT_EXECUTION_POLICY } from '../../runtime/execution-policy.js';
4
4
  import { allowsToolCaller } from '../callers.js';
5
- import { boundedModelToolOutput, structuredToolEventData } from '../output-projection.js';
5
+ import { boundedModelToolOutput, structuredToolEventData, structuredToolFailureData } from '../output-projection.js';
6
6
  import { executeRegisteredTool } from '../runtime.js';
7
7
  /** Capture only the cell's immutable dispatch identity, never the model loop or its AbortSignal. */
8
8
  export function createCodeToolDispatch(input) {
@@ -56,11 +56,13 @@ export function createCodeToolDispatch(input) {
56
56
  throw error;
57
57
  }
58
58
  const diagnostic = describeMarAgentError(error, 'MAR_AGENT_HOST_OPERATION_FAILED');
59
+ const data = structuredToolFailureData(call.name, error);
59
60
  await input.emit({
60
61
  type: 'tool.failed',
61
62
  ...identity,
62
63
  code: diagnostic.code,
63
- message: diagnostic.message
64
+ message: diagnostic.message,
65
+ ...(data === undefined ? {} : { data })
64
66
  });
65
67
  throw error;
66
68
  }
@@ -15,6 +15,7 @@ import { BoundedTextOutput, renderCapturedText } from './text-output.js';
15
15
  import { TOOL_EXECUTION_LIMITS as limits } from '../execution-limits.js';
16
16
  import { createToolCallScheduler } from '../scheduler.js';
17
17
  import { detectImageMime } from '../local/view-image.js';
18
+ import { structuredToolFailureData } from '../output-projection.js';
18
19
  export const codeModeToolDefinition = {
19
20
  name: 'code_exec',
20
21
  description: `Run JavaScript to orchestrate or compose tool calls.
@@ -511,6 +512,7 @@ export class CodeModeTool {
511
512
  if (context.signal.aborted)
512
513
  return;
513
514
  const diagnostic = describeMarAgentError(error, 'MAR_AGENT_HOST_OPERATION_FAILED');
515
+ const data = structuredToolFailureData(source.toolName, error);
514
516
  recordCall({
515
517
  ...source,
516
518
  status: 'failed',
@@ -520,7 +522,12 @@ export class CodeModeTool {
520
522
  source,
521
523
  outcome: 'failed',
522
524
  result: {
523
- content: JSON.stringify({ error: diagnostic.code, message: diagnostic.message })
525
+ content: JSON.stringify({
526
+ error: diagnostic.code,
527
+ message: diagnostic.message,
528
+ ...(data === undefined ? {} : { data })
529
+ }),
530
+ ...(data === undefined ? {} : { data })
524
531
  }
525
532
  });
526
533
  await write({
@@ -132,6 +132,10 @@ export interface ApplyPatchResult {
132
132
  deletedLines: number;
133
133
  };
134
134
  }
135
+ export interface ApplyPatchPartialResult extends ApplyPatchResult {
136
+ failedFile: string;
137
+ pendingFiles: string[];
138
+ }
135
139
  export interface ReadEventData {
136
140
  mode: 'batch';
137
141
  results: Array<{
@@ -3,7 +3,7 @@ import { AGENT_EXECUTION_POLICY } from '../runtime/execution-policy.js';
3
3
  import { allowsToolCaller } from './callers.js';
4
4
  import { CodeCellExecutionError } from './code-mode/cells.js';
5
5
  import { createCodeToolDispatch } from './code-mode/dispatch.js';
6
- import { boundedModelToolOutput, structuredToolArtifacts, structuredToolEventData } from './output-projection.js';
6
+ import { boundedModelToolOutput, structuredToolArtifacts, structuredToolEventData, structuredToolFailureData } from './output-projection.js';
7
7
  import { executeRegisteredTool } from './runtime.js';
8
8
  import { scheduleToolCallBatch } from './scheduler.js';
9
9
  /** Shared raw context construction for physical tools and native calls in a real model round. */
@@ -114,6 +114,11 @@ export async function executeRoundTools(calls, scope) {
114
114
  catch (error) {
115
115
  const diagnostic = describeMarAgentError(error, 'MAR_AGENT_HOST_OPERATION_FAILED');
116
116
  const partial = error instanceof CodeCellExecutionError ? error.result : undefined;
117
+ const failureData = structuredToolFailureData(call.name, error);
118
+ const eventData = failureData ??
119
+ (partial?.data === undefined
120
+ ? undefined
121
+ : structuredToolEventData(call.name, partial.data));
117
122
  await scope.emit({
118
123
  type: 'tool.failed',
119
124
  toolName: call.name,
@@ -121,20 +126,17 @@ export async function executeRoundTools(calls, scope) {
121
126
  ...(parentCallId === undefined ? {} : { parentCallId }),
122
127
  code: diagnostic.code,
123
128
  message: diagnostic.message,
124
- ...(partial?.data === undefined
125
- ? {}
126
- : {
127
- data: structuredToolEventData(call.name, partial.data)
128
- })
129
+ ...(eventData === undefined ? {} : { data: eventData })
129
130
  });
130
131
  return {
131
132
  call,
132
133
  content: partial === undefined
133
- ? JSON.stringify({
134
+ ? boundedModelToolOutput(call.name, JSON.stringify({
134
135
  error: diagnostic.code,
135
136
  message: diagnostic.message,
137
+ ...(failureData === undefined ? {} : { data: failureData }),
136
138
  ...(error instanceof MarAgentError && error.retryable ? { retryable: true } : {})
137
- })
139
+ }), failureData).content
138
140
  : boundedModelToolOutput(call.name, partial.content, partial.data).content,
139
141
  ...(partial?.images?.length ? { images: partial.images } : {}),
140
142
  failed: true,
@@ -526,7 +526,17 @@ function partialPatchError(cause, appliedResults, pendingChanges) {
526
526
  return new MarAgentError('MAR_AGENT_PATCH_PARTIAL', `Patch partially applied. Applied: ${summarizePaths(appliedFiles)}. Failed: ${failedFile}. Pending: ${summarizePaths(pendingFiles)}. Retry only failed and pending paths.`, {
527
527
  cause,
528
528
  retryable: true,
529
- details: { appliedFiles, failedFile, pendingFiles }
529
+ details: {
530
+ appliedFiles,
531
+ failedFile,
532
+ pendingFiles,
533
+ files: appliedResults,
534
+ totals: {
535
+ files: appliedResults.length,
536
+ addedLines: appliedResults.reduce((total, file) => total + file.addedLines, 0),
537
+ deletedLines: appliedResults.reduce((total, file) => total + file.deletedLines, 0)
538
+ }
539
+ }
530
540
  });
531
541
  }
532
542
  function resultPath(result) {
@@ -1,5 +1,5 @@
1
1
  import type { SubagentTaskSnapshot } from '../subagent/contracts.js';
2
- import type { ApplyPatchResult, CodeCellEventData, ExecResult, MarAgentToolArtifact, ToolExecutionResult, ReadEventData, ViewImageResult } from './contracts.js';
2
+ import type { ApplyPatchResult, ApplyPatchPartialResult, CodeCellEventData, ExecResult, MarAgentToolArtifact, ToolExecutionResult, ReadEventData, ViewImageResult } from './contracts.js';
3
3
  import type { ToolRuntimeImage } from './runtime-context.js';
4
4
  export declare function currentExecutionImages(modelInputImages: readonly {
5
5
  readonly mimeType: string;
@@ -11,6 +11,8 @@ export declare function boundedModelToolOutput(toolName: string, value: string,
11
11
  };
12
12
  /** Project only known, validated tool result shapes into public structured events. */
13
13
  export declare function structuredToolEventData(toolName: string, value: unknown): CodeCellEventData | ExecResult | ApplyPatchResult | ReadEventData | ViewImageResult | SubagentTaskSnapshot | undefined;
14
+ /** Partial patch facts are allowlisted; arbitrary error details never become public event data. */
15
+ export declare function structuredToolFailureData(toolName: string, error: unknown): ApplyPatchPartialResult | undefined;
14
16
  export declare function structuredToolArtifacts(value: readonly MarAgentToolArtifact[] | undefined): readonly MarAgentToolArtifact[] | undefined;
15
17
  /** Shared result contract for model, programmatic, and native API callers. */
16
18
  export declare function validateToolResult(value: ToolExecutionResult): ToolExecutionResult;
@@ -1,4 +1,5 @@
1
1
  import { MarAgentError } from '../error.js';
2
+ import { parseApplyPatchResult, parseApplyPatchPartialResult } from './shared/patch-result.js';
2
3
  import { AGENT_EXECUTION_POLICY } from '../runtime/execution-policy.js';
3
4
  import { countModelTokens, countTextLines, truncateModelText } from '../runtime/token-budget.js';
4
5
  import { TOOL_EXECUTION_LIMITS } from './execution-limits.js';
@@ -127,25 +128,18 @@ export function structuredToolEventData(toolName, value) {
127
128
  isNonnegativeInteger(value.stdoutDroppedBytes) &&
128
129
  isNonnegativeInteger(value.stderrDroppedBytes))
129
130
  return value;
130
- if (toolName === 'apply_patch' &&
131
- Array.isArray(value.files) &&
132
- value.files.every((file) => isRecord(file) &&
133
- typeof file.path === 'string' &&
134
- file.path.length > 0 &&
135
- (file.targetPath === undefined || typeof file.targetPath === 'string') &&
136
- (file.operation === 'added' ||
137
- file.operation === 'modified' ||
138
- file.operation === 'deleted' ||
139
- file.operation === 'moved') &&
140
- isNonnegativeInteger(file.addedLines) &&
141
- isNonnegativeInteger(file.deletedLines)) &&
142
- isRecord(value.totals) &&
143
- isNonnegativeInteger(value.totals.files) &&
144
- isNonnegativeInteger(value.totals.addedLines) &&
145
- isNonnegativeInteger(value.totals.deletedLines))
146
- return value;
131
+ if (toolName === 'apply_patch')
132
+ return parseApplyPatchResult(value);
147
133
  return undefined;
148
134
  }
135
+ /** Partial patch facts are allowlisted; arbitrary error details never become public event data. */
136
+ export function structuredToolFailureData(toolName, error) {
137
+ return toolName === 'apply_patch' &&
138
+ error instanceof MarAgentError &&
139
+ error.code === 'MAR_AGENT_PATCH_PARTIAL'
140
+ ? parseApplyPatchPartialResult(error.details)
141
+ : undefined;
142
+ }
149
143
  export function structuredToolArtifacts(value) {
150
144
  if (value === undefined)
151
145
  return undefined;
@@ -0,0 +1,4 @@
1
+ import type { ApplyPatchPartialResult, ApplyPatchResult } from '../contracts.js';
2
+ /** Keep event projection and persisted GC evidence on the same file-result contract. */
3
+ export declare function parseApplyPatchResult(value: unknown): ApplyPatchResult | undefined;
4
+ export declare function parseApplyPatchPartialResult(value: unknown): ApplyPatchPartialResult | undefined;
@@ -0,0 +1,66 @@
1
+ import { TOOL_EXECUTION_LIMITS } from '../execution-limits.js';
2
+ /** Keep event projection and persisted GC evidence on the same file-result contract. */
3
+ export function parseApplyPatchResult(value) {
4
+ if (!isRecord(value) ||
5
+ !Array.isArray(value.files) ||
6
+ value.files.length > TOOL_EXECUTION_LIMITS.patchMaxFiles ||
7
+ !isRecord(value.totals))
8
+ return undefined;
9
+ const files = [];
10
+ for (const file of value.files) {
11
+ if (!isRecord(file) ||
12
+ !isPath(file.path) ||
13
+ (file.operation !== 'added' &&
14
+ file.operation !== 'modified' &&
15
+ file.operation !== 'deleted' &&
16
+ file.operation !== 'moved') ||
17
+ (file.targetPath !== undefined && !isPath(file.targetPath)) ||
18
+ (file.operation === 'moved' && !isPath(file.targetPath)) ||
19
+ !isCount(file.addedLines) ||
20
+ !isCount(file.deletedLines))
21
+ return undefined;
22
+ files.push({
23
+ path: file.path,
24
+ operation: file.operation,
25
+ ...(typeof file.targetPath === 'string' ? { targetPath: file.targetPath } : {}),
26
+ addedLines: file.addedLines,
27
+ deletedLines: file.deletedLines
28
+ });
29
+ }
30
+ const totals = value.totals;
31
+ if (!isCount(totals.files) ||
32
+ !isCount(totals.addedLines) ||
33
+ !isCount(totals.deletedLines) ||
34
+ totals.files !== files.length ||
35
+ totals.addedLines !== files.reduce((n, file) => n + file.addedLines, 0) ||
36
+ totals.deletedLines !== files.reduce((n, file) => n + file.deletedLines, 0))
37
+ return undefined;
38
+ return {
39
+ files,
40
+ totals: {
41
+ files: totals.files,
42
+ addedLines: totals.addedLines,
43
+ deletedLines: totals.deletedLines
44
+ }
45
+ };
46
+ }
47
+ export function parseApplyPatchPartialResult(value) {
48
+ const result = parseApplyPatchResult(value);
49
+ if (!result?.files.length ||
50
+ !isRecord(value) ||
51
+ !isPath(value.failedFile) ||
52
+ !Array.isArray(value.pendingFiles) ||
53
+ !value.pendingFiles.every(isPath) ||
54
+ result.files.length + value.pendingFiles.length + 1 > TOOL_EXECUTION_LIMITS.patchMaxFiles)
55
+ return undefined;
56
+ return { ...result, failedFile: value.failedFile, pendingFiles: [...value.pendingFiles] };
57
+ }
58
+ function isRecord(value) {
59
+ return !!value && typeof value === 'object' && !Array.isArray(value);
60
+ }
61
+ function isPath(value) {
62
+ return typeof value === 'string' && value.length > 0 && !value.includes('\0');
63
+ }
64
+ function isCount(value) {
65
+ return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
66
+ }
package/dist/index.d.ts CHANGED
@@ -37,7 +37,7 @@ export type { CoreProcessesApi, CoreProcessesBackend, CoreProcessInput, CoreProc
37
37
  export type { CreateMarAgentOptions, CreateSessionInput, ExecutionHandle, MarAgent, MarAgentSession, SessionTitleUpdateInput } from './agent-core/sdk/types.js';
38
38
  export { openMarAgentSessionCatalog, type MarAgentContextUsage, type MarAgentHistoryEntry, type MarAgentHistoryPage, type MarAgentSessionCatalog, type MarAgentSessionDeleteResult, type MarAgentSessionImage, type MarAgentSessionPage, type MarAgentSessionSummary } from './agent-core/session/catalog.js';
39
39
  export type { SessionListInput, SessionSource } from './agent-core/session/types.js';
40
- export type { ApplyPatchFileResult, ApplyPatchResult, ClientToolDefinition, MarAgentToolArtifact, MarAgentToolImageArtifact, McpCatalogTool, McpToolCallContext, McpToolCatalog, McpToolCatalogContext, ToolCall, ToolCaller, ToolExecutionContext, ToolExecutionResult } from './agent-core/tools/contracts.js';
40
+ export type { ApplyPatchFileResult, ApplyPatchResult, ApplyPatchPartialResult, ClientToolDefinition, MarAgentToolArtifact, MarAgentToolImageArtifact, McpCatalogTool, McpToolCallContext, McpToolCatalog, McpToolCatalogContext, ToolCall, ToolCaller, ToolExecutionContext, ToolExecutionResult } from './agent-core/tools/contracts.js';
41
41
  export { MCP_DEFINITION_LIMITS, assertMcpToolCatalog, assertMcpToolDefinition } from './agent-core/tools/mcp/catalog.js';
42
42
  export type { WorkspacePathMount } from './agent-core/tools/shared/path-resolver.js';
43
43
  export type { CodeModeEngine, CodeModeEngineFactory, CreateCodeModeEngine } from './agent-core/tools/code-mode/engine.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/agent",
3
- "version": "0.10.1",
3
+ "version": "0.10.2",
4
4
  "description": "Embeddable MAR coding agent SDK and CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",