@myagentroam/agent 0.9.142 → 0.9.143

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 (28) hide show
  1. package/dist/agent-core/model/anthropic-messages.js +24 -13
  2. package/dist/agent-core/model/contracts.d.ts +4 -0
  3. package/dist/agent-core/model/openai-responses.js +16 -9
  4. package/dist/agent-core/runtime/context/gc-tokens.js +4 -2
  5. package/dist/agent-core/runtime/context/gc.js +146 -13
  6. package/dist/agent-core/runtime/context/projection.js +31 -0
  7. package/dist/agent-core/runtime/events.d.ts +1 -0
  8. package/dist/agent-core/runtime/execution.js +4 -0
  9. package/dist/agent-core/session/context-gc-checkpoint.d.ts +3 -0
  10. package/dist/agent-core/session/context-gc-checkpoint.js +121 -22
  11. package/dist/agent-core/session/context-gc-observations.d.ts +38 -0
  12. package/dist/agent-core/session/context-gc-observations.js +150 -0
  13. package/dist/agent-core/session/context-gc-replacements.d.ts +1 -1
  14. package/dist/agent-core/session/context-gc-replacements.js +6 -2
  15. package/dist/agent-core/tools/code-mode/cells.d.ts +3 -0
  16. package/dist/agent-core/tools/code-mode/cells.js +59 -5
  17. package/dist/agent-core/tools/code-mode/dispatch.js +7 -4
  18. package/dist/agent-core/tools/code-mode/engine.d.ts +12 -0
  19. package/dist/agent-core/tools/code-mode/index.js +176 -13
  20. package/dist/agent-core/tools/code-mode/observations.d.ts +103 -0
  21. package/dist/agent-core/tools/code-mode/observations.js +195 -0
  22. package/dist/agent-core/tools/code-mode/quickjs-engine.js +91 -17
  23. package/dist/agent-core/tools/code-mode/worker.js +8 -1
  24. package/dist/agent-core/tools/contracts.d.ts +4 -0
  25. package/dist/agent-core/tools/execution.d.ts +2 -0
  26. package/dist/agent-core/tools/execution.js +10 -1
  27. package/dist/agent-core/tools/output-projection.js +35 -1
  28. package/package.json +1 -1
@@ -1,6 +1,7 @@
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
5
  import { parseReadContextOutput } from '../tools/local/read-context.js';
5
6
  import { codeCellCompletionSequence, contextGcApplyPatchInputReplacement, contextGcCodeModeInputReplacement, contextGcCodeModeOutputReplacement, contextGcExecInputReplacement, contextGcExecOutputReplacement, contextGcPatchResultFromCompletedPayload, contextGcStaleReadReplacement, contextGcWebOutputReplacement, normalizeContextGcCodeModeInput, normalizeContextGcExecResult } from './context-gc-replacements.js';
6
7
  import { contextGcSubagentNeedsRetainedPriorFinal, contextGcSubagentOutputReplacement, contextGcSubagentSnapshotIsPreciselyCovered, contextGcSubagentSnapshotIsSuperseded, normalizeContextGcSubagentSnapshot } from './context-gc-subagent.js';
@@ -57,7 +58,7 @@ export function contextGcLogicalPathKey(path, workspace, platform) {
57
58
  return resolved;
58
59
  }
59
60
  export function contextGcTargetKey(target) {
60
- return `${target.sequence}:${target.recordId}:${target.role}:${target.callId ?? ''}:${target.toolName ?? ''}`;
61
+ return `${target.sequence}:${target.recordId}:${target.role}:${target.callId ?? ''}:${target.toolName ?? ''}${target.observationId ? `:${target.observationId}` : ''}`;
61
62
  }
