@myagentroam/agent 0.9.142 → 0.9.144

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 (31) 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/async-queue.js +7 -5
  5. package/dist/agent-core/runtime/context/gc-tokens.js +4 -2
  6. package/dist/agent-core/runtime/context/gc.js +147 -14
  7. package/dist/agent-core/runtime/context/projection.js +32 -1
  8. package/dist/agent-core/runtime/events.d.ts +1 -0
  9. package/dist/agent-core/runtime/execution.js +4 -0
  10. package/dist/agent-core/sdk/session.js +1 -1
  11. package/dist/agent-core/session/context-gc-checkpoint.d.ts +4 -1
  12. package/dist/agent-core/session/context-gc-checkpoint.js +123 -26
  13. package/dist/agent-core/session/context-gc-observations.d.ts +38 -0
  14. package/dist/agent-core/session/context-gc-observations.js +148 -0
  15. package/dist/agent-core/session/context-gc-replacements.d.ts +1 -1
  16. package/dist/agent-core/session/context-gc-replacements.js +6 -4
  17. package/dist/agent-core/tools/callers.js +1 -1
  18. package/dist/agent-core/tools/code-mode/cells.d.ts +3 -0
  19. package/dist/agent-core/tools/code-mode/cells.js +62 -6
  20. package/dist/agent-core/tools/code-mode/dispatch.js +7 -4
  21. package/dist/agent-core/tools/code-mode/engine.d.ts +12 -0
  22. package/dist/agent-core/tools/code-mode/index.js +183 -14
  23. package/dist/agent-core/tools/code-mode/observations.d.ts +103 -0
  24. package/dist/agent-core/tools/code-mode/observations.js +195 -0
  25. package/dist/agent-core/tools/code-mode/quickjs-engine.js +121 -20
  26. package/dist/agent-core/tools/code-mode/worker.js +8 -1
  27. package/dist/agent-core/tools/contracts.d.ts +4 -0
  28. package/dist/agent-core/tools/execution.d.ts +2 -0
  29. package/dist/agent-core/tools/execution.js +10 -1
  30. package/dist/agent-core/tools/output-projection.js +35 -1
  31. package/package.json +1 -1
@@ -1,3 +1,4 @@
1
+ import { observationModelParts } from '../tools/code-mode/observations.js';
1
2
  import { randomUUID } from 'node:crypto';
2
3
  import { MarAgentError } from '../error.js';
3
4
  import { resolveModelDefaultReasoningEffort } from './configuration.js';
