@myagentroam/agent 0.9.159 → 0.9.160

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 (128) hide show
  1. package/dist/agent-core/extensions/agents.d.ts +69 -0
  2. package/dist/agent-core/extensions/agents.js +160 -0
  3. package/dist/agent-core/extensions/contracts.d.ts +260 -0
  4. package/dist/agent-core/extensions/contracts.js +1 -0
  5. package/dist/agent-core/extensions/data.d.ts +84 -0
  6. package/dist/agent-core/extensions/data.js +83 -0
  7. package/dist/agent-core/extensions/environment.d.ts +27 -0
  8. package/dist/agent-core/extensions/environment.js +52 -0
  9. package/dist/agent-core/extensions/files.d.ts +64 -0
  10. package/dist/agent-core/extensions/files.js +26 -0
  11. package/dist/agent-core/extensions/hooks.d.ts +32 -0
  12. package/dist/agent-core/extensions/hooks.js +135 -0
  13. package/dist/agent-core/extensions/manager.d.ts +39 -0
  14. package/dist/agent-core/extensions/manager.js +458 -0
  15. package/dist/agent-core/extensions/mcp.d.ts +99 -0
  16. package/dist/agent-core/extensions/mcp.js +433 -0
  17. package/dist/agent-core/extensions/model-request.d.ts +60 -0
  18. package/dist/agent-core/extensions/model-request.js +167 -0
  19. package/dist/agent-core/extensions/model-stream.d.ts +9 -0
  20. package/dist/agent-core/extensions/model-stream.js +101 -0
  21. package/dist/agent-core/extensions/models.d.ts +43 -0
  22. package/dist/agent-core/extensions/models.js +1 -0
  23. package/dist/agent-core/extensions/network.d.ts +32 -0
  24. package/dist/agent-core/extensions/network.js +97 -0
  25. package/dist/agent-core/extensions/operation.d.ts +29 -0
  26. package/dist/agent-core/extensions/operation.js +98 -0
  27. package/dist/agent-core/extensions/processes.d.ts +52 -0
  28. package/dist/agent-core/extensions/processes.js +283 -0
  29. package/dist/agent-core/extensions/prompt-composition.d.ts +24 -0
  30. package/dist/agent-core/extensions/prompt-composition.js +106 -0
  31. package/dist/agent-core/extensions/prompt-input.d.ts +23 -0
  32. package/dist/agent-core/extensions/prompt-input.js +46 -0
  33. package/dist/agent-core/extensions/resources.d.ts +27 -0
  34. package/dist/agent-core/extensions/resources.js +221 -0
  35. package/dist/agent-core/extensions/services.d.ts +31 -0
  36. package/dist/agent-core/extensions/services.js +61 -0
  37. package/dist/agent-core/extensions/session-input.d.ts +151 -0
  38. package/dist/agent-core/extensions/session-input.js +351 -0
  39. package/dist/agent-core/extensions/session-messages.d.ts +41 -0
  40. package/dist/agent-core/extensions/session-messages.js +76 -0
  41. package/dist/agent-core/extensions/session.d.ts +68 -0
  42. package/dist/agent-core/extensions/session.js +593 -0
  43. package/dist/agent-core/extensions/settings.d.ts +59 -0
  44. package/dist/agent-core/extensions/settings.js +115 -0
  45. package/dist/agent-core/extensions/store.d.ts +10 -0
  46. package/dist/agent-core/extensions/store.js +131 -0
  47. package/dist/agent-core/extensions/tools.d.ts +61 -0
  48. package/dist/agent-core/extensions/tools.js +266 -0
  49. package/dist/agent-core/host/contracts.d.ts +25 -0
  50. package/dist/agent-core/host/local-agent-host.d.ts +10 -1
  51. package/dist/agent-core/host/local-agent-host.js +132 -20
  52. package/dist/agent-core/host/local-file-access.d.ts +30 -0
  53. package/dist/agent-core/host/local-file-access.js +107 -0
  54. package/dist/agent-core/host/local-network-access.d.ts +6 -0
  55. package/dist/agent-core/host/local-network-access.js +97 -0
  56. package/dist/agent-core/host/local-process-executor.d.ts +5 -1
  57. package/dist/agent-core/host/local-session-environment.d.ts +18 -0
  58. package/dist/agent-core/host/local-session-environment.js +102 -0
  59. package/dist/agent-core/model/anthropic-messages.js +73 -23
  60. package/dist/agent-core/model/contracts.d.ts +12 -0
  61. package/dist/agent-core/model/openai-responses.js +73 -12
  62. package/dist/agent-core/model/text-blocks.d.ts +7 -0
  63. package/dist/agent-core/model/text-blocks.js +40 -0
  64. package/dist/agent-core/prompts/index.d.ts +6 -2
  65. package/dist/agent-core/prompts/index.js +38 -24
  66. package/dist/agent-core/runtime/context/gc-tokens.js +6 -1
  67. package/dist/agent-core/runtime/context/normalize.js +21 -1
  68. package/dist/agent-core/runtime/context/projection.js +35 -1
  69. package/dist/agent-core/runtime/context/request-budget.js +5 -2
  70. package/dist/agent-core/runtime/context/restore.d.ts +5 -0
  71. package/dist/agent-core/runtime/context/restore.js +47 -4
  72. package/dist/agent-core/runtime/context/usage.js +2 -3
  73. package/dist/agent-core/runtime/contracts.d.ts +2 -0
  74. package/dist/agent-core/runtime/execution.js +5 -2
  75. package/dist/agent-core/sdk/agent.js +177 -40
  76. package/dist/agent-core/sdk/extension-agents.d.ts +9 -0
  77. package/dist/agent-core/sdk/extension-agents.js +51 -0
  78. package/dist/agent-core/sdk/extension-models.d.ts +13 -0
  79. package/dist/agent-core/sdk/extension-models.js +128 -0
  80. package/dist/agent-core/sdk/extension-session.d.ts +30 -0
  81. package/dist/agent-core/sdk/extension-session.js +103 -0
  82. package/dist/agent-core/sdk/extension-tools.d.ts +16 -0
  83. package/dist/agent-core/sdk/extension-tools.js +126 -0
  84. package/dist/agent-core/sdk/session.d.ts +26 -2
  85. package/dist/agent-core/sdk/session.js +1215 -506
  86. package/dist/agent-core/sdk/types.d.ts +3 -0
  87. package/dist/agent-core/session/context-gc-checkpoint.js +1 -1
  88. package/dist/agent-core/session/jsonl-store.d.ts +28 -8
  89. package/dist/agent-core/session/jsonl-store.js +124 -84
  90. package/dist/agent-core/session/types.d.ts +4 -1
  91. package/dist/agent-core/storage/file-lock.d.ts +4 -0
  92. package/dist/agent-core/storage/file-lock.js +83 -0
  93. package/dist/agent-core/subagent/contracts.d.ts +11 -1
  94. package/dist/agent-core/subagent/scheduler.d.ts +8 -0
  95. package/dist/agent-core/subagent/session-controller.d.ts +22 -1
  96. package/dist/agent-core/subagent/session-controller.js +355 -98
  97. package/dist/agent-core/tools/agent/agent-start.d.ts +1 -1
  98. package/dist/agent-core/tools/code-mode/cells.js +5 -15
  99. package/dist/agent-core/tools/code-mode/dispatch.d.ts +1 -0
  100. package/dist/agent-core/tools/code-mode/dispatch.js +13 -1
  101. package/dist/agent-core/tools/code-mode/index.js +1 -2
  102. package/dist/agent-core/tools/code-mode/observations.d.ts +5 -0
  103. package/dist/agent-core/tools/code-mode/observations.js +16 -5
  104. package/dist/agent-core/tools/code-mode/quickjs-engine.js +10 -5
  105. package/dist/agent-core/tools/execution.d.ts +11 -2
  106. package/dist/agent-core/tools/execution.js +58 -21
  107. package/dist/agent-core/tools/input.d.ts +2 -0
  108. package/dist/agent-core/tools/input.js +36 -0
  109. package/dist/agent-core/tools/local/apply-patch.js +21 -17
  110. package/dist/agent-core/tools/local/exec.d.ts +4 -4
  111. package/dist/agent-core/tools/local/exec.js +234 -62
  112. package/dist/agent-core/tools/local/registry.d.ts +6 -0
  113. package/dist/agent-core/tools/local/registry.js +60 -9
  114. package/dist/agent-core/tools/mcp/catalog.d.ts +6 -2
  115. package/dist/agent-core/tools/mcp/catalog.js +4 -4
  116. package/dist/agent-core/tools/mcp/history.js +2 -2
  117. package/dist/agent-core/tools/mcp/tool-search.d.ts +3 -1
  118. package/dist/agent-core/tools/mcp/tool-search.js +2 -2
  119. package/dist/agent-core/tools/output-projection.d.ts +3 -1
  120. package/dist/agent-core/tools/output-projection.js +11 -2
  121. package/dist/agent-core/tools/runtime-context.d.ts +1 -0
  122. package/dist/agent-core/tools/runtime.d.ts +3 -1
  123. package/dist/agent-core/tools/runtime.js +22 -7
  124. package/dist/agent-core/tools/scheduler.d.ts +2 -0
  125. package/dist/agent-core/tools/scheduler.js +43 -6
  126. package/dist/index.d.ts +21 -0
  127. package/dist/sandbox/linux/bubblewrap-sandbox-executor.js +5 -2
  128. package/package.json +1 -1
