@myagentroam/agent 0.9.106 → 0.9.108
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.
package/dist/tools/exec.d.ts
CHANGED
|
@@ -30,6 +30,8 @@ export interface ExecResult {
|
|
|
30
30
|
stderrBytes: number;
|
|
31
31
|
stdoutDroppedBytes: number;
|
|
32
32
|
stderrDroppedBytes: number;
|
|
33
|
+
/** A terminal snapshot replay rather than new process output. */
|
|
34
|
+
cached?: boolean;
|
|
33
35
|
}
|
|
34
36
|
export declare const execToolDefinition: ClientToolDefinition;
|
|
35
37
|
export declare class ExecTool {
|
package/dist/tools/exec.js
CHANGED
|
@@ -6,7 +6,7 @@ import { TOOL_EXECUTION_LIMITS } from './execution-limits.js';
|
|
|
6
6
|
export const execToolDefinition = {
|
|
7
7
|
name: 'exec',
|
|
8
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
|
|
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 incremental output while running; after termination, poll returns a bounded cached result with earlier output for up to five minutes while the Session remains hosted. write sends stdin to running processes; cancel terminates a running process or removes a completed result. Returns status, processId, 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'],
|
|
@@ -34,7 +34,8 @@ export const execToolDefinition = {
|
|
|
34
34
|
stdoutBytes: { type: 'number' },
|
|
35
35
|
stderrBytes: { type: 'number' },
|
|
36
36
|
stdoutDroppedBytes: { type: 'number' },
|
|
37
|
-
stderrDroppedBytes: { type: 'number' }
|
|
37
|
+
stderrDroppedBytes: { type: 'number' },
|
|
38
|
+
cached: { type: 'boolean' }
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
}
|
|
@@ -77,6 +78,7 @@ export class ExecTool {
|
|
|
77
78
|
shell;
|
|
78
79
|
processExecutor;
|
|
79
80
|
#processes = new Map();
|
|
81
|
+
#completed = new Map();
|
|
80
82
|
#startingProcesses = new Set();
|
|
81
83
|
constructor(paths, maxOutputBytes = TOOL_EXECUTION_LIMITS.execDefaultMaxOutputBytes, maxProcesses = TOOL_EXECUTION_LIMITS.maxProcesses, environment = process.env, shell, processExecutor = directLocalProcessExecutor) {
|
|
82
84
|
this.paths = paths;
|
|
@@ -92,6 +94,16 @@ export class ExecTool {
|
|
|
92
94
|
const input = parseExecInput(arguments_);
|
|
93
95
|
if ('processId' in input) {
|
|
94
96
|
const process = this.#processes.get(input.processId);
|
|
97
|
+
const completed = this.#completed.get(input.processId);
|
|
98
|
+
if (completed) {
|
|
99
|
+
if (owner !== undefined && completed.ownerSessionId !== owner.sessionId)
|
|
100
|
+
throw new MarAgentError('MAR_AGENT_PROCESS_NOT_FOUND', 'Managed process was not found.');
|
|
101
|
+
if (input.action === 'write')
|
|
102
|
+
throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Completed process cannot accept input.');
|
|
103
|
+
if (input.action === 'cancel')
|
|
104
|
+
this.#removeCompleted(input.processId);
|
|
105
|
+
return { ...completed.result, cached: true };
|
|
106
|
+
}
|
|
95
107
|
if (!process || (owner !== undefined && process.ownerSessionId !== owner.sessionId))
|
|
96
108
|
throw new MarAgentError('MAR_AGENT_PROCESS_NOT_FOUND', 'Managed process was not found.');
|
|
97
109
|
if (input.action === 'poll' && input.yieldMs)
|
|
@@ -101,8 +113,11 @@ export class ExecTool {
|
|
|
101
113
|
if (input.action === 'cancel')
|
|
102
114
|
await this.#terminate(process, true);
|
|
103
115
|
const result = this.#result(input.processId, process);
|
|
104
|
-
if (process.status !== 'running')
|
|
105
|
-
this.#
|
|
116
|
+
if (process.status !== 'running') {
|
|
117
|
+
this.#retainCompleted(input.processId, process);
|
|
118
|
+
if (input.action === 'cancel')
|
|
119
|
+
this.#removeCompleted(input.processId);
|
|
120
|
+
}
|
|
106
121
|
return result;
|
|
107
122
|
}
|
|
108
123
|
if (this.#processes.size + this.#startingProcesses.size >= this.maxProcesses)
|
|
@@ -137,6 +152,8 @@ export class ExecTool {
|
|
|
137
152
|
cwd: cwd.displayPath,
|
|
138
153
|
stdout: createOutputBuffer(),
|
|
139
154
|
stderr: createOutputBuffer(),
|
|
155
|
+
stdoutHistory: createOutputBuffer(),
|
|
156
|
+
stderrHistory: createOutputBuffer(),
|
|
140
157
|
status: 'running',
|
|
141
158
|
exited: new Promise((resolve) => (resolveExited = resolve)),
|
|
142
159
|
settled: new Promise((resolve) => (resolveSettled = resolve)),
|
|
@@ -155,53 +172,65 @@ export class ExecTool {
|
|
|
155
172
|
child.stdout.on('data', (chunk) => {
|
|
156
173
|
if (managed.finalized)
|
|
157
174
|
return;
|
|
158
|
-
|
|
175
|
+
const decoded = Buffer.from(managed.stdout.decoder.write(chunk));
|
|
176
|
+
appendOutput(managed.stdout, decoded, this.maxOutputBytes, chunk.length);
|
|
177
|
+
appendOutput(managed.stdoutHistory, decoded, this.maxOutputBytes, chunk.length);
|
|
159
178
|
notifyProcessChange(managed);
|
|
160
179
|
});
|
|
161
180
|
child.stderr.on('data', (chunk) => {
|
|
162
181
|
if (managed.finalized)
|
|
163
182
|
return;
|
|
164
|
-
|
|
183
|
+
const decoded = Buffer.from(managed.stderr.decoder.write(chunk));
|
|
184
|
+
appendOutput(managed.stderr, decoded, this.maxOutputBytes, chunk.length);
|
|
185
|
+
appendOutput(managed.stderrHistory, decoded, this.maxOutputBytes, chunk.length);
|
|
165
186
|
notifyProcessChange(managed);
|
|
166
187
|
});
|
|
167
188
|
child.once('exit', (code) => {
|
|
168
189
|
managed.exitObserved = true;
|
|
169
190
|
managed.exitCode = code;
|
|
170
191
|
managed.resolveExited();
|
|
171
|
-
void finalizeAfterOutputGrace(managed, this.maxOutputBytes);
|
|
192
|
+
void finalizeAfterOutputGrace(managed, this.maxOutputBytes).then(() => this.#retainCompleted(id, managed));
|
|
172
193
|
});
|
|
173
194
|
child.once('close', (code) => {
|
|
174
195
|
managed.exitObserved = true;
|
|
175
196
|
managed.resolveExited();
|
|
176
197
|
finalizeManagedProcess(managed, this.maxOutputBytes, code);
|
|
198
|
+
this.#retainCompleted(id, managed);
|
|
177
199
|
});
|
|
178
200
|
child.once('error', (error) => {
|
|
179
201
|
if (managed.finalized)
|
|
180
202
|
return;
|
|
181
|
-
|
|
203
|
+
const message = Buffer.from(error.message);
|
|
204
|
+
appendOutput(managed.stderr, message, this.maxOutputBytes);
|
|
205
|
+
appendOutput(managed.stderrHistory, message, this.maxOutputBytes);
|
|
182
206
|
notifyProcessChange(managed);
|
|
183
207
|
});
|
|
184
208
|
managed.timer = setTimeout(() => {
|
|
185
|
-
void this.#terminate(managed, true);
|
|
209
|
+
void this.#terminate(managed, true).then(() => this.#retainCompleted(id, managed));
|
|
186
210
|
}, Math.min(input.timeoutMs ?? TOOL_EXECUTION_LIMITS.execDefaultTimeoutMs, TOOL_EXECUTION_LIMITS.execMaxTimeoutMs));
|
|
187
211
|
managed.timer.unref();
|
|
188
212
|
await waitForExitOrDelay(managed.settled, Math.min(input.yieldMs ?? TOOL_EXECUTION_LIMITS.execDefaultYieldMs, TOOL_EXECUTION_LIMITS.execMaxYieldMs), signal);
|
|
189
213
|
const result = this.#result(id, managed);
|
|
190
214
|
if (managed.status !== 'running')
|
|
191
|
-
this.#
|
|
215
|
+
this.#retainCompleted(id, managed);
|
|
192
216
|
return result;
|
|
193
217
|
}
|
|
194
218
|
listSessionResources(sessionId) {
|
|
195
|
-
return [
|
|
196
|
-
|
|
197
|
-
{
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
219
|
+
return [
|
|
220
|
+
...[...this.#processes.entries()].flatMap(([processId, process]) => process.ownerSessionId === sessionId
|
|
221
|
+
? [{ processId, status: process.status, command: process.command, cwd: process.cwd }]
|
|
222
|
+
: []),
|
|
223
|
+
...[...this.#completed.entries()].flatMap(([processId, process]) => process.ownerSessionId === sessionId
|
|
224
|
+
? [
|
|
225
|
+
{
|
|
226
|
+
processId,
|
|
227
|
+
status: process.result.status,
|
|
228
|
+
command: process.command,
|
|
229
|
+
cwd: process.cwd
|
|
230
|
+
}
|
|
231
|
+
]
|
|
232
|
+
: [])
|
|
233
|
+
];
|
|
205
234
|
}
|
|
206
235
|
async disposeSession(sessionId) {
|
|
207
236
|
await Promise.all([...this.#startingProcesses]
|
|
@@ -211,6 +240,45 @@ export class ExecTool {
|
|
|
211
240
|
await Promise.all(owned.map(([, process]) => process.status === 'running' ? this.#terminate(process, false) : process.settled));
|
|
212
241
|
for (const [processId] of owned)
|
|
213
242
|
this.#processes.delete(processId);
|
|
243
|
+
for (const [processId, process] of this.#completed)
|
|
244
|
+
if (process.ownerSessionId === sessionId)
|
|
245
|
+
this.#removeCompleted(processId);
|
|
246
|
+
}
|
|
247
|
+
#removeCompleted(id) {
|
|
248
|
+
const completed = this.#completed.get(id);
|
|
249
|
+
if (!completed)
|
|
250
|
+
return;
|
|
251
|
+
clearTimeout(completed.timer);
|
|
252
|
+
this.#completed.delete(id);
|
|
253
|
+
}
|
|
254
|
+
#retainCompleted(id, process) {
|
|
255
|
+
if (process.status === 'running' || !process.finalized || this.#processes.get(id) !== process)
|
|
256
|
+
return;
|
|
257
|
+
this.#processes.delete(id);
|
|
258
|
+
const stdout = consumeOutput(process.stdoutHistory);
|
|
259
|
+
const stderr = consumeOutput(process.stderrHistory);
|
|
260
|
+
const timer = setTimeout(() => this.#removeCompleted(id), TOOL_EXECUTION_LIMITS.execCompletedRetentionMs);
|
|
261
|
+
timer.unref();
|
|
262
|
+
this.#completed.set(id, {
|
|
263
|
+
...(process.ownerSessionId === undefined ? {} : { ownerSessionId: process.ownerSessionId }),
|
|
264
|
+
command: process.command,
|
|
265
|
+
cwd: process.cwd,
|
|
266
|
+
timer,
|
|
267
|
+
result: {
|
|
268
|
+
status: process.status,
|
|
269
|
+
processId: id,
|
|
270
|
+
stdout: stdout.value,
|
|
271
|
+
stderr: stderr.value,
|
|
272
|
+
...(process.exitCode === undefined ? {} : { exitCode: process.exitCode }),
|
|
273
|
+
truncated: stdout.droppedBytes > 0 || stderr.droppedBytes > 0,
|
|
274
|
+
stdoutBytes: process.stdout.totalBytes,
|
|
275
|
+
stderrBytes: process.stderr.totalBytes,
|
|
276
|
+
stdoutDroppedBytes: stdout.droppedBytes,
|
|
277
|
+
stderrDroppedBytes: stderr.droppedBytes
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
while (this.#completed.size > TOOL_EXECUTION_LIMITS.execMaxCompletedResults)
|
|
281
|
+
this.#removeCompleted(this.#completed.keys().next().value);
|
|
214
282
|
}
|
|
215
283
|
async #terminate(managed, waitForOutputClose) {
|
|
216
284
|
if (managed.terminate)
|
|
@@ -252,7 +320,7 @@ export class ExecTool {
|
|
|
252
320
|
const stderr = consumeOutput(process.stderr);
|
|
253
321
|
return {
|
|
254
322
|
status: process.status,
|
|
255
|
-
|
|
323
|
+
processId: id,
|
|
256
324
|
stdout: stdout.value,
|
|
257
325
|
stderr: stderr.value,
|
|
258
326
|
...(process.exitCode === undefined ? {} : { exitCode: process.exitCode }),
|
|
@@ -267,6 +335,8 @@ export class ExecTool {
|
|
|
267
335
|
await Promise.all([...this.#startingProcesses].map((process) => process.settled));
|
|
268
336
|
await Promise.all([...this.#processes.values()].map((process) => process.status === 'running' ? this.#terminate(process, false) : process.settled));
|
|
269
337
|
this.#processes.clear();
|
|
338
|
+
for (const id of this.#completed.keys())
|
|
339
|
+
this.#removeCompleted(id);
|
|
270
340
|
}
|
|
271
341
|
}
|
|
272
342
|
function createOutputBuffer() {
|
|
@@ -283,8 +353,12 @@ function finalizeManagedProcess(managed, maximumOutputBytes, exitCode) {
|
|
|
283
353
|
if (managed.finalized)
|
|
284
354
|
return;
|
|
285
355
|
managed.finalized = true;
|
|
286
|
-
|
|
287
|
-
|
|
356
|
+
const stdoutEnd = Buffer.from(managed.stdout.decoder.end());
|
|
357
|
+
const stderrEnd = Buffer.from(managed.stderr.decoder.end());
|
|
358
|
+
appendOutput(managed.stdout, stdoutEnd, maximumOutputBytes);
|
|
359
|
+
appendOutput(managed.stderr, stderrEnd, maximumOutputBytes);
|
|
360
|
+
appendOutput(managed.stdoutHistory, stdoutEnd, maximumOutputBytes);
|
|
361
|
+
appendOutput(managed.stderrHistory, stderrEnd, maximumOutputBytes);
|
|
288
362
|
managed.status = managed.status === 'cancelled' ? 'cancelled' : 'completed';
|
|
289
363
|
if (exitCode !== undefined)
|
|
290
364
|
managed.exitCode = exitCode;
|
|
@@ -13,6 +13,8 @@ export declare const TOOL_EXECUTION_LIMITS: Readonly<{
|
|
|
13
13
|
execTerminationGraceMs: 1000;
|
|
14
14
|
execDefaultMaxOutputBytes: 1000000;
|
|
15
15
|
maxProcesses: 8;
|
|
16
|
+
execMaxCompletedResults: 16;
|
|
17
|
+
execCompletedRetentionMs: 300000;
|
|
16
18
|
codeMaxInputBytes: 1048576;
|
|
17
19
|
codeMaxOutputBytes: 1048576;
|
|
18
20
|
codeMaxFrameBytes: 33554432;
|
|
@@ -13,6 +13,8 @@ export const TOOL_EXECUTION_LIMITS = Object.freeze({
|
|
|
13
13
|
execTerminationGraceMs: 1_000,
|
|
14
14
|
execDefaultMaxOutputBytes: 1_000_000,
|
|
15
15
|
maxProcesses: 8,
|
|
16
|
+
execMaxCompletedResults: 16,
|
|
17
|
+
execCompletedRetentionMs: 300_000,
|
|
16
18
|
codeMaxInputBytes: 1_048_576,
|
|
17
19
|
codeMaxOutputBytes: 1_048_576,
|
|
18
20
|
codeMaxFrameBytes: 33_554_432,
|