@@ -475,19 +476,29 @@ function anthropicMessages(messages, images, tools = [], modelId) {
475
476
  const block = {
476
477
  type: 'tool_result',
477
478
  tool_use_id: message.callId,
478
- content: message.images?.length
479
- ? [
480
- { type: 'text', text: message.content },
481
- ...message.images.map((image) => ({
482
- type: 'image',
483
- source: {
484
- type: 'base64',
485
- media_type: image.mimeType,
486
- data: image.dataBase64
487
- }
488
- }))
489
- ]
490
- : message.content
479
+ content: observationModelParts(message)?.map((part) => part.type === 'text'
480
+ ? { type: 'text', text: part.text }
481
+ : {
482
+ type: 'image',
483
+ source: {
484
+ type: 'base64',
485
+ media_type: part.image.mimeType,
486
+ data: part.image.dataBase64
487
+ }
488
+ }) ??
489
+ (message.images?.length
490
+ ? [
491
+ { type: 'text', text: message.content },
492
+ ...message.images.map((image) => ({
493
+ type: 'image',
494
+ source: {
495
+ type: 'base64',
496
+ media_type: image.mimeType,
497
+ data: image.dataBase64
498
+ }
499
+ }))
500
+ ]
501
+ : message.content)
491
502
  };
492
503
  const previous = result.at(-1);
493
504
  if (previous?.role === 'user' && Array.isArray(previous.content))
@@ -2,6 +2,7 @@ import type { ClientToolDefinition } from '../tools/contracts.js';
2
2
  import type { MarAgentModelConfiguration, MarAgentReasoningEffort, ModelCredentialAcquireReason } from './configuration.js';
3
3
  export type { ClientToolDefinition, ToolCaller } from '../tools/contracts.js';
4
4
  export interface ModelMessage {
5
+ calls?: readonly import('../tools/code-mode/observations.js').CodeToolCallRecord[];
5
6
  role: 'user' | 'assistant' | 'tool' | 'tool_call' | 'provider';
6
7
  content: string;
7
8
  contextKind?: 'environment' | 'private';
@@ -18,6 +19,9 @@ export interface ModelMessage {
18
19
  mimeType: string;
19
20
  dataBase64: string;
20
21
  }[];
22
+ observations?: readonly import('../tools/code-mode/observations.js').CodeObservation[];
23
+ /** Runtime-only location used by the GC evidence adapter, never a Provider call. */
24
+ observationId?: string;
21
25
  }
22
26
  export interface ModelAttemptDiagnostic {
23
27
  protocol: MarAgentModelConfiguration['protocol'];
@@ -1,3 +1,4 @@
1
+ import { observationModelParts } from '../tools/code-mode/observations.js';
1
2
  import { isDeepStrictEqual } from 'node:util';
2
3
  import { createHash, randomUUID } from 'node:crypto';
3
4
  import { describeMarAgentError, MarAgentError } from '../error.js';
@@ -1811,15 +1812,21 @@ function openAiInputItem(message, images, customCallIds = new Set(), nativeToolS
1811
1812
  ? 'custom_tool_call_output'
1812
1813
  : 'function_call_output',
1813
1814
  call_id: message.callId,
1814
- output: message.images?.length
1815
- ? [
1816
- { type: 'input_text', text: message.content },
1817
- ...message.images.map((image) => ({
1818
- type: 'input_image',
1819
- image_url: `data:${image.mimeType};base64,${image.dataBase64}`
1820
- }))
1821
- ]
1822
- : message.content
1815
+ output: observationModelParts(message)?.map((part) => part.type === 'text'
1816
+ ? { type: 'input_text', text: part.text }
1817
+ : {
1818
+ type: 'input_image',
1819
+ image_url: `data:${part.image.mimeType};base64,${part.image.dataBase64}`
1820
+ }) ??
1821
+ (message.images?.length
1822
+ ? [
1823
+ { type: 'input_text', text: message.content },
1824
+ ...message.images.map((image) => ({
1825
+ type: 'input_image',
1826
+ image_url: `data:${image.mimeType};base64,${image.dataBase64}`
1827
+ }))
1828
+ ]
1829
+ : message.content)
1823
1830
  };
1824
1831
  return {
1825
1832
  role: message.role,
@@ -9,15 +9,17 @@ export class AsyncQueue {
9
9
  push(value) {
10
10
  if (this.#closed || this.#discarded)
11
11
  return;
12
- const waiter = this.#waiters[this.#waiterOffset++];
12
+ const waiter = this.#waiters[this.#waiterOffset];
13
+ if (!waiter) {
14
+ this.#values.push(value);
15
+ return;
16
+ }
17
+ this.#waiterOffset++;
13
18
  if (this.#waiterOffset > 1024 && this.#waiterOffset * 2 > this.#waiters.length) {
14
19
  this.#waiters.splice(0, this.#waiterOffset);
15
20
  this.#waiterOffset = 0;
16
21
  }
17
- if (waiter)
18
- waiter({ value, done: false });
19
- else
20
- this.#values.push(value);
22
+ waiter({ value, done: false });
21
23
  }
22
24
  close() {
23
25
  if (this.#closed)
@@ -14,8 +14,10 @@ function serializedEntries(input, messages) {
14
14
  (input.messages ?? messages).some((message) => message.provider === 'OPENAI_RESPONSES'));
15
15
  const values = responses
16
16
  ? openAiInput(messages, undefined, input.modelId, true, true, input.responsesConfigurationUpdates !== false)
17
- : messages.filter((message) => message.role !== 'provider' ||
18
- (message.provider === 'ANTHROPIC_MESSAGES' && message.providerModelId === input.modelId));
17
+ : messages
18
+ .filter((message) => message.role !== 'provider' ||
19
+ (message.provider === 'ANTHROPIC_MESSAGES' && message.providerModelId === input.modelId))
20
+ .map(({ observations: _observations, observationId: _observationId, ...message }) => message);
19
21
  return values.map((value, index) => {
20
22
  const item = value;
21
23
  const callId = item.call_id ?? item.callId;
@@ -1,3 +1,6 @@
1
+ import { countModelTokens } from '../token-budget.js';
2
+ import { renderObservations } from '../../tools/code-mode/observations.js';
3
+ import { codeCallEvidence, codeObservationEvidence, retainedCodeInputs } from '../../session/context-gc-observations.js';
1
4
  import { ContextGcCheckpointCache, contextGcBaseFromRecords, contextGcLogicalPathKey, contextGcMinimumUsageGrowth, contextGcRecordSourceHash, contextGcTargetKey, normalizeContextGcReplacements } from '../../session/context-gc-checkpoint.js';
2
5
  import { codeCellCompletionSequence, contextGcApplyPatchInputReplacement, contextGcCodeModeInputReplacement, contextGcCodeModeOutputReplacement, contextGcExecInputReplacement, contextGcExecOutputReplacement, contextGcPatchResultFromCompletedPayload, contextGcStaleReadReplacement, contextGcWebOutputReplacement, normalizeContextGcCodeModeInput, normalizeContextGcExecResult } from '../../session/context-gc-replacements.js';
3
6
  import { contextGcSubagentNeedsRetainedPriorFinal, contextGcSubagentOutputReplacement, contextGcSubagentPriorFinalIsRetained, contextGcSubagentSnapshotIsPreciselyCovered, contextGcSubagentSnapshotIsSuperseded, contextGcSubagentSnapshotTurnId, normalizeContextGcSubagentSnapshot } from '../../session/context-gc-subagent.js';
@@ -231,7 +234,7 @@ function collectCodeModeCandidates(input, groups, existingTargets, usageRatio) {
231
234
  const eligible = [];
232
235
  let protectedTokens = 0;
233
236
  let retryAfterRollouts = Infinity;
234
- for (const group of [...groups.values()].filter((item) => ['code_mode', 'code_exec', 'code_exec_wait'].includes(item.toolName))) {
237
+ for (const group of [...groups.values()].filter((item) => ['code_exec', 'code_exec_wait'].includes(item.toolName))) {
235
238
  if (!group.call ||
236
239
  !group.result ||
237
240
  group.failed ||
@@ -258,6 +261,37 @@ function collectCodeModeCandidates(input, groups, existingTargets, usageRatio) {
258
261
  : LARGE_TERMINAL_OUTPUT_TOKENS;
259
262
  if (visibleTokens < minimumVisibleTokens)
260
263
  continue;
264
+ for (const observation of group.result.message.observations ?? []) {
265
+ if (observation.type !== 'value' ||
266
+ countModelTokens(observation.value) < minimumVisibleTokens)
267
+ continue;
268
+ const target = targetFor(group.result.record, {
269
+ ...group.result.message,
270
+ observationId: observation.id
271
+ });
272
+ if (!target || existingTargets.has(contextGcTargetKey(target)))
273
+ continue;
274
+ const evidenceSequence = group.result.record.sequence;
275
+ const candidate = {
276
+ replacements: [
277
+ {
278
+ kind: 'CODE_MODE_OUTPUT',
279
+ callId: group.callId,
280
+ target,
281
+ replacementContent: contextGcCodeModeOutputReplacement(observation.value, observation.truncated === true),
282
+ evidenceSequence
283
+ }
284
+ ],
285
+ observedRollouts: successfulRolloutsAfter(input.records, Math.max(evidenceSequence, completionSequence), input.modelId, input.contextWindowTokens),
286
+ evidenceSequence
287
+ };
288
+ if (candidate.observedRollouts >= CONTEXT_GC_POLICY.largeExecProtectionRollouts)
289
+ eligible.push(candidate);
290
+ else {
291
+ protectedTokens += candidateTokens(input, candidate);
292
+ retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_POLICY.largeExecProtectionRollouts - candidate.observedRollouts);
293
+ }
294
+ }
261
295
  const callTarget = targetFor(group.call.record, group.call.message);
262
296
  const resultTarget = targetFor(group.result.record, group.result.message);
263
297
  if (!callTarget || !resultTarget)
@@ -270,13 +304,14 @@ function collectCodeModeCandidates(input, groups, existingTargets, usageRatio) {
270
304
  toolName: group.toolName,
271
305
  callId: group.callId,
272
306
  target: callTarget,
273
- replacementArguments: contextGcCodeModeInputReplacement(code),
307
+ replacementArguments: contextGcCodeModeInputReplacement(code, group.result.message.observations ? retainedCodeInputs(input.records, group.callId) : []),
274
308
  providerAliasTargets: group.aliases
275
309
  .map(({ record, message }) => targetFor(record, message))
276
310
  .filter((target) => target !== undefined),
277
311
  evidenceSequence
278
312
  });
279
- if (!existingTargets.has(contextGcTargetKey(resultTarget)))
313
+ if (!group.result.message.observations &&
314
+ !existingTargets.has(contextGcTargetKey(resultTarget)))
280
315
  replacements.push({
281
316
  kind: 'CODE_MODE_OUTPUT',
282
317
  callId: group.callId,
@@ -503,7 +538,7 @@ function collectStageACandidates(input, groups, existingReplacements, existingTa
503
538
  const reads = [...groups.values()].filter((group) => group.toolName === 'read' &&
504
539
  group.call?.message.role === 'tool_call' &&
505
540
  group.result?.message.role === 'tool');
506
- const claimedReadItems = new Set([...existingReadReplacements.values()].flatMap((replacement) => replacement.items.map((item) => `${replacement.target.recordId}:${item.index}`)));
541
+ const claimedReadItems = new Set([...existingReadReplacements.values()].flatMap((replacement) => replacement.items.map((item) => `${replacement.target.recordId}:${replacement.target.observationId ?? ''}:${item.index}`)));
507
542
  const patches = [...groups.values()]
508
543
  .flatMap((patch) => {
509
544
  if (patch.toolName !== 'apply_patch' ||
@@ -534,7 +569,7 @@ function collectStageACandidates(input, groups, existingReplacements, existingTa
534
569
  evidenceSequence
535
570
  });
536
571
  for (const read of reads) {
537
- if (read.result.record.sequence >= evidenceSequence)
572
+ if ((read.completed?.sequence ?? read.result.record.sequence) >= patch.completed.sequence)
538
573
  continue;
539
574
  const parsed = parseReadContextOutput(read.result.message.content);
540
575
  if (!parsed)
@@ -542,7 +577,7 @@ function collectStageACandidates(input, groups, existingReplacements, existingTa
542
577
  const selected = parsed.items.flatMap((item) => {
543
578
  if (item.pathTruncated)
544
579
  return [];
545
- const itemKey = `${read.result.record.recordId}:${item.index}`;
580
+ const itemKey = `${read.result.record.recordId}:${read.result.message.observationId ?? ''}:${item.index}`;
546
581
  if (claimedReadItems.has(itemKey))
547
582
  return [];
548
583
  const pathKey = contextGcLogicalPathKey(item.path, input.workspace, input.platform);
@@ -562,7 +597,8 @@ function collectStageACandidates(input, groups, existingReplacements, existingTa
562
597
  supersededByCallId: patch.callId,
563
598
  operation: matched.operation
564
599
  }),
565
- evidenceSequence
600
+ evidenceSequence,
601
+ ...(read.result.message.observationId ? { supersededByCallId: patch.callId } : {})
566
602
  }
567
603
  ];
568
604
  });
@@ -616,8 +652,10 @@ function collectSubagentCandidates(input, groups, existingTargets) {
616
652
  const turnId = snapshot && contextGcSubagentSnapshotTurnId(snapshot);
617
653
  return snapshot && turnId ? [{ group, snapshot, turnId }] : [];
618
654
  })
619
- .sort((left, right) => left.group.result.record.sequence - right.group.result.record.sequence);
655
+ .sort((left, right) => left.group.completed.sequence - right.group.completed.sequence);
620
656
  for (const [index, source] of invocations.entries()) {
657
+ if (source.group.evidenceOnly)
658
+ continue;
621
659
  if (source.group.toolName !== 'agent_wait' &&
622
660
  source.group.toolName !== 'agent_message' &&
623
661
  source.group.toolName !== 'agent_cancel')
@@ -759,7 +797,9 @@ function collectExecCandidates(input, groups, existingTargets, usageRatio) {
759
797
  if (!terminal || (processId && input.activeProcessIds.has(processId)))
760
798
  continue;
761
799
  const final = lifecycle.at(-1);
762
- const visibleTokens = contextGcMessageTokens(input, lifecycle.flatMap((invocation) => [
800
+ const visibleTokens = contextGcMessageTokens(input, lifecycle
801
+ .filter((invocation) => !invocation.group.evidenceOnly)
802
+ .flatMap((invocation) => [
763
803
  invocation.group.call.message,
764
804
  invocation.group.result.message,
765
805
  ...invocation.group.aliases.map((alias) => alias.message)
@@ -773,13 +813,15 @@ function collectExecCandidates(input, groups, existingTargets, usageRatio) {
773
813
  const observedRollouts = successfulRolloutsAfter(input.records, evidenceSequence, input.modelId, input.contextWindowTokens);
774
814
  const replacements = [];
775
815
  for (const invocation of lifecycle) {
816
+ if (invocation.group.evidenceOnly)
817
+ continue;
776
818
  const callTarget = targetFor(invocation.group.call.record, invocation.group.call.message);
777
819
  const resultTarget = targetFor(invocation.group.result.record, invocation.group.result.message);
778
- if (!callTarget || !resultTarget) {
820
+ if (!resultTarget) {
779
821
  replacements.splice(0);
780
822
  break;
781
823
  }
782
- if (!existingTargets.has(contextGcTargetKey(callTarget)))
824
+ if (callTarget && !existingTargets.has(contextGcTargetKey(callTarget)))
783
825
  replacements.push({
784
826
  kind: 'TOOL_CALL_INPUT',
785
827
  toolName: invocation.group.toolName,
@@ -794,6 +836,7 @@ function collectExecCandidates(input, groups, existingTargets, usageRatio) {
794
836
  if (!existingTargets.has(contextGcTargetKey(resultTarget)))
795
837
  replacements.push({
796
838
  kind: 'EXEC_OUTPUT',
839
+ ...(resultTarget.observationId ? { terminalCallId: final.group.callId } : {}),
797
840
  callId: invocation.group.callId,
798
841
  target: resultTarget,
799
842
  replacementContent: contextGcExecOutputReplacement(invocation.result),
@@ -918,6 +961,89 @@ function buildToolGroups(records, messages, sources) {
918
961
  else if (type === 'tool.failed')
919
962
  current.failed = record;
920
963
  }
964
+ for (const [index, message] of messages.entries()) {
965
+ if (!message.observations || message.role !== 'tool')
966
+ continue;
967
+ const outer = message.callId ? groups.get(message.callId) : undefined;
968
+ if (!outer?.completed ||
969
+ outer.failed ||
970
+ codeCellCompletionSequence(records, outer.completed) === undefined)
971
+ continue;
972
+ const record = sources[index];
973
+ for (const entry of message.calls ?? []) {
974
+ if (![
975
+ 'apply_patch',
976
+ 'exec',
977
+ 'exec_wait',
978
+ 'agent_start',
979
+ 'agent_wait',
980
+ 'agent_message',
981
+ 'agent_cancel'
982
+ ].includes(entry.toolName) ||
983
+ message.observations.some((value) => value.type === 'tool_result' && value.source.callId === entry.callId))
984
+ continue;
985
+ const evidence = codeCallEvidence(records, record, entry);
986
+ if (!evidence)
987
+ continue;
988
+ groups.set(`execution:${evidence.callId}`, {
989
+ evidenceOnly: true,
990
+ callId: evidence.callId,
991
+ toolName: entry.toolName,
992
+ aliases: [],
993
+ completed: evidence.completed,
994
+ call: {
995
+ record: evidence.call,
996
+ message: {
997
+ role: 'tool_call',
998
+ callId: evidence.callId,
999
+ name: entry.toolName,
1000
+ content: '',
1001
+ arguments: entry.input
1002
+ }
1003
+ },
1004
+ result: {
1005
+ record,
1006
+ message: {
1007
+ role: 'tool',
1008
+ callId: message.callId,
1009
+ content: evidence.result.payload.content
1010
+ }
1011
+ }
1012
+ });
1013
+ }
1014
+ for (const observation of message.observations) {
1015
+ if (observation.type !== 'tool_result')
1016
+ continue;
1017
+ const evidence = codeObservationEvidence(records, record, observation);
1018
+ if (!evidence)
1019
+ continue;
1020
+ groups.set(`${evidence.callId}:${observation.id}`, {
1021
+ callId: evidence.callId,
1022
+ toolName: observation.source.toolName,
1023
+ aliases: [],
1024
+ completed: evidence.completed,
1025
+ call: {
1026
+ record: evidence.call,
1027
+ message: {
1028
+ role: 'tool_call',
1029
+ callId: evidence.callId,
1030
+ name: observation.source.toolName,
1031
+ content: '',
1032
+ arguments: observation.source.input
1033
+ }
1034
+ },
1035
+ result: {
1036
+ record,
1037
+ message: {
1038
+ role: 'tool',
1039
+ callId: message.callId,
1040
+ content: observation.result.content,
1041
+ observationId: observation.id
1042
+ }
1043
+ }
1044
+ });
1045
+ }
1046
+ }
921
1047
  return groups;
922
1048
  }
923
1049
  function successfulRolloutsAfter(records, evidenceSequence, modelId, contextWindowTokens) {
@@ -1111,9 +1237,15 @@ function candidateTokens(input, candidate) {
1111
1237
  ? replacement.providerAliasTargets.map(contextGcTargetKey)
1112
1238
  : [])
1113
1239
  ]));
1114
- return contextGcMessageTokens(input, input.messages.filter((message, index) => {
1240
+ return contextGcMessageTokens(input, input.messages.flatMap((message, index) => {
1115
1241
  const target = targetFor(input.messageSources[index], message);
1116
- return target !== undefined && targets.has(contextGcTargetKey(target));
1242
+ if (target && targets.has(contextGcTargetKey(target)))
1243
+ return [message];
1244
+ const selected = target &&
1245
+ message.observations?.filter((value) => targets.has(contextGcTargetKey({ ...target, observationId: value.id })));
1246
+ return selected?.length
1247
+ ? [{ ...message, observations: selected, content: renderObservations(selected) }]
1248
+ : [];
1117
1249
  }));
1118
1250
  }
1119
1251
  function targetFor(record, message) {
@@ -1128,7 +1260,8 @@ function targetFor(record, message) {
1128
1260
  role: message.role,
1129
1261
  ...(callId === undefined ? {} : { callId }),
1130
1262
  ...(toolName === undefined ? {} : { toolName }),
1131
- sourceHash: contextGcRecordSourceHash(record)
1263
+ sourceHash: contextGcRecordSourceHash(record),
1264
+ ...(message.observationId ? { observationId: message.observationId } : {})
1132
1265
  };
1133
1266
  }
1134
1267
  function modelContextUsage(value) {
@@ -2,6 +2,7 @@ import { anthropicThinkingBlock } from '../../model/anthropic-thinking.js';
2
2
  import { contextGcRecordSourceHash, contextGcTargetKey, latestValidContextGcCheckpoint } from '../../session/context-gc-checkpoint.js';
3
3
  import { TOOL_EXECUTION_LIMITS } from '../../tools/execution-limits.js';
4
4
  import { replaceReadContextItems } from '../../tools/local/read-context.js';
5
+ import { parseCodeCalls, parseObservations, renderObservationResult } from '../../tools/code-mode/observations.js';
5
6
  const MAX_MODEL_CONTEXT_BYTES = 1024 * 1024;
6
7
  export function buildContextProjection(records, workspace, checkpointCache) {
7
8
  let activeModelId;
@@ -50,6 +51,32 @@ export function applyContextGcReplacementsToMessages(messages, messageSources, r
50
51
  continue;
51
52
  const replacement = target ? direct.get(contextGcTargetKey(target)) : undefined;
52
53
  let next = cloneMessage(message);
54
+ if (next.observations && target) {
55
+ const observations = next.observations.map((value) => {
56
+ const itemTarget = { ...target, observationId: value.id };
57
+ const item = direct.get(contextGcTargetKey(itemTarget));
58
+ if (!item || !targetMatchesReplacement(itemTarget, item.target))
59
+ return value;
60
+ if (item.kind === 'READ_OUTPUT_ITEMS' && value.type === 'tool_result')
61
+ return {
62
+ ...value,
63
+ result: {
64
+ ...value.result,
65
+ content: replaceReadContextItems(value.result.content, item.items)
66
+ }
67
+ };
68
+ if ('replacementContent' in item)
69
+ return value.type === 'tool_result'
70
+ ? { ...value, result: { ...value.result, content: item.replacementContent } }
71
+ : { ...value, value: item.replacementContent };
72
+ return value;
73
+ });
74
+ next = {
75
+ ...next,
76
+ observations,
77
+ content: renderObservationResult(observations, next.content)
78
+ };
79
+ }
53
80
  if (replacement && target && targetMatchesReplacement(target, replacement.target)) {
54
81
  if (replacement.kind === 'READ_OUTPUT_ITEMS' &&
55
82
  next.role === 'tool' &&
@@ -107,7 +134,7 @@ export function restoreModelContext(payload, providerModelId) {
107
134
  : nativeItem?.type === 'custom_tool_call'
108
135
  ? nativeItem.name
109
136
  : undefined;
110
- const inputLimit = toolName === 'code_mode' || toolName === 'code_exec' || toolName === 'code_exec_wait'
137
+ const inputLimit = toolName === 'code_exec' || toolName === 'code_exec_wait'
111
138
  ? TOOL_EXECUTION_LIMITS.codeMaxInputBytes
112
139
  : toolName === 'apply_patch'
113
140
  ? TOOL_EXECUTION_LIMITS.patchMaxBytes
@@ -122,6 +149,10 @@ export function restoreModelContext(payload, providerModelId) {
122
149
  role: 'tool',
123
150
  content: payload.content,
124
151
  callId: payload.callId,
152
+ ...(parseCodeCalls(payload.calls) ? { calls: parseCodeCalls(payload.calls) } : {}),
153
+ ...(parseObservations(payload.observations)
154
+ ? { observations: parseObservations(payload.observations) }
155
+ : {}),
125
156
  ...(Array.isArray(payload.toolSearchTools)
126
157
  ? {
127
158
  toolSearchTools: payload.toolSearchTools.filter((tool) => isRecord(tool) &&
@@ -82,6 +82,7 @@ export type MarAgentEvent = MarAgentEventBase & ({
82
82
  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;
83
83
  } | {
84
84
  type: 'tool.failed';
85
+ data?: import('../tools/contracts.js').CodeCellEventData;
85
86
  toolName: string;
86
87
  callId: string;
87
88
  parentCallId?: string;
@@ -312,6 +312,8 @@ export class ExecutionLoop {
312
312
  role: 'tool',
313
313
  callId: result.call.callId,
314
314
  content: result.content,
315
+ ...(result.calls ? { calls: result.calls } : {}),
316
+ ...(result.observations ? { observations: result.observations } : {}),
315
317
  ...(toolSearchTools === undefined ? {} : { toolSearchTools }),
316
318
  ...('images' in result && result.images?.length ? { images: result.images } : {})
317
319
  };
@@ -320,6 +322,8 @@ export class ExecutionLoop {
320
322
  role: message.role,
321
323
  callId: result.call.callId,
322
324
  content: message.content,
325
+ ...(message.calls ? { calls: message.calls } : {}),
326
+ ...(message.observations ? { observations: message.observations } : {}),
323
327
  ...(message.toolSearchTools === undefined
324
328
  ? {}
325
329
  : { toolSearchTools: message.toolSearchTools })
@@ -562,7 +562,7 @@ export function createSessionOpener(services) {
562
562
  .slice(contextualWorldState.entries.findLast((entry) => entry.kind === 'resources')
563
563
  ?.index ?? 0)
564
564
  .some((message) => message.role === 'tool_call' &&
565
- ['exec', 'code_exec', 'code_mode', 'agent_start'].includes(message.name ?? '')))
565
+ ['exec', 'code_exec', 'agent_start'].includes(message.name ?? '')))
566
566
  });
567
567
  sessionRuntime.contextualWorldState = contextualWorldState;
568
568
  if (!isDeepStrictEqual(previousContextualWorldState, contextualWorldState))
@@ -29,6 +29,7 @@ export type ContextGcBase = {
29
29
  readonly recordId: string;
30
30
  };
31
31
  export interface ContextGcTarget {
32
+ readonly observationId?: string;
32
33
  readonly sequence: number;
33
34
  readonly recordId: string;
34
35
  readonly role: 'tool_call' | 'tool' | 'provider';
@@ -37,6 +38,7 @@ export interface ContextGcTarget {
37
38
  readonly sourceHash: string;
38
39
  }
39
40
  export interface ContextGcReadItemReplacement {
41
+ readonly supersededByCallId?: string;
40
42
  readonly index: number;
41
43
  readonly path: string;
42
44
  readonly replacement: string;
@@ -50,7 +52,7 @@ export type ContextGcReplacement = {
50
52
  readonly evidenceSequences: readonly number[];
51
53
  } | {
52
54
  readonly kind: 'TOOL_CALL_INPUT';
53
- readonly toolName: 'apply_patch' | 'exec' | 'exec_wait' | 'code_mode' | 'code_exec';
55
+ readonly toolName: 'apply_patch' | 'exec' | 'exec_wait' | 'code_exec';
54
56
  readonly callId: string;
55
57
  readonly target: ContextGcTarget;
56
58
  readonly replacementArguments: unknown;
@@ -58,6 +60,7 @@ export type ContextGcReplacement = {
58
60
  readonly evidenceSequence: number;
59
61
  } | {
60
62
  readonly kind: 'EXEC_OUTPUT';
63
+ readonly terminalCallId?: string;
61
64
  readonly callId: string;
62
65
  readonly target: ContextGcTarget;
63
66
  readonly replacementContent: string;