62
63
  export function contextGcReplacementKey(replacement) {
63
64
  return contextGcTargetKey(replacement.target);
@@ -154,6 +155,8 @@ export function validateContextGcCheckpoint(records, record, workspace = session
154
155
  if (contextGcSourceProjectionHash(records, payload.boundarySequence, payload.base) !==
155
156
  payload.sourceProjectionHash)
156
157
  return undefined;
158
+ // Later cell completion cannot retroactively authorize an earlier checkpoint.
159
+ records = records.filter((item) => item.sequence <= payload.boundarySequence);
157
160
  const recordsBySequence = new Map(records.map((item) => [item.sequence, item]));
158
161
  if (!triggerMatchesRecords(records, recordsBySequence, payload))
159
162
  return undefined;
@@ -225,13 +228,39 @@ function targetMatchesRecord(target, record) {
225
228
  const messageCallId = modelContextCallId(record.payload);
226
229
  const messageToolName = modelContextToolName(record.payload);
227
230
  return ((target.callId === undefined || target.callId === messageCallId) &&
231
+ (target.observationId === undefined ||
232
+ observationsFromRecord(record)?.some((value) => value.id === target.observationId) ===
233
+ true) &&
228
234
  (target.toolName === undefined || target.toolName === messageToolName));
229
235
  }
230
236
  /** A checkpoint draft is valid only when its replacements match the source records and evidence. */
231
237
  function replacementMatchesRecords(replacement, records, recordsBySequence, workspace, platform) {
232
- const source = recordsBySequence.get(replacement.target.sequence);
238
+ let source = recordsBySequence.get(replacement.target.sequence);
233
239
  if (!source || !isRecord(source.payload))
234
240
  return false;
241
+ if (replacement.target.observationId) {
242
+ const selected = observationsFromRecord(source)?.find((value) => value.id === replacement.target.observationId);
243
+ if (!selected)
244
+ return false;
245
+ if (selected.type === 'tool_result') {
246
+ const evidence = codeObservationEvidence(records, source, selected);
247
+ if (!evidence)
248
+ return false;
249
+ source = {
250
+ ...source,
251
+ payload: {
252
+ ...source.payload,
253
+ callId: selected.source.callId,
254
+ content: selected.result.content
255
+ }
256
+ };
257
+ }
258
+ else
259
+ source = { ...source, payload: { ...source.payload, content: selected.value } };
260
+ recordsBySequence = new Map(recordsBySequence).set(source.sequence, source);
261
+ }
262
+ if (!isRecord(source.payload))
263
+ return false;
235
264
  if (replacement.kind === 'READ_OUTPUT_ITEMS') {
236
265
  if (!workspace || toolNameForCallId(records, replacement.callId, source.sequence) !== 'read')
237
266
  return false;
@@ -243,7 +272,7 @@ function replacementMatchesRecords(replacement, records, recordsBySequence, work
243
272
  const originalItems = new Map(parsed.items.map((item) => [item.index, item]));
244
273
  return replacement.items.every((item) => {
245
274
  const original = originalItems.get(item.index);
246
- const evidence = successfulPatchEvidenceForPath(records, recordsBySequence, item.evidenceSequence, item.path, workspace, platform);
275
+ const evidence = successfulPatchEvidenceForPath(records, recordsBySequence, item.evidenceSequence, item.path, workspace, platform, item.supersededByCallId);
247
276
  return (original?.pathTruncated === false &&
248
277
  original?.path === item.path &&
249
278
  evidence !== undefined &&
@@ -274,7 +303,9 @@ function replacementMatchesRecords(replacement, records, recordsBySequence, work
274
303
  codeCellCompletionSequence(records, evidence.completed) !== undefined &&
275
304
  evidence.call.sequence === replacement.target.sequence &&
276
305
  input !== undefined &&
277
- isDeepStrictEqual(replacement.replacementArguments, contextGcCodeModeInputReplacement(input)));
306
+ isDeepStrictEqual(replacement.replacementArguments, contextGcCodeModeInputReplacement(input, observationsFromRecord(evidence.result)
307
+ ? retainedCodeInputs(records, replacement.callId)
308
+ : [])));
278
309
  }
279
310
  if (replacement.kind === 'CODE_MODE_OUTPUT') {
280
311
  const evidence = ['code_mode', 'code_exec', 'code_exec_wait']
@@ -288,12 +319,16 @@ function replacementMatchesRecords(replacement, records, recordsBySequence, work
288
319
  isRecord(evidence.completed.payload) &&
289
320
  typeof evidence.completed.payload.truncated === 'boolean' &&
290
321
  replacement.replacementContent ===
291
- contextGcCodeModeOutputReplacement(evidence.result.payload.content, evidence.completed.payload.truncated));
322
+ contextGcCodeModeOutputReplacement(source.payload.content, replacement.target.observationId
323
+ ? observationsFromRecord(source)?.some((value) => value.id === replacement.target.observationId &&
324
+ value.type === 'value' &&
325
+ value.truncated === true) === true
326
+ : evidence.completed.payload.truncated));
292
327
  }
293
328
  if (replacement.kind === 'SUBAGENT_OUTPUT') {
294
329
  const retainedPriorFinal = replacement.retainedPriorFinal;
295
330
  const sourceEvidence = successfulToolEvidence(records, recordsBySequence, source.sequence, replacement.toolName);
296
- const terminalEvidence = successfulToolEvidence(records, recordsBySequence, replacement.evidenceSequence, replacement.terminalToolName);
331
+ const terminalEvidence = successfulToolEvidence(records, recordsBySequence, replacement.evidenceSequence, replacement.terminalToolName, replacement.terminalCallId);
297
332
  if (!sourceEvidence ||
298
333
  sourceEvidence.callId !== replacement.callId ||
299
334
  !terminalEvidence ||
@@ -304,7 +339,7 @@ function replacementMatchesRecords(replacement, records, recordsBySequence, work
304
339
  let retainedPriorFinalEvidence;
305
340
  let retainedPriorFinalSnapshot;
306
341
  if (retainedPriorFinal !== undefined) {
307
- retainedPriorFinalEvidence = successfulToolEvidence(records, recordsBySequence, retainedPriorFinal.evidenceSequence, retainedPriorFinal.toolName);
342
+ retainedPriorFinalEvidence = successfulToolEvidence(records, recordsBySequence, retainedPriorFinal.evidenceSequence, retainedPriorFinal.toolName, retainedPriorFinal.callId);
308
343
  if (!retainedPriorFinalEvidence ||
309
344
  retainedPriorFinalEvidence.callId !== retainedPriorFinal.callId ||
310
345
  !isRecord(retainedPriorFinalEvidence.result.payload))
@@ -319,7 +354,7 @@ function replacementMatchesRecords(replacement, records, recordsBySequence, work
319
354
  (retainedPriorFinal !== undefined) &&
320
355
  (retainedPriorFinalEvidence === undefined ||
321
356
  retainedPriorFinalEvidence.result.sequence < sourceEvidence.result.sequence) &&
322
- sourceEvidence.result.sequence < terminalEvidence.result.sequence &&
357
+ sourceEvidence.completed.sequence < terminalEvidence.completed.sequence &&
323
358
  (contextGcSubagentSnapshotIsSuperseded(sourceSnapshot, terminalSnapshot, retainedPriorFinalSnapshot) ||
324
359
  contextGcSubagentSnapshotIsPreciselyCovered(sourceSnapshot, terminalSnapshot)) &&
325
360
  replacement.replacementContent ===
@@ -351,8 +386,8 @@ function replacementMatchesRecords(replacement, records, recordsBySequence, work
351
386
  return false;
352
387
  if (replacement.kind === 'TOOL_CALL_INPUT')
353
388
  return isDeepStrictEqual(replacement.replacementArguments, contextGcExecInputReplacement(source.payload.arguments, execInputReplacementFormatVersion(replacement.replacementArguments), replacement.toolName));
354
- const targetEvidence = successfulToolEvidence(records, recordsBySequence, source.sequence, 'exec') ??
355
- successfulToolEvidence(records, recordsBySequence, source.sequence, 'exec_wait');
389
+ const targetEvidence = successfulToolEvidence(records, recordsBySequence, source.sequence, 'exec', replacement.callId) ??
390
+ successfulToolEvidence(records, recordsBySequence, source.sequence, 'exec_wait', replacement.callId);
356
391
  const result = normalizeContextGcExecResult(source.payload.content, targetEvidence?.completed.payload);
357
392
  return (result !== undefined &&
358
393
  replacement.replacementContent ===
@@ -401,7 +436,7 @@ function toolNameForCallId(records, callId, beforeSequence) {
401
436
  ? call.payload.name
402
437
  : undefined;
403
438
  }
404
- function successfulToolEvidence(records, recordsBySequence, evidenceSequence, toolName) {
439
+ function successfulToolEvidence(records, recordsBySequence, evidenceSequence, toolName, expectedCallId) {
405
440
  const result = recordsBySequence.get(evidenceSequence);
406
441
  if (result?.type !== 'model.context' ||
407
442
  !isRecord(result.payload) ||
@@ -410,8 +445,28 @@ function successfulToolEvidence(records, recordsBySequence, evidenceSequence, to
410
445
  return undefined;
411
446
  const callId = result.payload.callId;
412
447
  const call = modelToolCallForCallId(records, callId, result.sequence);
413
- if (!call || !isRecord(call.payload) || call.payload.name !== toolName)
448
+ if (!call ||
449
+ !isRecord(call.payload) ||
450
+ call.payload.name !== toolName ||
451
+ (expectedCallId && expectedCallId !== callId)) {
452
+ for (const observation of observationsFromRecord(result) ?? []) {
453
+ if (observation.type !== 'tool_result' ||
454
+ observation.source.toolName !== toolName ||
455
+ (expectedCallId && observation.source.callId !== expectedCallId))
456
+ continue;
457
+ const evidence = codeObservationEvidence(records, result, observation);
458
+ if (evidence)
459
+ return evidence;
460
+ }
461
+ for (const entry of callsFromRecord(result) ?? []) {
462
+ if (entry.toolName !== toolName || (expectedCallId && entry.callId !== expectedCallId))
463
+ continue;
464
+ const evidence = codeCallEvidence(records, result, entry);
465
+ if (evidence)
466
+ return evidence;
467
+ }
414
468
  return undefined;
469
+ }
415
470
  const completed = records.findLast((record) => record.sequence > call.sequence &&
416
471
  record.sequence < result.sequence &&
417
472
  record.type === 'tool.completed' &&
@@ -441,11 +496,29 @@ function hasFailedToolEvidence(records, call, result, toolName) {
441
496
  record.payload.toolName === toolName);
442
497
  }
443
498
  function modelToolCallForCallId(records, callId, beforeSequence) {
444
- return records.findLast((record) => record.sequence < beforeSequence &&
499
+ const direct = records.findLast((record) => record.sequence < beforeSequence &&
445
500
  record.type === 'model.context' &&
446
501
  isRecord(record.payload) &&
447
502
  record.payload.role === 'tool_call' &&
448
503
  record.payload.callId === callId);
504
+ if (direct)
505
+ return direct;
506
+ const started = records.findLast((record) => record.sequence < beforeSequence &&
507
+ record.type === 'tool.started' &&
508
+ isRecord(record.payload) &&
509
+ record.payload.callId === callId &&
510
+ typeof record.payload.parentCallId === 'string');
511
+ return started && isRecord(started.payload)
512
+ ? {
513
+ ...started,
514
+ payload: {
515
+ role: 'tool_call',
516
+ name: started.payload.toolName,
517
+ callId,
518
+ arguments: started.payload.input
519
+ }
520
+ }
521
+ : undefined;
449
522
  }
450
523
  function modelToolResultForCall(records, call, beforeSequence) {
451
524
  if (!isRecord(call.payload) || typeof call.payload.callId !== 'string')
@@ -458,8 +531,8 @@ function modelToolResultForCall(records, call, beforeSequence) {
458
531
  record.payload.role === 'tool' &&
459
532
  record.payload.callId === callId);
460
533
  }
461
- function successfulPatchEvidenceForPath(records, recordsBySequence, evidenceSequence, path, workspace, platform) {
462
- const evidence = successfulToolEvidence(records, recordsBySequence, evidenceSequence, 'apply_patch');
534
+ function successfulPatchEvidenceForPath(records, recordsBySequence, evidenceSequence, path, workspace, platform, expectedCallId) {
535
+ const evidence = successfulToolEvidence(records, recordsBySequence, evidenceSequence, 'apply_patch', expectedCallId);
463
536
  if (!evidence || !isRecord(evidence.completed.payload))
464
537
  return undefined;
465
538
  const result = contextGcPatchResultFromCompletedPayload(evidence.completed.payload);
@@ -475,8 +548,9 @@ function successfulPatchEvidenceForPath(records, recordsBySequence, evidenceSequ
475
548
  : undefined;
476
549
  }
477
550
  function isSuccessfulExecLifecycleEvidence(replacement, source, records, recordsBySequence) {
478
- const evidence = successfulToolEvidence(records, recordsBySequence, replacement.evidenceSequence, 'exec') ??
479
- successfulToolEvidence(records, recordsBySequence, replacement.evidenceSequence, 'exec_wait');
551
+ const expectedTerminal = replacement.kind === 'EXEC_OUTPUT' ? replacement.terminalCallId : undefined;
552
+ const evidence = successfulToolEvidence(records, recordsBySequence, replacement.evidenceSequence, 'exec', expectedTerminal) ??
553
+ successfulToolEvidence(records, recordsBySequence, replacement.evidenceSequence, 'exec_wait', expectedTerminal);
480
554
  if (!evidence || !isRecord(evidence.call.payload) || !isRecord(evidence.result.payload))
481
555
  return false;
482
556
  const finalResult = normalizeContextGcExecResult(evidence.result.payload.content, evidence.completed.payload);
@@ -497,7 +571,7 @@ function isSuccessfulExecLifecycleEvidence(replacement, source, records, records
497
571
  : modelToolResultForCall(records, targetCall, evidence.result.sequence);
498
572
  if (!targetResult)
499
573
  return false;
500
- const targetEvidence = successfulToolEvidence(records, recordsBySequence, targetResult.sequence, targetCall.payload.name);
574
+ const targetEvidence = successfulToolEvidence(records, recordsBySequence, targetResult.sequence, targetCall.payload.name, replacement.callId);
501
575
  if (!targetEvidence || targetEvidence.call.sequence !== targetCall.sequence)
502
576
  return false;
503
577
  if (targetEvidence.callId === evidence.callId)
@@ -597,7 +671,7 @@ function isContextGcReplacement(value) {
597
671
  if (!isRecord(value) ||
598
672
  !isContextGcTarget(value.target) ||
599
673
  !isNonemptyString(value.callId) ||
600
- value.target.callId !== value.callId)
674
+ (value.target.callId !== value.callId && value.target.observationId === undefined))
601
675
  return false;
602
676
  if (value.kind === 'READ_OUTPUT_ITEMS') {
603
677
  const items = value.items;
@@ -607,7 +681,14 @@ function isContextGcReplacement(value) {
607
681
  Array.isArray(items) &&
608
682
  items.length > 0 &&
609
683
  items.every((item) => isRecord(item) &&
610
- hasOnlyKeys(item, ['index', 'path', 'replacement', 'evidenceSequence']) &&
684
+ hasOnlyKeys(item, [
685
+ 'index',
686
+ 'path',
687
+ 'replacement',
688
+ 'evidenceSequence',
689
+ 'supersededByCallId'
690
+ ]) &&
691
+ (item.supersededByCallId === undefined || isNonemptyString(item.supersededByCallId)) &&
611
692
  isNonnegativeInteger(item.index) &&
612
693
  isNonemptyString(item.path) &&
613
694
  isNonemptyString(item.replacement) &&
@@ -697,14 +778,32 @@ function isContextGcReplacement(value) {
697
778
  isNonemptyString(value.replacementContent) &&
698
779
  isPositiveInteger(value.evidenceSequence));
699
780
  return (value.kind === 'EXEC_OUTPUT' &&
700
- hasOnlyKeys(value, ['kind', 'callId', 'target', 'replacementContent', 'evidenceSequence']) &&
781
+ hasOnlyKeys(value, [
782
+ 'kind',
783
+ 'callId',
784
+ 'target',
785
+ 'replacementContent',
786
+ 'evidenceSequence',
787
+ 'terminalCallId'
788
+ ]) &&
789
+ (value.terminalCallId === undefined || isNonemptyString(value.terminalCallId)) &&
701
790
  value.target.role === 'tool' &&
702
791
  isNonemptyString(value.replacementContent) &&
703
792
  isPositiveInteger(value.evidenceSequence));
704
793
  }
705
794
  function isContextGcTarget(value) {
706
795
  return (isRecord(value) &&
707
- hasOnlyKeys(value, ['sequence', 'recordId', 'role', 'callId', 'toolName', 'sourceHash']) &&
796
+ hasOnlyKeys(value, [
797
+ 'sequence',
798
+ 'recordId',
799
+ 'role',
800
+ 'callId',
801
+ 'toolName',
802
+ 'sourceHash',
803
+ 'observationId'
804
+ ]) &&
805
+ (value.observationId === undefined ||
806
+ (value.role === 'tool' && isNonemptyString(value.observationId))) &&
708
807
  isPositiveInteger(value.sequence) &&
709
808
  isNonemptyString(value.recordId) &&
710
809
  (value.role === 'tool_call' || value.role === 'tool' || value.role === 'provider') &&
@@ -0,0 +1,38 @@
1
+ import type { SessionRecord } from './jsonl-store.js';
2
+ import { type CodeObservation, type CodeToolCallRecord } from '../tools/code-mode/observations.js';
3
+ /** Resolve private observation identity to real dispatch facts, not Provider tool calls. */
4
+ export declare function codeObservationEvidence(records: readonly SessionRecord[], result: SessionRecord, observation: Extract<CodeObservation, {
5
+ type: 'tool_result';
6
+ }>): {
7
+ callId: string;
8
+ call: SessionRecord;
9
+ completed: SessionRecord;
10
+ result: SessionRecord;
11
+ } | undefined;
12
+ export declare function observationsFromRecord(record: SessionRecord): CodeObservation[] | undefined;
13
+ export declare function codeCallEvidence(records: readonly SessionRecord[], result: SessionRecord, entry: CodeToolCallRecord): {
14
+ result: {
15
+ payload: {
16
+ role: string;
17
+ callId: string;
18
+ content: string;
19
+ };
20
+ schemaVersion: 1;
21
+ recordId: string;
22
+ sequence: number;
23
+ timestamp: string;
24
+ sessionId: string;
25
+ executionId?: string;
26
+ turnId?: string;
27
+ type: string;
28
+ };
29
+ callId: string;
30
+ call: SessionRecord;
31
+ completed: SessionRecord;
32
+ } | undefined;
33
+ export declare function callsFromRecord(record: SessionRecord): CodeToolCallRecord[] | undefined;
34
+ export declare function retainedCodeInputs(records: readonly SessionRecord[], parentCallId: string): {
35
+ callId: unknown;
36
+ toolName: unknown;
37
+ input: unknown;
38
+ }[];
@@ -0,0 +1,150 @@
1
+ import { isDeepStrictEqual } from 'node:util';
2
+ import { codeCellCompletionSequence, contextGcPatchResultFromCompletedPayload } from './context-gc-replacements.js';
3
+ import { parseCodeCalls, parseObservations } from '../tools/code-mode/observations.js';
4
+ /** Resolve private observation identity to real dispatch facts, not Provider tool calls. */
5
+ export function codeObservationEvidence(records, result, observation) {
6
+ if (observation.outcome !== 'completed')
7
+ return undefined;
8
+ const started = records.findLast((record) => record.sequence < result.sequence &&
9
+ record.type === 'tool.started' &&
10
+ isRecord(record.payload) &&
11
+ record.payload.callId === observation.source.callId &&
12
+ record.payload.toolName === observation.source.toolName &&
13
+ typeof record.payload.parentCallId === 'string' &&
14
+ isDeepStrictEqual(record.payload.input, observation.source.input));
15
+ if (!started || !isRecord(started.payload))
16
+ return undefined;
17
+ const parentId = started.payload.parentCallId;
18
+ const parent = records.find((record) => record.type === 'model.context' &&
19
+ record.sequence < started.sequence &&
20
+ isRecord(record.payload) &&
21
+ record.payload.role === 'tool_call' &&
22
+ record.payload.callId === parentId &&
23
+ (record.payload.name === 'code_exec' || record.payload.name === 'code_mode'));
24
+ if (!parent)
25
+ return undefined;
26
+ const original = records.find((record) => record.recordId === result.recordId) ?? result;
27
+ if (!isRecord(original.payload))
28
+ return undefined;
29
+ const outerId = original.payload.callId;
30
+ const outer = records.findLast((record) => record.sequence < result.sequence &&
31
+ record.type === 'tool.completed' &&
32
+ isRecord(record.payload) &&
33
+ record.payload.callId === outerId &&
34
+ (record.payload.toolName === 'code_exec' ||
35
+ record.payload.toolName === 'code_mode' ||
36
+ record.payload.toolName === 'code_exec_wait'));
37
+ if (!outer ||
38
+ codeCellCompletionSequence(records, outer) === undefined ||
39
+ records.some((record) => record.sequence < result.sequence &&
40
+ record.type === 'tool.failed' &&
41
+ isRecord(record.payload) &&
42
+ record.payload.callId === outerId))
43
+ return undefined;
44
+ if (original.payload.callId !== parentId) {
45
+ const initial = records.find((record) => record.sequence < outer.sequence &&
46
+ record.type === 'tool.completed' &&
47
+ isRecord(record.payload) &&
48
+ record.payload.callId === parentId);
49
+ if (!initial ||
50
+ !isRecord(outer.payload) ||
51
+ outer.payload.toolName !== 'code_exec_wait' ||
52
+ !isRecord(initial.payload) ||
53
+ !isRecord(outer.payload.data) ||
54
+ !isRecord(initial.payload.data) ||
55
+ typeof outer.payload.data.cellId !== 'string' ||
56
+ outer.payload.data.cellId !== initial.payload.data.cellId)
57
+ return undefined;
58
+ }
59
+ const completed = records.findLast((record) => record.sequence > started.sequence &&
60
+ record.sequence < result.sequence &&
61
+ record.type === 'tool.completed' &&
62
+ isRecord(record.payload) &&
63
+ record.payload.callId === observation.source.callId &&
64
+ record.payload.toolName === observation.source.toolName &&
65
+ record.payload.parentCallId === parentId);
66
+ if (!completed ||
67
+ records.some((record) => record.sequence > started.sequence &&
68
+ record.sequence < result.sequence &&
69
+ record.type === 'tool.failed' &&
70
+ isRecord(record.payload) &&
71
+ record.payload.callId === observation.source.callId))
72
+ return undefined;
73
+ const call = {
74
+ ...started,
75
+ payload: {
76
+ role: 'tool_call',
77
+ name: observation.source.toolName,
78
+ callId: observation.source.callId,
79
+ arguments: observation.source.input
80
+ }
81
+ };
82
+ const selected = {
83
+ ...result,
84
+ payload: {
85
+ role: 'tool',
86
+ callId: observation.source.callId,
87
+ content: observation.result.content,
88
+ observationId: observation.id
89
+ }
90
+ };
91
+ return { callId: observation.source.callId, call, completed, result: selected };
92
+ }
93
+ export function observationsFromRecord(record) {
94
+ return isRecord(record.payload) ? parseObservations(record.payload.observations) : undefined;
95
+ }
96
+ export function codeCallEvidence(records, result, entry) {
97
+ if (entry.status !== 'completed')
98
+ return undefined;
99
+ const evidence = codeObservationEvidence(records, result, {
100
+ type: 'tool_result',
101
+ id: `call:${entry.callId}`,
102
+ source: { callId: entry.callId, toolName: entry.toolName, input: entry.input },
103
+ outcome: 'completed',
104
+ result: { content: '' }
105
+ });
106
+ if (!evidence || !isRecord(evidence.completed.payload))
107
+ return undefined;
108
+ return {
109
+ ...evidence,
110
+ result: {
111
+ ...evidence.result,
112
+ payload: {
113
+ role: 'tool',
114
+ callId: entry.callId,
115
+ content: JSON.stringify(evidence.completed.payload.data ?? {})
116
+ }
117
+ }
118
+ };
119
+ }
120
+ export function callsFromRecord(record) {
121
+ return isRecord(record.payload) ? parseCodeCalls(record.payload.calls) : undefined;
122
+ }
123
+ export function retainedCodeInputs(records, parentCallId) {
124
+ return records.flatMap((record) => {
125
+ if (!isRecord(record.payload) ||
126
+ record.type !== 'tool.started' ||
127
+ record.payload.parentCallId !== parentCallId)
128
+ return [];
129
+ const call = record.payload;
130
+ const completedPatch = call.toolName === 'apply_patch' &&
131
+ records.some((next) => next.sequence > record.sequence &&
132
+ next.type === 'tool.completed' &&
133
+ isRecord(next.payload) &&
134
+ next.payload.callId === call.callId &&
135
+ next.payload.toolName === 'apply_patch' &&
136
+ next.payload.parentCallId === parentCallId &&
137
+ contextGcPatchResultFromCompletedPayload(next.payload) !== undefined) &&
138
+ !records.some((next) => next.sequence > record.sequence &&
139
+ next.type === 'tool.failed' &&
140
+ isRecord(next.payload) &&
141
+ next.payload.callId === call.callId);
142
+ // Successful patches have durable file evidence; other inputs retain their direct-mode semantics.
143
+ return completedPatch
144
+ ? []
145
+ : [{ callId: call.callId, toolName: call.toolName, input: call.input }];
146
+ });
147
+ }
148
+ function isRecord(value) {
149
+ return !!value && typeof value === 'object' && !Array.isArray(value);
150
+ }
@@ -4,7 +4,7 @@ export interface ContextGcCodeModeInput {
4
4
  readonly inputMode: 'freeform' | 'json';
5
5
  }
6
6
  export declare function normalizeContextGcCodeModeInput(arguments_: unknown): ContextGcCodeModeInput | undefined;
7
- export declare function contextGcCodeModeInputReplacement(input: ContextGcCodeModeInput): unknown;
7
+ export declare function contextGcCodeModeInputReplacement(input: ContextGcCodeModeInput, retainedInputs?: readonly unknown[]): unknown;
8
8
  export declare function contextGcCodeModeOutputReplacement(originalContent: string, truncated: boolean): string;
9
9
  export interface ContextGcPatchFile {
10
10
  readonly path: string;
@@ -14,9 +14,13 @@ export function normalizeContextGcCodeModeInput(arguments_) {
14
14
  return { code: arguments_.code, inputMode: 'json' };
15
15
  return undefined;
16
16
  }
17
- export function contextGcCodeModeInputReplacement(input) {
17
+ export function contextGcCodeModeInputReplacement(input, retainedInputs = []) {
18
18
  const replacement = `<context_gc kind="completed_code_mode_input" originalBytes="${Buffer.byteLength(input.code)}" lines="${lineCount(input.code)}" inputMode="${input.inputMode}">Completed Code Mode source was reclaimed after six successful rollouts. The paired output, images, completion evidence, and nested tool side effects remain in history.</context_gc>`;
19
- return input.inputMode === 'freeform' ? replacement : { code: replacement };
19
+ const content = replacement +
20
+ (retainedInputs.length
21
+ ? `\nRetained tool instructions: ${JSON.stringify(retainedInputs)}`
22
+ : '');
23
+ return input.inputMode === 'freeform' ? content : { code: content };
20
24
  }
21
25
  export function contextGcCodeModeOutputReplacement(originalContent, truncated) {
22
26
  const preview = unicodeHeadTail(originalContent, CODE_MODE_OUTPUT_PREVIEW_CHARACTERS);
@@ -1,6 +1,9 @@
1
1
  import { MarAgentError } from '../../error.js';
2
2
  import type { ToolExecutionContext, ToolExecutionResult } from '../contracts.js';
3
+ import { type CodeObservation } from './observations.js';
3
4
  export interface CellOutput {
5
+ call(value: import('./observations.js').CodeToolCallRecord): void;
6
+ observe(value: CodeObservation): void;
4
7
  text(value: string, originalBytes?: number): void;
5
8
  image(value: NonNullable<ToolExecutionResult['images']>[number]): void;
6
9
  yield(): void;
@@ -3,6 +3,7 @@ import { MarAgentError, describeMarAgentError } from '../../error.js';
3
3
  import { truncateModelText } from '../../runtime/token-budget.js';
4
4
  import { TOOL_EXECUTION_LIMITS as limits } from '../execution-limits.js';
5
5
  import { BoundedTextOutput, renderCapturedText } from './text-output.js';
6
+ import { boundObservations, selectObservationImages, newObservationId, visibleObservation } from './observations.js';
6
7
  /** Keeps a failed tool call observable without discarding earlier explicit output. */
7
8
  export class CodeCellExecutionError extends MarAgentError {
8
9
  result;
@@ -88,6 +89,9 @@ export class CodeCells {
88
89
  status: 'running',
89
90
  text: new BoundedTextOutput(limits.codeMaxOutputBytes),
90
91
  images: [],
92
+ observations: [],
93
+ calls: new Map(),
94
+ imageOffset: 0,
91
95
  yielded: false,
92
96
  truncated: false,
93
97
  listeners: new Set(),
@@ -104,6 +108,8 @@ export class CodeCells {
104
108
  .then(async () => {
105
109
  releaseSession = await context.codeMode?.retainSession?.();
106
110
  return run(source, { ...context, signal: controller.signal }, {
111
+ call: (value) => cell.calls.set(value.callId, value),
112
+ observe: (value) => cell.observations.push(value),
107
113
  text: (text, originalBytes) => cell.text.append(text, originalBytes),
108
114
  image: (image) => cell.images.push(image),
109
115
  yield: () => {
@@ -199,6 +205,31 @@ export class CodeCells {
199
205
  if (!supportsImages && (cell.images.length || cell.terminal?.images?.length))
200
206
  throw new MarAgentError('MAR_AGENT_INPUT_CAPABILITY_UNSUPPORTED', 'The current model cannot receive this cell image output.');
201
207
  if (cell.terminal) {
208
+ if (cell.terminal.observations) {
209
+ const bounded = boundObservations(cell.terminal.observations, input.maxOutputTokens);
210
+ const visible = selectObservationImages(bounded.observations, cell.terminal.images ?? []);
211
+ const original = cell.terminal.data;
212
+ const data = {
213
+ ...original,
214
+ output: truncateModelText(original.output ?? '', input.maxOutputTokens).content,
215
+ observations: visible.observations,
216
+ cached: true,
217
+ truncated: cell.terminal.truncated === true || bounded.truncated
218
+ };
219
+ return {
220
+ ...cell.terminal,
221
+ observations: visible.observations,
222
+ images: visible.images,
223
+ content: JSON.stringify({
224
+ ...data,
225
+ output: undefined,
226
+ calls: undefined,
227
+ observations: visible.observations.map(visibleObservation)
228
+ }),
229
+ data,
230
+ truncated: data.truncated
231
+ };
232
+ }
202
233
  const snapshot = cell.terminal.data;
203
234
  const bounded = truncateModelText(snapshot.output, input.maxOutputTokens);
204
235
  const data = {
@@ -219,7 +250,21 @@ export class CodeCells {
219
250
  const text = renderCapturedText(captured, images.length);
220
251
  cell.yielded = false;
221
252
  const bounded = truncateModelText(text, input.maxOutputTokens);
222
- cell.truncated ||= captured.droppedBytes > 0 || bounded.truncated;
253
+ const pending = cell.observations.splice(0);
254
+ if (!pending.length && captured.text)
255
+ pending.push({
256
+ type: 'value',
257
+ id: newObservationId(),
258
+ format: 'text',
259
+ value: captured.text,
260
+ truncated: captured.droppedBytes > 0
261
+ });
262
+ const structured = boundObservations(pending, input.maxOutputTokens);
263
+ const visible = selectObservationImages(structured.observations, images, cell.imageOffset);
264
+ const remapped = visible.observations;
265
+ const visibleImages = visible.images;
266
+ cell.imageOffset += images.length;
267
+ cell.truncated ||= captured.droppedBytes > 0 || bounded.truncated || structured.truncated;
223
268
  const error = cell.error === undefined
224
269
  ? undefined
225
270
  : describeMarAgentError(cell.error, 'MAR_AGENT_CODE_FAILED');
@@ -227,6 +272,8 @@ export class CodeCells {
227
272
  cellId: cell.cellId,
228
273
  status: cell.status,
229
274
  output: bounded.content,
275
+ observations: remapped,
276
+ calls: [...cell.calls.values()],
230
277
  ...cell.result?.data,
231
278
  outputBytes: captured.originalBytes,
232
279
  outputDroppedBytes: captured.droppedBytes,
@@ -235,10 +282,17 @@ export class CodeCells {
235
282
  cached: false
236
283
  };
237
284
  const result = {
238
- content: JSON.stringify(data),
285
+ content: JSON.stringify({
286
+ ...data,
287
+ output: undefined,
288
+ calls: undefined,
289
+ observations: remapped.map(visibleObservation)
290
+ }),
291
+ calls: [...cell.calls.values()],
292
+ observations: remapped,
239
293
  data,
240
- truncated: cell.truncated,
241
- ...(images.length ? { images } : {})
294
+ truncated: cell.truncated || structured.truncated,
295
+ ...(visibleImages.length ? { images: visibleImages } : {})
242
296
  };
243
297
  if (cell.status !== 'running')
244
298
  cell.terminal = result;
@@ -246,7 +300,7 @@ export class CodeCells {
246
300
  if (initial && cell.status !== 'running') {
247
301
  if (cell.error !== undefined)
248
302
  throw new CodeCellExecutionError(cell.error, result);
249
- return { ...result, content: bounded.content || 'Code Exec completed without emitted text.' };
303
+ return result;
250
304
  }
251
305
  return result;
252
306
  }