@myagentroam/agent 0.9.139 → 0.9.141

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 (35) hide show
  1. package/dist/host/local-agent-host.d.ts +3 -1
  2. package/dist/host/local-agent-host.js +29 -3
  3. package/dist/model/anthropic-messages.js +24 -7
  4. package/dist/model/contracts.d.ts +4 -0
  5. package/dist/model/openai-responses.js +28 -10
  6. package/dist/model/sse.d.ts +2 -0
  7. package/dist/model/sse.js +11 -1
  8. package/dist/prompts/resources.js +4 -2
  9. package/dist/prompts/workflow.js +2 -0
  10. package/dist/runtime/context-gc.js +25 -8
  11. package/dist/sdk/agent.js +68 -42
  12. package/dist/session/context-gc-checkpoint.d.ts +1 -1
  13. package/dist/session/context-gc-checkpoint.js +8 -5
  14. package/dist/session/context-gc-replacements.d.ts +1 -1
  15. package/dist/session/context-gc-replacements.js +5 -3
  16. package/dist/tools/agent-wait.js +1 -1
  17. package/dist/tools/apply-patch.js +1 -1
  18. package/dist/tools/callers.js +1 -1
  19. package/dist/tools/code-mode/cells.d.ts +7 -1
  20. package/dist/tools/code-mode/cells.js +32 -8
  21. package/dist/tools/code-mode/engine.d.ts +5 -2
  22. package/dist/tools/code-mode/quickjs-engine.js +116 -18
  23. package/dist/tools/code-mode/text-output.d.ts +21 -0
  24. package/dist/tools/code-mode/text-output.js +75 -0
  25. package/dist/tools/code-mode/worker.js +1 -1
  26. package/dist/tools/code-mode.js +36 -18
  27. package/dist/tools/exec.d.ts +3 -0
  28. package/dist/tools/exec.js +56 -15
  29. package/dist/tools/execution-limits.d.ts +4 -1
  30. package/dist/tools/execution-limits.js +4 -1
  31. package/dist/tools/local-registry.js +5 -5
  32. package/dist/tools/registry.js +2 -1
  33. package/dist/tools/skill.js +1 -1
  34. package/dist/tools/tool-search.js +5 -3
  35. package/package.json +1 -1
package/dist/sdk/agent.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { createHash, randomUUID } from 'node:crypto';
2
2
  import { isDeepStrictEqual } from 'node:util';
3
3
  import { describeMarAgentError, MarAgentError } from '../error.js';
4
+ import { CodeCellExecutionError } from '../tools/code-mode/cells.js';
4
5
  import { negotiateHost } from '../host/contracts.js';
5
6
  import { AnthropicMessagesAdapter, isAnthropicRecoverableStreamError } from '../model/anthropic-messages.js';
6
7
  import { marAgentReasoningEffortSchema, resolveModelDefaultReasoningEffort, usesResponsesConfigurationUpdates } from '../model/configuration.js';
@@ -36,8 +37,8 @@ import { createCodeToolDispatch } from '../tools/code-mode/dispatch.js';
36
37
  import { createSessionToolScheduler, scheduleToolCallBatch } from '../tools/scheduler.js';
37
38
  import { projectCodeModeSearchResult, projectCodeModeTools } from '../tools/code-mode.js';
38
39
  import { allowsDirectTool, allowsToolCaller, toolCallers } from '../tools/callers.js';
40
+ import { modelStreamEventObservedAt } from '../model/sse.js';
39
41
  import { SubagentSessionController } from '../subagent/session-controller.js';
40
- const MODEL_PERFORMANCE_MIN_INTERVAL_MS = 1_000;
41
42
  /** Retry only classified recoverable failures within the attempt budget; never retry cancellation. */
