@myagentroam/agent 0.9.85 → 0.9.86

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.
@@ -5,7 +5,6 @@ export interface ModelContinuationCheckpoint {
5
5
  readonly configurationHash: string;
6
6
  readonly responseId: string;
7
7
  readonly inputPrefixLength: number;
8
- readonly inputPrefixHash: string;
9
8
  }
10
9
  export interface ClientToolDefinition {
11
10
  name: string;
@@ -35,9 +35,14 @@ export class OpenAiResponsesAdapter {
35
35
  if (this.#cacheLeased ||
36
36
  this.#cachedState.lastRequest !== undefined ||
37
37
  this.#cachedState.lastResponse !== undefined ||
38
- !responsesContinuationEnabled(this.#configuration) ||
38
+ !responsesPersistedContinuationEnabled(this.#configuration) ||
39
39
  checkpoint.version !== 2 ||
40
40
  checkpoint.protocol !== 'OPENAI_RESPONSES' ||
41
+ typeof checkpoint.responseId !== 'string' ||
42
+ checkpoint.responseId.length === 0 ||
43
+ checkpoint.responseId.length > 1024 ||
44
+ !Number.isSafeInteger(checkpoint.inputPrefixLength) ||
45
+ checkpoint.inputPrefixLength < 0 ||
41
46
  checkpoint.configurationHash !== responsesContinuationIdentityHash(this.#configuration))
42
47
  return false;
43
48
  this.#cachedState.restoredContinuation = checkpoint;
@@ -656,7 +661,7 @@ class OpenAiResponsesTurnSession {
656
661
  clearResponsesContinuation(this.state);
657
662
  }
658
663
  continuationCheckpoint() {
659
- if (!responsesContinuationEnabled(this.adapter.configuration))
664
+ if (!responsesPersistedContinuationEnabled(this.adapter.configuration))
660
665
  return undefined;
661
666
  const request = this.state.lastRequest;
662
667
  const response = this.state.lastResponse;
@@ -668,8 +673,7 @@ class OpenAiResponsesTurnSession {
668
673
  protocol: 'OPENAI_RESPONSES',
669
674
  configurationHash: responsesContinuationIdentityHash(this.adapter.configuration),
670
675
  responseId: response.responseId,
671
- inputPrefixLength: baseline.length,
672
- inputPrefixHash: hashResponsesValue(baseline)
676
+ inputPrefixLength: baseline.length
673
677
  };
674
678
  }
675
679
  prepareRequest(request) {
@@ -885,10 +889,7 @@ function incrementalResponsesInput(state, current) {
885
889
  const completion = state.lastResponse;
886
890
  if (previous === undefined || completion === undefined) {
887
891
  const restored = state.restoredContinuation;
888
- if (restored === undefined ||
889
- current.input.length < restored.inputPrefixLength ||
890
- restored.inputPrefixHash !==
891
- hashResponsesValue(current.input.slice(0, restored.inputPrefixLength)))
892
+ if (restored === undefined || current.input.length < restored.inputPrefixLength)
892
893
  return undefined;
893
894
  state.lastResponse = { responseId: restored.responseId, outputItems: [] };
894
895
  return current.input.slice(restored.inputPrefixLength);
@@ -920,6 +921,9 @@ function responsesContinuationEnabled(configuration) {
920
921
  return (configuration.responsesPreviousResponseId === true &&
921
922
  configuration.responsesTransport?.transport === 'WEBSOCKET');
922
923
  }
924
+ function responsesPersistedContinuationEnabled(configuration) {
925
+ return responsesContinuationEnabled(configuration) && configuration.responsesEncoding !== 'LITE';
926
+ }
923
927
  function responsesCredentialFingerprint(credential) {
924
928
  return hashResponsesValue({
925
929
  bearer: credential.bearer.reveal(),
@@ -1460,11 +1464,17 @@ function isInvalidPreviousResponseFields(code, message) {
1460
1464
  normalizedCode === 'invalid_previous_response_id')
1461
1465
  return true;
1462
1466
  const normalizedMessage = message?.toLowerCase().replaceAll('`', '') ?? '';
1467
+ if ((normalizedCode === 'unsupported_parameter' ||
1468
+ normalizedCode === 'unsupported_previous_response_id') &&
1469
+ normalizedMessage.includes('previous_response_id'))
1470
+ return true;
1463
1471
  return (normalizedMessage.includes('previous_response_id') &&
1464
1472
  (normalizedMessage.includes('invalid') ||
1465
1473
  normalizedMessage.includes('not found') ||
1466
1474
  normalizedMessage.includes('does not exist') ||
1467
1475
  normalizedMessage.includes('expired') ||
1476
+ normalizedMessage.includes('not supported') ||
1477
+ normalizedMessage.includes('unsupported') ||
1468
1478
  normalizedMessage.includes('unavailable')));
1469
1479
  }
1470
1480
  const RESPONSE_ITEM_TYPES = new Set([
@@ -632,7 +632,9 @@ function collectExecCandidates(input, groups, existingTargets, usageRatio) {
632
632
  }
633
633
  for (const startGroup of execGroups) {
634
634
  const start = invocations.get(startGroup.callId);
635
- if (!start || start.input.action !== 'start')
635
+ if (!start ||
636
+ (start.input.action !== 'start' &&
637
+ !(start.input.action == null && typeof start.input.command === 'string')))
636
638
  continue;
637
639
  const lifecycle = [start];
638
640
  let processId;
package/dist/sdk/agent.js CHANGED
@@ -1605,8 +1605,7 @@ function isModelContinuationCheckpoint(value) {
1605
1605
  isBoundedString(checkpoint.configurationHash, 64) &&
1606
1606
  isBoundedString(checkpoint.responseId, 1_024) &&
1607
1607
  Number.isSafeInteger(checkpoint.inputPrefixLength) &&
1608
- checkpoint.inputPrefixLength >= 0 &&
1609
- isBoundedString(checkpoint.inputPrefixHash, 64));
1608
+ checkpoint.inputPrefixLength >= 0);
1610
1609
  }
1611
1610
  function isBoundedString(value, maximumLength) {
1612
1611
  return typeof value === 'string' && value.length > 0 && value.length <= maximumLength;
@@ -74,7 +74,11 @@ export function contextGcWebOutputReplacement(toolName, originalContent, truncat
74
74
  export function contextGcExecInputReplacement(arguments_, formatVersion = 3) {
75
75
  if (!isRecord(arguments_))
76
76
  return `<context_gc kind="terminal_exec_input"${formatVersion === 3 ? ' formatVersion="3"' : ''} originalBytes="${Buffer.byteLength(stableJson(arguments_))}"${formatVersion === 3 ? '' : ` originalSha256="${sha256(stableJson(arguments_))}"`} />`;
77
- const action = typeof arguments_.action === 'string' ? arguments_.action : 'unknown';
77
+ const action = typeof arguments_.action === 'string'
78
+ ? arguments_.action
79
+ : typeof arguments_.command === 'string'
80
+ ? 'start'
81
+ : 'unknown';
78
82
  const original = stableJson(arguments_);
79
83
  const retained = {
80
84
  action,
@@ -2,7 +2,7 @@ import type { ClientToolDefinition } from '../model/contracts.js';
2
2
  import { type LocalProcessExecutor } from '../host/local-process-executor.js';
3
3
  import { WorkspacePathResolver } from './shared/path-resolver.js';
4
4
  export type ExecInput = {
5
- action: 'start';
5
+ action?: 'start';
6
6
  command: string;
7
7
  cwd?: string;
8
8
  timeoutMs?: number;
@@ -5,8 +5,8 @@ import { MarAgentError } from '../error.js';
5
5
  import { TOOL_EXECUTION_LIMITS } from './execution-limits.js';
6
6
  export const execToolDefinition = {
7
7
  name: 'exec',
8
- parallelSafety: 'serial',
9
- description: 'Unrestricted general system execution. start runs any platform-native command, script, text/file tool, builder, generator, network client, or program in workspace/default cwd and may return running with processId; poll waits up to yieldMs for new output or termination and returns only output produced since the prior result; write sends stdin; cancel terminates the process tree. Returns status, processId, incremental stdout/stderr, cumulative stdoutBytes/stderrBytes, per-result dropped bytes, exitCode, and truncated; a completed status does not imply exitCode 0. Always inspect stderr/exitCode and consume terminal output or cancel.',
8
+ parallelSafety: 'safe',
9
+ description: 'Unrestricted general system execution. Providing command starts any platform-native command, script, text/file tool, builder, generator, network client, or program in workspace/default cwd; action may be omitted for start. poll waits up to yieldMs for new output or termination and returns only output produced since the prior result; write sends stdin; cancel terminates the process tree. Returns status, processId, incremental stdout/stderr, cumulative stdoutBytes/stderrBytes, per-result dropped bytes, exitCode, and truncated; a completed status does not imply exitCode 0. Always inspect stderr/exitCode and consume terminal output or cancel.',
10
10
  outputSchema: {
11
11
  type: 'object',
12
12
  required: ['content', 'data'],
@@ -42,8 +42,14 @@ export const execToolDefinition = {
42
42
  inputSchema: {
43
43
  type: 'object',
44
44
  properties: {
45
- action: { enum: ['start', 'poll', 'write', 'cancel'], description: 'Lifecycle operation.' },
46
- command: { type: 'string', description: 'Required only for start.' },
45
+ action: {
46
+ enum: ['start', 'poll', 'write', 'cancel'],
47
+ description: 'Lifecycle operation. Omit when providing command to start a process.'
48
+ },
49
+ command: {
50
+ type: 'string',
51
+ description: 'Required for start; implies start when action is omitted.'
52
+ },
47
53
  cwd: {
48
54
  type: 'string',
49
55
  description: 'Optional relative/absolute start directory; defaults to workspace.'
@@ -59,7 +65,7 @@ export const execToolDefinition = {
59
65
  processId: { type: 'string', description: 'Required for poll, write and cancel.' },
60
66
  data: { type: 'string', description: 'stdin data required for write.' }
61
67
  },
62
- required: ['action'],
68
+ required: [],
63
69
  additionalProperties: false
64
70
  }
65
71
  };
@@ -71,6 +77,7 @@ export class ExecTool {
71
77
  shell;
72
78
  processExecutor;
73
79
  #processes = new Map();
80
+ #startingProcesses = new Set();
74
81
  constructor(paths, maxOutputBytes = TOOL_EXECUTION_LIMITS.execDefaultMaxOutputBytes, maxProcesses = TOOL_EXECUTION_LIMITS.maxProcesses, environment = process.env, shell, processExecutor = directLocalProcessExecutor) {
75
82
  this.paths = paths;
76
83
  this.maxOutputBytes = maxOutputBytes;
@@ -83,7 +90,7 @@ export class ExecTool {
83
90
  if (signal?.aborted)
84
91
  throw executionAbortReason(signal);
85
92
  const input = parseExecInput(arguments_);
86
- if (input.action !== 'start') {
93
+ if ('processId' in input) {
87
94
  const process = this.#processes.get(input.processId);
88
95
  if (!process || (owner !== undefined && process.ownerSessionId !== owner.sessionId))
89
96
  throw new MarAgentError('MAR_AGENT_PROCESS_NOT_FOUND', 'Managed process was not found.');
@@ -98,38 +105,53 @@ export class ExecTool {
98
105
  this.#processes.delete(input.processId);
99
106
  return result;
100
107
  }
101
- if (this.#processes.size >= this.maxProcesses)
108
+ if (this.#processes.size + this.#startingProcesses.size >= this.maxProcesses)
102
109
  throw new MarAgentError('MAR_AGENT_PROCESS_LIMIT', 'The managed process limit was reached.');
103
- const id = randomUUID();
104
- const cwd = await this.paths.resolveWithDisplay(input.cwd ?? '.');
105
- const child = await this.processExecutor.spawn({
106
- command: input.command,
107
- cwd: cwd.physicalPath,
108
- shell: this.shell ?? true,
109
- environment: this.environment,
110
- detached: process.platform !== 'win32',
111
- ...(owner === undefined ? {} : { owner }),
112
- ...(signal === undefined ? {} : { signal })
113
- });
114
- let resolveExited;
115
- let resolveSettled;
116
- const managed = {
117
- child,
110
+ let resolveStarting;
111
+ const starting = {
118
112
  ...(owner === undefined ? {} : { ownerSessionId: owner.sessionId }),
119
- command: input.command,
120
- cwd: cwd.displayPath,
121
- stdout: createOutputBuffer(),
122
- stderr: createOutputBuffer(),
123
- status: 'running',
124
- exited: new Promise((resolve) => (resolveExited = resolve)),
125
- settled: new Promise((resolve) => (resolveSettled = resolve)),
126
- resolveExited,
127
- resolveSettled,
128
- exitObserved: child.exitCode !== null,
129
- finalized: false,
130
- waiters: new Set()
113
+ settled: new Promise((resolve) => (resolveStarting = resolve)),
114
+ resolveSettled: () => resolveStarting()
131
115
  };
132
- this.#processes.set(id, managed);
116
+ this.#startingProcesses.add(starting);
117
+ const id = randomUUID();
118
+ let child;
119
+ let managed;
120
+ try {
121
+ const cwd = await this.paths.resolveWithDisplay(input.cwd ?? '.');
122
+ child = await this.processExecutor.spawn({
123
+ command: input.command,
124
+ cwd: cwd.physicalPath,
125
+ shell: this.shell ?? true,
126
+ environment: this.environment,
127
+ detached: process.platform !== 'win32',
128
+ ...(owner === undefined ? {} : { owner }),
129
+ ...(signal === undefined ? {} : { signal })
130
+ });
131
+ let resolveExited;
132
+ let resolveSettled;
133
+ managed = {
134
+ child,
135
+ ...(owner === undefined ? {} : { ownerSessionId: owner.sessionId }),
136
+ command: input.command,
137
+ cwd: cwd.displayPath,
138
+ stdout: createOutputBuffer(),
139
+ stderr: createOutputBuffer(),
140
+ status: 'running',
141
+ exited: new Promise((resolve) => (resolveExited = resolve)),
142
+ settled: new Promise((resolve) => (resolveSettled = resolve)),
143
+ resolveExited,
144
+ resolveSettled,
145
+ exitObserved: child.exitCode !== null,
146
+ finalized: false,
147
+ waiters: new Set()
148
+ };
149
+ this.#processes.set(id, managed);
150
+ }
151
+ finally {
152
+ this.#startingProcesses.delete(starting);
153
+ starting.resolveSettled();
154
+ }
133
155
  child.stdout.on('data', (chunk) => {
134
156
  if (managed.finalized)
135
157
  return;
@@ -182,6 +204,9 @@ export class ExecTool {
182
204
  : []);
183
205
  }
184
206
  async disposeSession(sessionId) {
207
+ await Promise.all([...this.#startingProcesses]
208
+ .filter((process) => process.ownerSessionId === sessionId)
209
+ .map((process) => process.settled));
185
210
  const owned = [...this.#processes.entries()].filter(([, process]) => process.ownerSessionId === sessionId);
186
211
  await Promise.all(owned.map(([, process]) => process.status === 'running' ? this.#terminate(process, false) : process.settled));
187
212
  for (const [processId] of owned)
@@ -239,6 +264,7 @@ export class ExecTool {
239
264
  };
240
265
  }
241
266
  async dispose() {
267
+ await Promise.all([...this.#startingProcesses].map((process) => process.settled));
242
268
  await Promise.all([...this.#processes.values()].map((process) => process.status === 'running' ? this.#terminate(process, false) : process.settled));
243
269
  this.#processes.clear();
244
270
  }
@@ -332,7 +358,7 @@ function parseExecInput(value) {
332
358
  if (!value || typeof value !== 'object')
333
359
  return invalidExecInput();
334
360
  const input = value;
335
- const action = input.action;
361
+ const action = input.action == null && typeof input.command === 'string' ? 'start' : input.action;
336
362
  if (!['start', 'poll', 'write', 'cancel'].includes(String(action)))
337
363
  return invalidExecInput();
338
364
  const yieldMs = optionalBoundedInteger(input.yieldMs, 0, TOOL_EXECUTION_LIMITS.execMaxYieldMs);
@@ -343,7 +369,7 @@ function parseExecInput(value) {
343
369
  return invalidExecInput();
344
370
  const timeoutMs = optionalBoundedInteger(input.timeoutMs, 1, TOOL_EXECUTION_LIMITS.execMaxTimeoutMs);
345
371
  return {
346
- action,
372
+ ...(input.action === 'start' ? { action: 'start' } : {}),
347
373
  command: input.command,
348
374
  ...(typeof input.cwd === 'string' ? { cwd: input.cwd } : {}),
349
375
  ...(timeoutMs === undefined ? {} : { timeoutMs }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/agent",
3
- "version": "0.9.85",
3
+ "version": "0.9.86",
4
4
  "description": "Embeddable MAR coding agent SDK and CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",