@@ -8,5 +8,5 @@ export declare function agentStartToolDefinitionWithModels(models: readonly {
8
8
  }[]): ClientToolDefinition;
9
9
  export declare function executeAgentStart(arguments_: unknown, context: ToolRuntimeContext): Promise<{
10
10
  content: string;
11
- data: import("../../subagent/contracts.js").SubagentTaskSnapshot;
11
+ data: import("../../subagent/contracts.js").SubagentRunResult;
12
12
  }>;
@@ -22,26 +22,16 @@ function options(value, wait) {
22
22
  if (!value || typeof value !== 'object' || Array.isArray(value))
23
23
  throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Code Exec options must be an object.');
24
24
  const input = value;
25
- const keys = wait
26
- ? ['cellId', 'yieldMs', 'maxOutputTokens', 'action']
27
- : ['yieldMs', 'maxOutputTokens'];
28
- if (Object.keys(input).some((key) => !keys.includes(key)))
29
- throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Unknown Code Exec option.');
30
25
  const yieldMs = input.yieldMs ?? (wait ? limits.codeDefaultWaitYieldMs : limits.codeDefaultYieldMs);
31
- const maxOutputTokens = input.maxOutputTokens ?? limits.codeMaxOutputTokens;
32
26
  if (!Number.isSafeInteger(yieldMs) ||
33
27
  Number(yieldMs) < 0 ||
34
28
  Number(yieldMs) > (wait ? limits.codeMaxWaitYieldMs : limits.codeMaxYieldMs) ||
35
- !Number.isSafeInteger(maxOutputTokens) ||
36
- Number(maxOutputTokens) < 1 ||
37
- Number(maxOutputTokens) > limits.codeMaxOutputTokens ||
38
29
  (wait && (typeof input.cellId !== 'string' || !input.cellId)) ||
39
30
  (input.action !== undefined && input.action !== 'poll' && input.action !== 'cancel'))
40
31
  throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Code Exec options are out of range.');
41
32
  return {
42
33
  cellId: input.cellId,
43
34
  yieldMs: Number(yieldMs),
44
- maxOutputTokens: Number(maxOutputTokens),
45
35
  action: input.action ?? 'poll'
46
36
  };
47
37
  }