42
43
  async function recoverModelStream(error, retryState, signal) {
43
44
  if (signal.aborted ||
@@ -612,7 +613,14 @@ export async function createMarAgentWithAdapter(options) {
612
613
  instructionContent: renderAgentInstructions(instructionEntries, compactMayContainAgentInstructions ||
613
614
  contextualWorldState.instructionSnapshot !== undefined),
614
615
  resourceSnapshot: currentResourceSnapshot,
615
- previousMayContainResources: compactMayContainRetainedResources
616
+ previousMayContainResources: (!contextualWorldState.initialized && compactMayContainRetainedResources) ||
617
+ (options.host.listSessionResources !== undefined &&
618
+ currentResourceSnapshot === undefined &&
619
+ messages
620
+ .slice(contextualWorldState.entries.findLast((entry) => entry.kind === 'resources')
621
+ ?.index ?? 0)
622
+ .some((message) => message.role === 'tool_call' &&
623
+ ['exec', 'code_exec', 'code_mode', 'agent_start'].includes(message.name ?? '')))
616
624
  });
617
625
  sessionRuntime.contextualWorldState = contextualWorldState;
618
626
  if (!isDeepStrictEqual(previousContextualWorldState, contextualWorldState))
@@ -898,40 +906,39 @@ export async function createMarAgentWithAdapter(options) {
898
906
  let sawModelSemanticEvent = false;
899
907
  let requestStartedAt;
900
908
  let firstModelResponseAt;
909
+ let firstOutputDeltaAt;
910
+ let lastOutputDeltaAt;
901
911
  let requestCompletedAt;
902
912
  let requestOutputTokens;
903
- let outputTokenRevision = 0;
904
- let lastPerformanceEmittedAt;
905
- let lastPublishedOutputTokenRevision = 0;
906
- let performanceEmitted = false;
907
913
  const roundMessageCount = messages.length;
908
- const emitPerformance = async (sampledAt, force = false) => {
914
+ const emitLatency = async () => {
909
915
  if (mode === 'compact' || firstModelResponseAt === undefined)
910
916
  return;
911
- const outputDurationMs = Math.max(0, Math.round(sampledAt - firstModelResponseAt));
912
- const outputTokens = requestOutputTokens;
913
- const tokensPerSecond = outputTokens !== undefined && outputDurationMs > 0
914
- ? Number(((outputTokens * 1000) / outputDurationMs).toFixed(2))
915
- : undefined;
916
- if (performanceEmitted &&
917
- (tokensPerSecond === undefined ||
918
- outputTokenRevision === lastPublishedOutputTokenRevision))
917
+ await emit({
918
+ type: 'model.performance',
919
+ latencyMs: Math.max(0, Math.round(firstModelResponseAt - requestStartedAt))
920
+ });
921
+ };
922
+ const emitFinalPerformance = async (completedAt) => {
923
+ if (mode === 'compact' ||
924
+ firstModelResponseAt === undefined ||
925
+ firstOutputDeltaAt === undefined ||
926
+ lastOutputDeltaAt === undefined ||
927
+ lastOutputDeltaAt - firstOutputDeltaAt < 1)
919
928
  return;
920
- if (!force &&
921
- lastPerformanceEmittedAt !== undefined &&
922
- sampledAt - lastPerformanceEmittedAt < MODEL_PERFORMANCE_MIN_INTERVAL_MS)
929
+ const outputDuration = completedAt - firstOutputDeltaAt;
930
+ const outputTokens = requestOutputTokens;
931
+ if (outputTokens === undefined ||
932
+ outputDuration < 1 ||
933
+ completedAt < lastOutputDeltaAt)
923
934
  return;
924
935
  await emit({
925
936
  type: 'model.performance',
926
937
  latencyMs: Math.max(0, Math.round(firstModelResponseAt - requestStartedAt)),
927
- ...(outputTokens === undefined ? {} : { outputTokens }),
928
- outputDurationMs,
929
- ...(tokensPerSecond === undefined ? {} : { tokensPerSecond })
938
+ outputTokens,
939
+ outputDurationMs: Math.round(outputDuration),
940
+ tokensPerSecond: Number(((outputTokens * 1000) / outputDuration).toFixed(2))
930
941
  });
931
- performanceEmitted = true;
932
- lastPerformanceEmittedAt = sampledAt;
933
- if (tokensPerSecond !== undefined)
934
- lastPublishedOutputTokenRevision = outputTokenRevision;
935
942
  };
936
943
  const persistToolCalls = async (calls) => {
937
944
  for (const call of calls) {
@@ -978,10 +985,25 @@ export async function createMarAgentWithAdapter(options) {
978
985
  ...(modelInputImages?.length ? { images: modelInputImages } : {}),
979
986
  onAttemptDiagnostic: recordModelAttempt(mode === 'compact' ? 'COMPACTION' : 'EXECUTION')
980
987
  })) {
981
- const modelResponseAt = mode !== 'compact' && isModelResponseEvent(event) ? performance.now() : undefined;
988
+ if (event.type === 'output.reset') {
989
+ firstOutputDeltaAt = undefined;
990
+ lastOutputDeltaAt = undefined;
991
+ requestOutputTokens = undefined;
992
+ continue;
993
+ }
994
+ const modelResponseAt = mode !== 'compact' && isModelResponseEvent(event)
995
+ ? Math.max(requestStartedAt, modelStreamEventObservedAt(event) ?? performance.now())
996
+ : undefined;
997
+ const isFirstModelResponse = modelResponseAt !== undefined && firstModelResponseAt === undefined;
982
998
  if (modelResponseAt !== undefined) {
983
- sawModelSemanticEvent = true;
999
+ if (event.type !== 'output.delta')
1000
+ sawModelSemanticEvent = true;
984
1001
  firstModelResponseAt ??= modelResponseAt;
1002
+ if (event.type === 'output.delta' ||
1003
+ (event.type === 'text.delta' && event.delta.length > 0)) {
1004
+ firstOutputDeltaAt ??= modelResponseAt;
1005
+ lastOutputDeltaAt = modelResponseAt;
1006
+ }
985
1007
  }
986
1008
  if (event.type === 'response.item') {
987
1009
  // Compaction keeps only its successful checkpoint, never intermediate native output.
@@ -1070,7 +1092,6 @@ export async function createMarAgentWithAdapter(options) {
1070
1092
  : undefined;
1071
1093
  if (outputTokens !== undefined) {
1072
1094
  requestOutputTokens = outputTokens;
1073
- outputTokenRevision++;
1074
1095
  }
1075
1096
  const reportedContext = event.usage.contextInputTokens ?? event.usage.inputTokens;
1076
1097
  if (reportedContext !== undefined &&
@@ -1119,14 +1140,14 @@ export async function createMarAgentWithAdapter(options) {
1119
1140
  }
1120
1141
  else if (event.type === 'completed') {
1121
1142
  stop = event.reason;
1122
- requestCompletedAt = performance.now();
1143
+ requestCompletedAt = modelStreamEventObservedAt(event) ?? performance.now();
1123
1144
  }
1124
- if (modelResponseAt !== undefined)
1125
- await emitPerformance(modelResponseAt);
1145
+ if (isFirstModelResponse)
1146
+ await emitLatency();
1126
1147
  }
1127
1148
  if (mode !== 'compact' && firstModelResponseAt !== undefined) {
1128
1149
  const completedAt = requestCompletedAt ?? performance.now();
1129
- await emitPerformance(completedAt, true);
1150
+ await emitFinalPerformance(completedAt);
1130
1151
  }
1131
1152
  pendingOutputContinuation = undefined;
1132
1153
  incompleteToolCall = pendingTools.some((call) => call.argumentError !== undefined);
@@ -1291,6 +1312,7 @@ export async function createMarAgentWithAdapter(options) {
1291
1312
  }
1292
1313
  catch (error) {
1293
1314
  const diagnostic = describeMarAgentError(error, 'MAR_AGENT_HOST_OPERATION_FAILED');
1315
+ const partial = error instanceof CodeCellExecutionError ? error.result : undefined;
1294
1316
  await emit({
1295
1317
  type: 'tool.failed',
1296
1318
  toolName: call.name,
@@ -1301,13 +1323,16 @@ export async function createMarAgentWithAdapter(options) {
1301
1323
  });
1302
1324
  return {
1303
1325
  call,
1304
- content: JSON.stringify({
1305
- error: diagnostic.code,
1306
- message: diagnostic.message,
1307
- ...(error instanceof MarAgentError && error.retryable
1308
- ? { retryable: true }
1309
- : {})
1310
- }),
1326
+ content: partial === undefined
1327
+ ? JSON.stringify({
1328
+ error: diagnostic.code,
1329
+ message: diagnostic.message,
1330
+ ...(error instanceof MarAgentError && error.retryable
1331
+ ? { retryable: true }
1332
+ : {})
1333
+ })
1334
+ : boundedModelToolOutput(call.name, partial.content, partial.data).content,
1335
+ ...(partial?.images?.length ? { images: partial.images } : {}),
1311
1336
  failed: true,
1312
1337
  error
1313
1338
  };
@@ -2237,7 +2262,7 @@ function updateContextualWorldState(state, index, current) {
2237
2262
  if (current.instructionContent !== undefined)
2238
2263
  entries.push({ index, kind: 'instructions', content: current.instructionContent });
2239
2264
  }
2240
- const resourceContent = retainedSessionResourcesUpdate(current.resourceSnapshot, state.initialized ? state.resourceSnapshot : undefined, !state.initialized && current.previousMayContainResources);
2265
+ const resourceContent = retainedSessionResourcesUpdate(current.resourceSnapshot, state.initialized ? state.resourceSnapshot : undefined, current.previousMayContainResources);
2241
2266
  if (resourceContent !== undefined)
2242
2267
  entries.push({ index, kind: 'resources', content: resourceContent });
2243
2268
  return {
@@ -2283,7 +2308,7 @@ function estimatedRequestTokens(system, messages, tools, images) {
2283
2308
  transientImageCount * AGENT_EXECUTION_POLICY.estimatedTokensPerImage);
2284
2309
  }
2285
2310
  function boundedModelToolOutput(toolName, value, data) {
2286
- const execDiagnostics = toolName === 'exec' &&
2311
+ const execDiagnostics = (toolName === 'exec' || toolName === 'exec_wait') &&
2287
2312
  isRecord(data) &&
2288
2313
  typeof data.stdout === 'string' &&
2289
2314
  typeof data.stderr === 'string'
@@ -2390,7 +2415,7 @@ function structuredToolEventData(toolName, value) {
2390
2415
  typeof value.sha256 === 'string' &&
2391
2416
  /^[a-f0-9]{64}$/u.test(value.sha256))
2392
2417
  return value;
2393
- if (toolName === 'exec' &&
2418
+ if ((toolName === 'exec' || toolName === 'exec_wait') &&
2394
2419
  (value.status === 'running' || value.status === 'completed' || value.status === 'cancelled') &&
2395
2420
  typeof value.stdout === 'string' &&
2396
2421
  typeof value.stderr === 'string' &&
@@ -2457,6 +2482,7 @@ function isModelResponseEvent(event) {
2457
2482
  if (event.type === 'text.completed' || event.type === 'reasoning.summary')
2458
2483
  return event.text.length > 0;
2459
2484
  return (event.type === 'response.item' ||
2485
+ event.type === 'output.delta' ||
2460
2486
  event.type === 'client_tool.call' ||
2461
2487
  event.type === 'hosted_tool' ||
2462
2488
  event.type === 'citation');
@@ -50,7 +50,7 @@ export type ContextGcReplacement = {
50
50
  readonly evidenceSequences: readonly number[];
51
51
  } | {
52
52
  readonly kind: 'TOOL_CALL_INPUT';
53
- readonly toolName: 'apply_patch' | 'exec' | 'code_mode' | 'code_exec';
53
+ readonly toolName: 'apply_patch' | 'exec' | 'exec_wait' | 'code_mode' | 'code_exec';
54
54
  readonly callId: string;
55
55
  readonly target: ContextGcTarget;
56
56
  readonly replacementArguments: unknown;
@@ -350,8 +350,9 @@ function replacementMatchesRecords(replacement, records, recordsBySequence, work
350
350
  if (!isSuccessfulExecLifecycleEvidence(replacement, source, records, recordsBySequence))
351
351
  return false;
352
352
  if (replacement.kind === 'TOOL_CALL_INPUT')
353
- return isDeepStrictEqual(replacement.replacementArguments, contextGcExecInputReplacement(source.payload.arguments, execInputReplacementFormatVersion(replacement.replacementArguments)));
354
- const targetEvidence = successfulToolEvidence(records, recordsBySequence, source.sequence, 'exec');
353
+ 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');
355
356
  const result = normalizeContextGcExecResult(source.payload.content, targetEvidence?.completed.payload);
356
357
  return (result !== undefined &&
357
358
  replacement.replacementContent ===
@@ -474,7 +475,8 @@ function successfulPatchEvidenceForPath(records, recordsBySequence, evidenceSequ
474
475
  : undefined;
475
476
  }
476
477
  function isSuccessfulExecLifecycleEvidence(replacement, source, records, recordsBySequence) {
477
- const evidence = successfulToolEvidence(records, recordsBySequence, replacement.evidenceSequence, 'exec');
478
+ const evidence = successfulToolEvidence(records, recordsBySequence, replacement.evidenceSequence, 'exec') ??
479
+ successfulToolEvidence(records, recordsBySequence, replacement.evidenceSequence, 'exec_wait');
478
480
  if (!evidence || !isRecord(evidence.call.payload) || !isRecord(evidence.result.payload))
479
481
  return false;
480
482
  const finalResult = normalizeContextGcExecResult(evidence.result.payload.content, evidence.completed.payload);
@@ -487,7 +489,7 @@ function isSuccessfulExecLifecycleEvidence(replacement, source, records, records
487
489
  targetCall.sequence > evidence.call.sequence ||
488
490
  !isRecord(targetCall.payload) ||
489
491
  targetCall.payload.role !== 'tool_call' ||
490
- targetCall.payload.name !== 'exec' ||
492
+ (targetCall.payload.name !== 'exec' && targetCall.payload.name !== 'exec_wait') ||
491
493
  targetCall.payload.callId !== replacement.callId)
492
494
  return false;
493
495
  const targetResult = replacement.kind === 'EXEC_OUTPUT'
@@ -495,7 +497,7 @@ function isSuccessfulExecLifecycleEvidence(replacement, source, records, records
495
497
  : modelToolResultForCall(records, targetCall, evidence.result.sequence);
496
498
  if (!targetResult)
497
499
  return false;
498
- const targetEvidence = successfulToolEvidence(records, recordsBySequence, targetResult.sequence, 'exec');
500
+ const targetEvidence = successfulToolEvidence(records, recordsBySequence, targetResult.sequence, targetCall.payload.name);
499
501
  if (!targetEvidence || targetEvidence.call.sequence !== targetCall.sequence)
500
502
  return false;
501
503
  if (targetEvidence.callId === evidence.callId)
@@ -631,6 +633,7 @@ function isContextGcReplacement(value) {
631
633
  value.target.role === 'tool_call' &&
632
634
  (value.toolName === 'apply_patch' ||
633
635
  value.toolName === 'exec' ||
636
+ value.toolName === 'exec_wait' ||
634
637
  value.toolName === 'code_mode' ||
635
638
  value.toolName === 'code_exec') &&
636
639
  value.target.toolName === value.toolName &&
@@ -56,7 +56,7 @@ export declare function contextGcStaleReadReplacement(input: {
56
56
  readonly formatVersion?: 1 | 2;
57
57
  }): string;
58
58
  export declare function contextGcWebOutputReplacement(toolName: ContextGcWebToolName, originalContent: string, truncated: boolean): string;
59
- export declare function contextGcExecInputReplacement(arguments_: unknown, formatVersion?: 1 | 2 | 3): unknown;
59
+ export declare function contextGcExecInputReplacement(arguments_: unknown, formatVersion?: 1 | 2 | 3, toolName?: string): unknown;
60
60
  export declare function contextGcExecOutputReplacement(result: ContextGcExecResult, formatVersion?: 1 | 2 | 3): string;
61
61
  export declare function normalizeContextGcExecResult(modelContent: unknown, completedPayload: unknown): ContextGcExecResult | undefined;
62
62
  export declare function parseContextGcExecResult(value: unknown): ContextGcExecResult | undefined;
@@ -97,14 +97,16 @@ export function contextGcWebOutputReplacement(toolName, originalContent, truncat
97
97
  contextGc: 'Detailed web tool output was reclaimed after six successful rollouts. An incomplete preview is not the complete search result or page, and GC did not access the network.'
98
98
  });
99
99
  }
100
- export function contextGcExecInputReplacement(arguments_, formatVersion = 3) {
100
+ export function contextGcExecInputReplacement(arguments_, formatVersion = 3, toolName = 'exec') {
101
101
  if (!isRecord(arguments_))
102
102
  return `<context_gc kind="terminal_exec_input"${formatVersion === 3 ? ' formatVersion="3"' : ''} originalBytes="${Buffer.byteLength(stableJson(arguments_))}"${formatVersion === 3 ? '' : ` originalSha256="${sha256(stableJson(arguments_))}"`} />`;
103
103
  const action = typeof arguments_.action === 'string'
104
104
  ? arguments_.action
105
105
  : typeof arguments_.command === 'string'
106
106
  ? 'start'
107
- : 'unknown';
107
+ : toolName === 'exec_wait'
108
+ ? 'poll'
109
+ : 'unknown';
108
110
  const original = stableJson(arguments_);
109
111
  const retained = {
110
112
  action,
@@ -190,7 +192,7 @@ export function normalizeContextGcExecResult(modelContent, completedPayload) {
190
192
  if (typeof modelContent !== 'string' ||
191
193
  !isRecord(completedPayload) ||
192
194
  completedPayload.type !== 'tool.completed' ||
193
- completedPayload.toolName !== 'exec' ||
195
+ (completedPayload.toolName !== 'exec' && completedPayload.toolName !== 'exec_wait') ||
194
196
  completedPayload.truncated !== true)
195
197
  return undefined;
196
198
  const data = completedPayload.data;
@@ -4,7 +4,7 @@ export const agentWaitToolDefinition = {
4
4
  name: 'agent_wait',
5
5
  allowedCallers: ['direct', 'programmatic'],
6
6
  parallelSafety: 'serial',
7
- description: 'Read or wait a bounded time for a child agent result. waitFor defaults to "completion"; use "message" only when a fresh public commentary/final message would change the next decision. Omit agentId to wait for whichever live child returns first, matching Codex-style untargeted waiting and avoiding UUID transcription; provide the exact agentId returned by agent_start only when observing a specific child. If no child is live, untargeted waiting reads the most recently created child. For an exec processId, use exec with action:"poll" instead. Omitting waitMs waits up to 30 seconds by default; waitMs=0 returns the current snapshot immediately. Positive waits range up to 5 minutes, and positive values below 10 seconds are raised to 10 seconds. When blocked on a live child result, prefer one 300000 ms completion wait rather than repeated short waits. Every result includes waitOutcome as snapshot, message, completion, or timeout plus the real agentId and description. A message, timeout with unread progress, or first terminal completion result includes the newly observed latest public commentary/final message; snapshots and repeated observations omit previously observed message text. latestActivity can still identify a public message or bounded tool started/completed/failed status. A queued or running result is not completion. A message wait consumes only the latest unread public message in the current Session runtime; tool activity does not wake it, and parent cancellation preserves unread progress for a later Execution. Do not use message waits as heartbeat checks or request progress merely to confirm that a child is still running. First terminal results include bounded summary, changedFiles, verification, and read evidence with path/line ranges; use that evidence to avoid repeating the full exploration, while independently checking consequential edits and claims.',
7
+ description: 'Read or wait a bounded time for a child agent result. waitFor defaults to "completion"; use "message" only when a fresh public commentary/final message would change the next decision. Omit agentId to wait for whichever live child returns first, matching Codex-style untargeted waiting and avoiding UUID transcription; provide the exact agentId returned by agent_start only when observing a specific child. If no child is live, untargeted waiting reads the most recently created child. For an exec processId, use exec_wait instead (tools.exec_wait in Code Mode). Omitting waitMs waits up to 30 seconds by default; waitMs=0 returns the current snapshot immediately. Positive waits range up to 5 minutes, and positive values below 10 seconds are raised to 10 seconds. When blocked on a live child result, prefer one 300000 ms completion wait rather than repeated short waits. Every result includes waitOutcome as snapshot, message, completion, or timeout plus the real agentId and description. A message, timeout with unread progress, or first terminal completion result includes the newly observed latest public commentary/final message; snapshots and repeated observations omit previously observed message text. latestActivity can still identify a public message or bounded tool started/completed/failed status. A queued or running result is not completion. A message wait consumes only the latest unread public message in the current Session runtime; tool activity does not wake it, and parent cancellation preserves unread progress for a later Execution. Do not use message waits as heartbeat checks or request progress merely to confirm that a child is still running. First terminal results include bounded summary, changedFiles, verification, and read evidence with path/line ranges; use that evidence to avoid repeating the full exploration, while independently checking consequential edits and claims.',
8
8
  inputSchema: {
9
9
  type: 'object',
10
10
  properties: {
@@ -8,7 +8,7 @@ export const applyPatchToolDefinition = {
8
8
  name: 'apply_patch',
9
9
  allowedCallers: ['direct', 'programmatic'],
10
10
  parallelSafety: 'serial',
11
- description: `Preferred for precise, reviewable local edits. Do not batch all task files merely to reduce tool calls. Group only a few tightly coupled edits whose combined patch is quick to generate and review; split broad or independent work into successive bounded patches before composing them. Submit a raw *** Begin Patch / *** End Patch patch. Supports Add File, Update File, Delete File, and Move to. Add File may replace an existing regular file, and Move to may replace an existing regular destination; duplicate operations on the same resolved path remain invalid. Update hunks accept @@ or @@ <change context>; context-only hunks may also be used as ordered anchors before a later changing hunk. Context is searched forward with Codex-style exact, whitespace-tolerant, then common Unicode punctuation-tolerant matching. Syntax and resolved paths are validated before writing, then file units are committed in order. Each file unit is atomic, and a Move keeps its source and destination atomic. On MAR_AGENT_PATCH_PARTIAL, earlier files remain applied; the error lists applied, failed, and pending paths, so retry only failed and pending paths. Existing file permissions, line endings, and final-newline state are preserved. Returns changed file paths and operations with per-file and total line counts when all units succeed. In Code Mode, the Promise resolves on success and rejects with code/message on failure; the returned envelope has data.files and data.totals, not an ok or success flag. Oversized patches are rejected without advertising defensive limits as recommended patch sizes. A first-unit MAR_AGENT_PATCH_CONFLICT applies nothing; hunk match conflicts identify the affected path and hunk so you can reread only that file before retrying.`,
11
+ description: `Preferred for precise, reviewable local edits. Do not batch all task files merely to reduce tool calls. Group only a few tightly coupled edits whose combined patch is quick to generate and review; split broad or independent work into successive bounded patches before composing them. Submit a raw *** Begin Patch / *** End Patch patch. Supports Add File, Update File, Delete File, and Move to. Add File may replace an existing regular file, and Move to may replace an existing regular destination; duplicate operations on the same resolved path remain invalid. Update hunks accept @@ or @@ <change context>; context-only hunks may also be used as ordered anchors before a later changing hunk. Omit unchanged file sections: an Update File must change or move the file, and its last hunk must not be context-only. Context is searched forward with Codex-style exact, whitespace-tolerant, then common Unicode punctuation-tolerant matching. Syntax and resolved paths are validated before writing, then file units are committed in order. Each file unit is atomic, and a Move keeps its source and destination atomic. On MAR_AGENT_PATCH_PARTIAL, earlier files remain applied; the error lists applied, failed, and pending paths, so retry only failed and pending paths. Existing file permissions, line endings, and final-newline state are preserved. Returns changed file paths and operations with per-file and total line counts when all units succeed. In Code Mode, the Promise resolves on success and rejects with code/message on failure; the returned envelope has data.files and data.totals, not an ok or success flag. Oversized patches are rejected without advertising defensive limits as recommended patch sizes. A first-unit MAR_AGENT_PATCH_CONFLICT applies nothing; hunk match conflicts identify the affected path and hunk so you can reread only that file before retrying.`,
12
12
  inputMode: 'freeform',
13
13
  outputSchema: {
14
14
  type: 'object',
@@ -13,7 +13,7 @@ export function toolCallers(tool) {
13
13
  export function allowsToolCaller(tool, caller) {
14
14
  // Control tools cannot be widened by a Host override or historical definition.
15
15
  if (caller === 'programmatic' &&
16
- ['code_exec', 'code_exec_wait', 'code_mode', 'tool_search', 'question'].includes(tool.name))
16
+ ['code_exec', 'code_exec_wait', 'code_mode', 'tool_search', 'question', 'skill'].includes(tool.name))
17
17
  return false;
18
18
  return toolCallers(tool).includes(caller);
19
19
  }
@@ -1,9 +1,15 @@
1
+ import { MarAgentError } from '../../error.js';
1
2
  import type { ToolExecutionContext, ToolExecutionResult } from '../../sdk/types.js';
2
3
  export interface CellOutput {
3
- text(value: string): void;
4
+ text(value: string, originalBytes?: number): void;
4
5
  image(value: NonNullable<ToolExecutionResult['images']>[number]): void;
5
6
  yield(): void;
6
7
  }
8
+ /** Keeps a failed tool call observable without discarding earlier explicit output. */
9
+ export declare class CodeCellExecutionError extends MarAgentError {
10
+ readonly result: ToolExecutionResult;
11
+ constructor(error: unknown, result: ToolExecutionResult);
12
+ }
7
13
  type Status = 'running' | 'completed' | 'failed' | 'cancelled';
8
14
  /** Session-owned execution, separate from the cancellable model-tool wait. */
9
15
  export declare class CodeCells {
@@ -2,6 +2,19 @@ import { randomUUID } from 'node:crypto';
2
2
  import { MarAgentError, describeMarAgentError } from '../../error.js';
3
3
  import { TOOL_EXECUTION_LIMITS as limits } from '../execution-limits.js';
4
4
  import { truncateModelText } from '../../runtime/token-budget.js';
5
+ import { BoundedTextOutput, renderCapturedText } from './text-output.js';
6
+ /** Keeps a failed tool call observable without discarding earlier explicit output. */
7
+ export class CodeCellExecutionError extends MarAgentError {
8
+ result;
9
+ constructor(error, result) {
10
+ const diagnostic = describeMarAgentError(error, 'MAR_AGENT_CODE_FAILED');
11
+ super(diagnostic.code, diagnostic.message.replace(`${diagnostic.code}: `, ''), {
12
+ cause: error,
13
+ ...(error instanceof MarAgentError ? { retryable: error.retryable } : {})
14
+ });
15
+ this.result = result;
16
+ }
17
+ }
5
18
  function options(value, wait) {
6
19
  if (!value || typeof value !== 'object' || Array.isArray(value))
7
20
  throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Code Exec options must be an object.');
@@ -11,11 +24,11 @@ function options(value, wait) {
11
24
  : ['yieldMs', 'maxOutputTokens'];
12
25
  if (Object.keys(input).some((key) => !keys.includes(key)))
13
26
  throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Unknown Code Exec option.');
14
- const yieldMs = input.yieldMs ?? limits.codeDefaultYieldMs;
27
+ const yieldMs = input.yieldMs ?? (wait ? limits.codeDefaultWaitYieldMs : limits.codeDefaultYieldMs);
15
28
  const maxOutputTokens = input.maxOutputTokens ?? limits.codeMaxOutputTokens;
16
29
  if (!Number.isSafeInteger(yieldMs) ||
17
30
  Number(yieldMs) < 0 ||
18
- Number(yieldMs) > limits.codeMaxYieldMs ||
31
+ Number(yieldMs) > (wait ? limits.codeMaxWaitYieldMs : limits.codeMaxYieldMs) ||
19
32
  !Number.isSafeInteger(maxOutputTokens) ||
20
33
  Number(maxOutputTokens) < 1 ||
21
34
  Number(maxOutputTokens) > limits.codeMaxOutputTokens ||
@@ -73,9 +86,10 @@ export class CodeCells {
73
86
  executionId: context.executionId,
74
87
  controller,
75
88
  status: 'running',
76
- text: [],
89
+ text: new BoundedTextOutput(limits.codeMaxOutputBytes),
77
90
  images: [],
78
91
  yielded: false,
92
+ truncated: false,
79
93
  listeners: new Set(),
80
94
  done: Promise.resolve()
81
95
  };
@@ -90,7 +104,7 @@ export class CodeCells {
90
104
  .then(async () => {
91
105
  releaseSession = await context.codeMode?.retainSession?.();
92
106
  return run(source, { ...context, signal: controller.signal }, {
93
- text: (text) => cell.text.push(text),
107
+ text: (text, originalBytes) => cell.text.append(text, originalBytes),
94
108
  image: (image) => cell.images.push(image),
95
109
  yield: () => {
96
110
  cell.yielded = true;
@@ -187,7 +201,12 @@ export class CodeCells {
187
201
  if (cell.terminal) {
188
202
  const snapshot = cell.terminal.data;
189
203
  const bounded = truncateModelText(snapshot.output, input.maxOutputTokens);
190
- const data = { ...snapshot, output: bounded.content, cached: true };
204
+ const data = {
205
+ ...snapshot,
206
+ output: bounded.content,
207
+ truncated: cell.terminal.truncated || bounded.truncated,
208
+ cached: true
209
+ };
191
210
  return {
192
211
  ...cell.terminal,
193
212
  data,
@@ -195,10 +214,12 @@ export class CodeCells {
195
214
  truncated: cell.terminal.truncated || bounded.truncated
196
215
  };
197
216
  }
198
- const text = cell.text.splice(0).join('\n');
217
+ const captured = cell.text.drain();
199
218
  const images = cell.images.splice(0);
219
+ const text = renderCapturedText(captured, images.length);
200
220
  cell.yielded = false;
201
221
  const bounded = truncateModelText(text, input.maxOutputTokens);
222
+ cell.truncated ||= captured.droppedBytes > 0 || bounded.truncated;
202
223
  const error = cell.error === undefined
203
224
  ? undefined
204
225
  : describeMarAgentError(cell.error, 'MAR_AGENT_CODE_FAILED');
@@ -207,13 +228,16 @@ export class CodeCells {
207
228
  status: cell.status,
208
229
  output: bounded.content,
209
230
  ...cell.result?.data,
231
+ outputBytes: captured.originalBytes,
232
+ outputDroppedBytes: captured.droppedBytes,
233
+ truncated: cell.truncated,
210
234
  ...(error ? { error: { code: error.code, message: error.message } } : {}),
211
235
  cached: false
212
236
  };
213
237
  const result = {
214
238
  content: JSON.stringify(data),
215
239
  data,
216
- truncated: bounded.truncated,
240
+ truncated: cell.truncated,
217
241
  ...(images.length ? { images } : {})
218
242
  };
219
243
  if (cell.status !== 'running')
@@ -221,7 +245,7 @@ export class CodeCells {
221
245
  // Keep synchronous completion compatible; retained waits always include explicit lifecycle metadata.
222
246
  if (initial && cell.status !== 'running') {
223
247
  if (cell.error !== undefined)
224
- throw cell.error;
248
+ throw new CodeCellExecutionError(cell.error, result);
225
249
  return { ...result, content: bounded.content || 'Code Exec completed without emitted text.' };
226
250
  }
227
251
  return result;
@@ -1,5 +1,8 @@
1
1
  import type { ToolCall, ToolExecutionResult } from '../../sdk/types.js';
2
2
  import type { CodeModeStateEntry, CodeModeStateMutation } from './state.js';
3
+ export type ProgrammaticToolResult = Omit<ToolExecutionResult, 'content'> & {
4
+ content?: string;
5
+ };
3
6
  export interface CodeModeCatalogEntry {
4
7
  readonly name: string;
5
8
  readonly description: string;
@@ -26,8 +29,8 @@ export interface CodeModeEngine {
26
29
  stateTotalBytes: number;
27
30
  sleepMs: number;
28
31
  };
29
- onToolCall(call: ToolCall): Promise<ToolExecutionResult>;
30
- onText(value: string): void;
32
+ onToolCall(call: ToolCall): Promise<ProgrammaticToolResult>;
33
+ onText(value: string, originalBytes?: number): void;
31
34
  onImage(value: unknown): void;
32
35
  onYield(): void;
33
36
  }): Promise<{