@@ -208,12 +198,12 @@ export class CodeCells {
208
198
  throw new MarAgentError('MAR_AGENT_INPUT_CAPABILITY_UNSUPPORTED', 'The current model cannot receive this cell image output.');
209
199
  if (cell.terminal) {
210
200
  if (cell.terminal.observations) {
211
- const bounded = boundObservations(cell.terminal.observations, input.maxOutputTokens);
201
+ const bounded = boundObservations(cell.terminal.observations, limits.codeMaxOutputTokens);
212
202
  const visible = selectObservationImages(bounded.observations, cell.terminal.images ?? []);
213
203
  const original = cell.terminal.data;
214
204
  const data = {
215
205
  ...original,
216
- output: truncateModelText(original.output ?? '', input.maxOutputTokens).content,
206
+ output: truncateModelText(original.output ?? '', limits.codeMaxOutputTokens).content,
217
207
  observations: visible.observations,
218
208
  cached: true,
219
209
  truncated: cell.terminal.truncated === true || bounded.truncated
@@ -233,7 +223,7 @@ export class CodeCells {
233
223
  };
234
224
  }
235
225
  const snapshot = cell.terminal.data;
236
- const bounded = truncateModelText(snapshot.output, input.maxOutputTokens);
226
+ const bounded = truncateModelText(snapshot.output, limits.codeMaxOutputTokens);
237
227
  const data = {
238
228
  ...snapshot,
239
229
  output: bounded.content,
@@ -251,7 +241,7 @@ export class CodeCells {
251
241
  const images = cell.images.splice(0);
252
242
  const text = renderCapturedText(captured, images.length);
253
243
  cell.yielded = false;
254
- const bounded = truncateModelText(text, input.maxOutputTokens);
244
+ const bounded = truncateModelText(text, limits.codeMaxOutputTokens);
255
245
  const pending = cell.observations.splice(0);
256
246
  if (!pending.length && captured.text)
257
247
  pending.push({
@@ -261,7 +251,7 @@ export class CodeCells {
261
251
  value: captured.text,
262
252
  truncated: captured.droppedBytes > 0
263
253
  });
264
- const structured = boundObservations(pending, input.maxOutputTokens);
254
+ const structured = boundObservations(pending, limits.codeMaxOutputTokens);
265
255
  const visible = selectObservationImages(structured.observations, images, cell.imageOffset);
266
256
  const remapped = visible.observations;
267
257
  const visibleImages = visible.images;
@@ -14,6 +14,7 @@ export declare function createCodeToolDispatch(input: {
14
14
  expectations: ReadonlyMap<string, McpToolCallContext['expectedTool']>;
15
15
  parentCallId: string;
16
16
  schedule: ReturnType<typeof createSessionToolScheduler>;
17
+ assertLive: () => void;
17
18
  emit: (event: Payload) => Promise<void>;
18
19
  }): (call: ToolCall, signal: AbortSignal) => Promise<ToolExecutionResult>;
19
20
  export {};
@@ -8,24 +8,29 @@ import { executeRegisteredTool } from '../runtime.js';
8
8
  export function createCodeToolDispatch(input) {
9
9
  const definitions = new Map(input.tools.map((tool) => [tool.name, tool]));
10
10
  return async (call, signal) => {
11
+ input.assertLive();
11
12
  const definition = definitions.get(call.name);
12
13
  if (!definition || !allowsToolCaller(definition, 'programmatic'))
13
14
  throw new MarAgentError('MAR_AGENT_TOOL_NOT_AVAILABLE', 'The requested nested tool is unavailable.');
14
15
  return input.schedule(definition, async () => {
16
+ input.assertLive();
15
17
  signal.throwIfAborted();
16
18
  const callId = call.callId ?? randomUUID();
17
19
  const identity = { toolName: call.name, callId, parentCallId: input.parentCallId };
18
20
  await input.emit({ type: 'tool.started', ...identity, input: call.arguments });
19
21
  try {
22
+ input.assertLive();
23
+ signal.throwIfAborted();
20
24
  const expected = input.expectations.get(call.name);
21
25
  const result = await executeRegisteredTool(call.name, call.arguments, callId, {
22
26
  ...input.context,
23
27
  signal,
24
28
  ...(expected ? { expectedMcpTool: expected } : {})
25
- });
29
+ }, 'programmatic');
26
30
  const bounded = boundedModelToolOutput(call.name, result.content, result.data);
27
31
  const data = structuredToolEventData(call.name, result.data);
28
32
  const summary = result.content.slice(0, AGENT_EXECUTION_POLICY.toolEventSummaryCharacters);
33
+ input.assertLive();
29
34
  await input.emit({
30
35
  type: 'tool.completed',
31
36
  ...identity,
@@ -43,6 +48,13 @@ export function createCodeToolDispatch(input) {
43
48
  };
44
49
  }
45
50
  catch (error) {
51
+ // Retirement must not enqueue a failure event into the replacement Session epoch.
52
+ try {
53
+ input.assertLive();
54
+ }
55
+ catch {
56
+ throw error;
57
+ }
46
58
  const diagnostic = describeMarAgentError(error, 'MAR_AGENT_HOST_OPERATION_FAILED');
47
59
  await input.emit({
48
60
  type: 'tool.failed',
@@ -39,7 +39,7 @@ Built-in namespaces:
39
39
  - catalog.tools() returns a read-only snapshot of the tools callable in this cell, including name, description, inputSchema and inputMode. It does not discover or authorize additional tools.
40
40
  - await cell.sleep(ms) performs a cancellable delay up to ${limits.codeSleepMaxMs} ms.
41
41
  - await cell.yield_control() returns accumulated output to the model immediately while this cell continues running.
42
- - Optionally start source with // @options: {"yieldMs": ${limits.codeDefaultYieldMs}, "maxOutputTokens": 10000}. yieldMs defaults to ${limits.codeDefaultYieldMs} ms and controls this call's wait window (0..${limits.codeMaxYieldMs}), not nested tool timeouts or wait windows. A running result includes cellId; use direct code_exec_wait for new output, completion or termination.
42
+ - Optionally start source with // @options: {"yieldMs": ${limits.codeDefaultYieldMs}}. yieldMs defaults to ${limits.codeDefaultYieldMs} ms and controls this call's wait window (0..${limits.codeMaxYieldMs}), not nested tool timeouts or wait windows. A running result includes cellId; use direct code_exec_wait for new output, completion or termination.
43
43
  - Registered cells are Session warm resources: ending or cancelling the model Execution cancels its wait, not the cell. Reuse retained cellId; do not start duplicate work. Host cleanup or explicit termination stops the cell.
44
44
  - tools, observe, state, catalog and cell are frozen bindings. Do not shadow their names.
45
45
 
@@ -85,7 +85,6 @@ export const codeExecWaitToolDefinition = {
85
85
  default: limits.codeDefaultWaitYieldMs,
86
86
  description: `Wait up to this many milliseconds. Defaults to ${limits.codeDefaultWaitYieldMs}; maximum ${limits.codeMaxWaitYieldMs}. Zero returns immediately without stopping the cell. Waiting does not restart the cell or extend nested tool timeouts.`
87
87
  },
88
- maxOutputTokens: { type: 'integer', minimum: 1, maximum: limits.codeMaxOutputTokens },
89
88
  action: {
90
89
  type: 'string',
91
90
  enum: ['poll', 'cancel'],
@@ -85,6 +85,9 @@ export declare function boundObservations(values: readonly CodeObservation[], ma
85
85
  export declare function newObservationId(): string;
86
86
  export declare function observationModelParts(message: {
87
87
  content?: string;
88
+ textBlocks?: readonly {
89
+ readonly text: string;
90
+ }[];
88
91
  observations?: readonly CodeObservation[];
89
92
  images?: readonly {
90
93
  mimeType: string;
@@ -100,4 +103,6 @@ export declare function observationModelParts(message: {
100
103
  dataBase64: string;
101
104
  };
102
105
  }> | undefined;
106
+ /** Uses the same visible text as both adapters, without pixels or private observation data. */
107
+ export declare function observationModelText(message: Parameters<typeof observationModelParts>[0]): string | undefined;
103
108
  export declare function parseObservations(value: unknown): CodeObservation[] | undefined;
@@ -69,6 +69,11 @@ export function visibleObservation(value) {
69
69
  export function renderObservations(values) {
70
70
  return JSON.stringify(values.map(visibleObservation));
71
71
  }
72
+ function visibleObservationText(value) {
73
+ const { content: _content, value: _value, ...header } = visibleObservation(value);
74
+ const body = observationContent(value);
75
+ return JSON.stringify(header) + (body.length ? `\n${body}` : '');
76
+ }
72
77
  export function renderObservationResult(values, original) {
73
78
  try {
74
79
  const envelope = JSON.parse(original);
@@ -89,7 +94,7 @@ export function boundObservations(values, maximumTokens) {
89
94
  const empty = original.type === 'tool_result'
90
95
  ? { ...original, result: { ...original.result, content: '', truncated: true } }
91
96
  : { ...original, value: '', truncated: true };
92
- const overhead = countModelTokens(JSON.stringify(visibleObservation(empty))) + 8;
97
+ const overhead = countModelTokens(visibleObservationText(empty)) + 8;
93
98
  if (overheadBudget + overhead > remaining) {
94
99
  truncated = true;
95
100
  break;
@@ -119,7 +124,7 @@ export function boundObservations(values, maximumTokens) {
119
124
  value: bounded.content,
120
125
  truncated: original.truncated === true || bodyTruncated
121
126
  };
122
- if (countModelTokens(JSON.stringify(visibleObservation(value))) + 1 <=
127
+ if (countModelTokens(visibleObservationText(value)) + 1 <=
123
128
  remaining - (overheadBudget - overhead) ||
124
129
  bodyBudget === 0)
125
130
  break;
@@ -127,7 +132,7 @@ export function boundObservations(values, maximumTokens) {
127
132
  }
128
133
  overheadBudget -= overhead;
129
134
  observations.push(value);
130
- remaining -= countModelTokens(JSON.stringify(visibleObservation(value))) + 1;
135
+ remaining -= countModelTokens(visibleObservationText(value)) + 1;
131
136
  truncated ||=
132
137
  bodyTruncated ||
133
138
  (original.type === 'tool_result'
@@ -140,7 +145,7 @@ export function newObservationId() {
140
145
  return randomUUID();
141
146
  }
142
147
  export function observationModelParts(message) {
143
- if (!message.observations || !message.images?.length)
148
+ if (message.textBlocks !== undefined || !message.observations?.length)
144
149
  return undefined;
145
150
  const prefix = [];
146
151
  try {
@@ -154,7 +159,7 @@ export function observationModelParts(message) {
154
159
  return [
155
160
  ...prefix,
156
161
  ...message.observations.flatMap((value) => {
157
- const parts = [{ type: 'text', text: JSON.stringify(visibleObservation(value)) }];
162
+ const parts = [{ type: 'text', text: visibleObservationText(value) }];
158
163
  for (const index of observationImages(value)) {
159
164
  const image = message.images?.[index];
160
165
  if (image)
@@ -164,6 +169,12 @@ export function observationModelParts(message) {
164
169
  })
165
170
  ];
166
171
  }
172
+ /** Uses the same visible text as both adapters, without pixels or private observation data. */
173
+ export function observationModelText(message) {
174
+ return observationModelParts(message)
175
+ ?.flatMap((part) => (part.type === 'text' ? [part.text] : []))
176
+ .join('\n');
177
+ }
167
178
  export function parseObservations(value) {
168
179
  if (!Array.isArray(value) || value.length > 4096)
169
180
  return undefined;
@@ -111,8 +111,13 @@ export async function createCodeModeEngine(options) {
111
111
  'WebSocket', 'WebAssembly', 'SharedArrayBuffer', 'Atomics', 'text', 'image'])
112
112
  delete globalThis[name];
113
113
  globalThis.__parseJson = JSON.parse;
114
- globalThis.__serialize = value => {
115
- return JSON.stringify(value, (_key, item) => {
114
+ globalThis.__serialize = (value, toolInput = false) => {
115
+ let root = true;
116
+ return JSON.stringify(value, function (_key, item) {
117
+ const first = root;
118
+ root = false;
119
+ if (toolInput && !first && !Array.isArray(this) && typeof item === 'undefined')
120
+ return undefined;
116
121
  if (['bigint', 'function', 'symbol', 'undefined'].includes(typeof item))
117
122
  throw Object.assign(new TypeError('Only JSON values may cross the tool bridge.'),
118
123
  { code: 'MAR_AGENT_TOOL_INPUT_INVALID' });
@@ -183,8 +188,8 @@ export async function createCodeModeEngine(options) {
183
188
  handle.dispose();
184
189
  }
185
190
  };
186
- const jsonValue = (handle) => {
187
- const result = callGuest(serialize, vm.undefined, handle);
191
+ const jsonValue = (handle, toolInput = false) => {
192
+ const result = callGuest(serialize, vm.undefined, handle, toolInput ? vm.true : vm.false);
188
193
  if (result.error)
189
194
  return { error: result.error };
190
195
  try {
@@ -222,7 +227,7 @@ export async function createCodeModeEngine(options) {
222
227
  let toolCallSequence = 0;
223
228
  for (const name of input.tools) {
224
229
  method(tools, name, (args = vm.undefined) => {
225
- const parsed = jsonValue(args);
230
+ const parsed = jsonValue(args, true);
226
231
  if (parsed.error)
227
232
  return parsed;
228
233
  const operationId = ++toolCallSequence;
@@ -3,6 +3,7 @@ import type { EventPayload } from '../runtime/events.js';
3
3
  import type { ClientToolDefinition, McpToolCallContext, ToolExecutionContext, ToolExecutionResult } from './contracts.js';
4
4
  import type { ToolRuntimeContext, ToolRuntimeImage } from './runtime-context.js';
5
5
  import { type createSessionToolScheduler } from './scheduler.js';
6
+ import type { ToolCall } from './contracts.js';
6
7
  export type ClientToolCall = Extract<ModelEvent, {
7
8
  type: 'client_tool.call';
8
9
  }>;
@@ -26,11 +27,19 @@ export interface RoundToolScope {
26
27
  readonly expectations: ReadonlyMap<string, McpToolCallContext['expectedTool']>;
27
28
  readonly extensionToolNames: ReadonlySet<string>;
28
29
  readonly schedule: ReturnType<typeof createSessionToolScheduler>;
29
- readonly emit: (event: EventPayload) => Promise<void>;
30
+ readonly assertLive: () => void;
31
+ readonly assertExecutionLive: () => void;
32
+ readonly emit: (event: EventPayload, assertLive?: () => void) => Promise<void>;
30
33
  readonly searchMcpTools: NonNullable<ToolRuntimeContext['searchMcpTools']>;
31
34
  readonly recentImages: () => ToolRuntimeImage[];
32
35
  readonly captureCodeContext: () => Omit<ToolRuntimeContext, 'signal' | 'codeMode' | 'expectedMcpTool' | 'searchMcpTools'>;
33
- readonly codeModeContext: (call: ClientToolCall) => Promise<Omit<NonNullable<ToolExecutionContext['codeMode']>, 'invoke'>>;
36
+ readonly codeModeContext: (call: ToolCall & {
37
+ readonly callId: string;
38
+ }) => Promise<Omit<NonNullable<ToolExecutionContext['codeMode']>, 'invoke'>>;
34
39
  }
40
+ /** Shared raw context construction for physical tools and native calls in a real model round. */
41
+ export declare function createRoundToolRuntimeContext(scope: RoundToolScope, call: ToolCall & {
42
+ readonly callId: string;
43
+ }, signal: AbortSignal): Promise<ToolRuntimeContext>;
35
44
  /** Complete every call in model order even if individual execution fails. */
36
45
  export declare function executeRoundTools(calls: readonly ClientToolCall[], scope: RoundToolScope): Promise<ExecutedToolCall[]>;
@@ -6,6 +6,62 @@ import { createCodeToolDispatch } from './code-mode/dispatch.js';
6
6
  import { boundedModelToolOutput, structuredToolArtifacts, structuredToolEventData } from './output-projection.js';
7
7
  import { executeRegisteredTool } from './runtime.js';
8
8
  import { scheduleToolCallBatch } from './scheduler.js';
9
+ /** Shared raw context construction for physical tools and native calls in a real model round. */
10
+ export async function createRoundToolRuntimeContext(scope, call, signal) {
11
+ scope.assertLive();
12
+ signal.throwIfAborted();
13
+ const codeMode = await scope.codeModeContext(call);
14
+ scope.assertLive();
15
+ signal.throwIfAborted();
16
+ const assertSessionLive = scope.assertLive;
17
+ const assertExecutionLive = scope.assertExecutionLive;
18
+ const emitCodeEvent = scope.emit;
19
+ const retainSession = codeMode.retainSession?.bind(codeMode);
20
+ let retained = 0;
21
+ const assertDispatchLive = () => {
22
+ assertSessionLive();
23
+ if (retained === 0)
24
+ assertExecutionLive();
25
+ };
26
+ return {
27
+ ...scope.context,
28
+ ...(scope.expectations.get(call.name)
29
+ ? { expectedMcpTool: scope.expectations.get(call.name) }
30
+ : {}),
31
+ searchMcpTools: scope.searchMcpTools,
32
+ signal,
33
+ codeMode: {
34
+ ...codeMode,
35
+ ...(retainSession === undefined
36
+ ? {}
37
+ : {
38
+ retainSession: async () => {
39
+ assertDispatchLive();
40
+ const release = await retainSession();
41
+ retained++;
42
+ let released = false;
43
+ return async () => {
44
+ if (released)
45
+ return;
46
+ released = true;
47
+ retained--;
48
+ await release();
49
+ };
50
+ }
51
+ }),
52
+ invoke: createCodeToolDispatch({
53
+ context: scope.captureCodeContext(),
54
+ tools: scope.tools,
55
+ expectations: scope.expectations,
56
+ parentCallId: call.callId,
57
+ schedule: scope.schedule,
58
+ assertLive: assertDispatchLive,
59
+ emit: (event) => emitCodeEvent(event, assertDispatchLive)
60
+ })
61
+ },
62
+ ...(scope.extensionToolNames.has(call.name) ? { recentImages: scope.recentImages() } : {})
63
+ };
64
+ }
9
65
  /** Complete every call in model order even if individual execution fails. */
10
66
  export async function executeRoundTools(calls, scope) {
11
67
  const executeCall = async (call, signal = scope.context.signal, caller = 'direct', parentCallId) => {
@@ -21,29 +77,10 @@ export async function executeRoundTools(calls, scope) {
21
77
  throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', call.argumentError);
22
78
  if (!scope.tools.some((tool) => tool.name === call.name && allowsToolCaller(tool, caller)))
23
79
  throw new MarAgentError('MAR_AGENT_TOOL_NOT_AVAILABLE', 'Tool is unavailable in this execution.');
24
- const codeMode = await scope.codeModeContext(call);
80
+ const context = await createRoundToolRuntimeContext(scope, call, signal);
25
81
  const runTool = () => {
26
82
  signal.throwIfAborted();
27
- return executeRegisteredTool(call.name, call.arguments, call.callId, {
28
- ...scope.context,
29
- ...(scope.expectations.get(call.name)
30
- ? { expectedMcpTool: scope.expectations.get(call.name) }
31
- : {}),
32
- searchMcpTools: scope.searchMcpTools,
33
- signal,
34
- codeMode: {
35
- ...codeMode,
36
- invoke: createCodeToolDispatch({
37
- context: scope.captureCodeContext(),
38
- tools: scope.tools,
39
- expectations: scope.expectations,
40
- parentCallId: call.callId,
41
- schedule: scope.schedule,
42
- emit: scope.emit
43
- })
44
- },
45
- ...(scope.extensionToolNames.has(call.name) ? { recentImages: scope.recentImages() } : {})
46
- });
83
+ return executeRegisteredTool(call.name, call.arguments, call.callId, context);
47
84
  };
48
85
  const output = call.name === 'code_exec' || call.name === 'code_exec_wait'
49
86
  ? await runTool()
@@ -0,0 +1,2 @@
1
+ /** Compatibility is not validation: required fields, types and values stay with the tool. */
2
+ export declare function normalizeToolInput(input: unknown, schema: unknown): unknown;
@@ -0,0 +1,36 @@
1
+ /** Compatibility is not validation: required fields, types and values stay with the tool. */
2
+ export function normalizeToolInput(input, schema) {
3
+ const visit = (value, definition, depth) => {
4
+ if (depth > 64 || value === null || typeof value !== 'object')
5
+ return value;
6
+ const shape = record(definition) ? definition : {};
7
+ if (Array.isArray(value))
8
+ return value.map((item) => visit(item, shape.items, depth + 1));
9
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)
10
+ return value;
11
+ const properties = record(shape.properties) ? shape.properties : {};
12
+ // Do not guess branch selection, resolve refs, or discard pattern-defined parameters.
13
+ const closed = shape.additionalProperties === false &&
14
+ !['$ref', 'anyOf', 'oneOf', 'allOf', 'if', 'patternProperties', 'dependentSchemas'].some((key) => Object.hasOwn(shape, key));
15
+ const result = {};
16
+ for (const [key, item] of Object.entries(value)) {
17
+ if (item === undefined)
18
+ continue;
19
+ const declared = Object.hasOwn(properties, key);
20
+ if (closed && !declared)
21
+ continue;
22
+ const child = declared ? properties[key] : shape.additionalProperties;
23
+ Object.defineProperty(result, key, {
24
+ value: visit(item, child, depth + 1),
25
+ enumerable: true,
26
+ writable: true,
27
+ configurable: true
28
+ });
29
+ }
30
+ return result;
31
+ };
32
+ return visit(input, schema, 0);
33
+ }
34
+ function record(value) {
35
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
36
+ }
@@ -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. 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.`,
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, including trailing hunks, and unchanged file sections are validated in order but are not written or reported as edits. The patch must include a change or move. 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',
@@ -73,18 +73,28 @@ export class ApplyPatchTool {
73
73
  const resolvedChanges = await this.#resolveChanges(changes);
74
74
  const results = [];
75
75
  let totalBytes = 0;
76
- for (const [index, resolvedChange] of resolvedChanges.entries()) {
76
+ const pendingChanges = [];
77
+ for (const resolvedChange of resolvedChanges) {
78
+ if (resolvedChange.kind === 'update' &&
79
+ resolvedChange.change.hunks.every((hunk) => hunk.anchor))
80
+ totalBytes = (await this.#preflight(resolvedChange, totalBytes)).totalBytes;
81
+ else
82
+ pendingChanges.push(resolvedChange);
83
+ }
84
+ for (const [index, resolvedChange] of pendingChanges.entries()) {
77
85
  try {
78
86
  const preflight = await this.#preflight(resolvedChange, totalBytes);
79
- await this.#commit(preflight.mutations);
80
- results.push(preflight.result);
87
+ if (preflight.mutations.length)
88
+ await this.#commit(preflight.mutations);
89
+ if (preflight.result)
90
+ results.push(preflight.result);
81
91
  totalBytes = preflight.totalBytes;
82
92
  }
83
93
  catch (error) {
84
94
  if (!results.length ||
85
95
  (error instanceof MarAgentError && error.code === 'MAR_AGENT_PATCH_ROLLBACK_FAILED'))
86
96
  throw error;
87
- throw partialPatchError(error, results, resolvedChanges.slice(index));
97
+ throw partialPatchError(error, results, pendingChanges.slice(index));
88
98
  }
89
99
  }
90
100
  return {
@@ -185,6 +195,8 @@ export class ApplyPatchTool {
185
195
  totalBytes = enforceContentLimits(content, totalBytes);
186
196
  const addedLines = change.hunks.reduce((sum, hunk) => sum + hunk.addedLines, 0);
187
197
  const deletedLines = change.hunks.reduce((sum, hunk) => sum + hunk.deletedLines, 0);
198
+ if (resolvedChange.kind === 'update' && !addedLines && !deletedLines)
199
+ return { mutations: [], totalBytes };
188
200
  if (resolvedChange.kind === 'move') {
189
201
  const moveChange = resolvedChange.change;
190
202
  const targetStat = await optionalLstat(resolvedChange.target);
@@ -385,18 +397,8 @@ function parsePatch(patch) {
385
397
  anchor: !addedLines && !deletedLines
386
398
  });
387
399
  }
388
- let hasFollowingChange = false;
389
- for (let hunkIndex = hunks.length - 1; hunkIndex >= 0; hunkIndex--) {
390
- const hunk = hunks[hunkIndex];
391
- if (hunk.anchor) {
392
- if (!hasFollowingChange)
393
- invalid('Context-only patch hunks must precede a changing hunk.');
394
- }
395
- else
396
- hasFollowingChange = true;
397
- }
398
- if (!hasFollowingChange && !targetPath)
399
- invalid('Update operations must change or move a file.');
400
+ if (!hunks.length && !targetPath)
401
+ invalid('Update operations must contain hunks or move a file.');
400
402
  changes.push({ kind: 'update', path, ...(targetPath ? { targetPath } : {}), hunks });
401
403
  }
402
404
  else
@@ -404,6 +406,8 @@ function parsePatch(patch) {
404
406
  if (changes.length > TOOL_EXECUTION_LIMITS.patchMaxFiles)
405
407
  throw new MarAgentError('MAR_AGENT_TOOL_OUTPUT_LIMIT', 'Patch exceeds the file limit.');
406
408
  }
409
+ if (!changes.some((change) => change.kind !== 'update' || change.targetPath || change.hunks.some((hunk) => !hunk.anchor)))
410
+ invalid('Patch must change or move at least one file.');
407
411
  return changes;
408
412
  }
409
413
  function applyHunks(original, hunks, path) {
@@ -2,6 +2,8 @@ import { type LocalProcessExecutor } from '../../host/local-process-executor.js'
2
2
  import type { ClientToolDefinition } from '../../model/contracts.js';
3
3
  import type { ExecResult } from '../contracts.js';
4
4
  import { WorkspacePathResolver } from '../shared/path-resolver.js';
5
+ import { type CoreProcessInput, type CoreProcessHandle } from '../../extensions/processes.js';
6
+ import type { CoreOperationContext } from '../../extensions/operation.js';
5
7
  export type { ExecResult } from '../contracts.js';
6
8
  export type ExecInput = {
7
9
  action?: 'start';
@@ -34,10 +36,8 @@ export declare class ExecTool {
34
36
  private readonly shell?;
35
37
  private readonly processExecutor;
36
38
  constructor(paths: WorkspacePathResolver, maxOutputBytes?: number, maxProcesses?: number, environment?: NodeJS.ProcessEnv, shell?: string | undefined, processExecutor?: LocalProcessExecutor);
37
- execute(arguments_: unknown, signal?: AbortSignal, owner?: {
38
- readonly sessionId: string;
39
- readonly executionId: string;
40
- }): Promise<ExecResult>;
39
+ execute(arguments_: unknown, signal?: AbortSignal, owner?: NonNullable<import('../../host/local-process-executor.js').LocalProcessSpawnRequest['owner']>): Promise<ExecResult>;
40
+ spawn(input: CoreProcessInput, call: CoreOperationContext): Promise<CoreProcessHandle>;
41
41
  listSessionResources(sessionId: string): {
42
42
  processId: string;
43
43
  status: "completed" | "cancelled" | "running";