@myagentroam/agent 0.9.84 → 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.
- package/dist/model/configuration.js +5 -0
- package/dist/model/contracts.d.ts +1 -3
- package/dist/model/openai-responses.d.ts +1 -2
- package/dist/model/openai-responses.js +39 -58
- package/dist/runtime/context-gc.js +3 -1
- package/dist/sdk/agent.js +2 -4
- package/dist/session/context-gc-replacements.js +5 -1
- package/dist/tools/agent-message.js +7 -2
- package/dist/tools/agent-start.js +1 -0
- package/dist/tools/agent-wait.js +3 -0
- package/dist/tools/exec.d.ts +1 -1
- package/dist/tools/exec.js +63 -37
- package/package.json +2 -1
|
@@ -80,6 +80,11 @@ const rawModelSchema = z
|
|
|
80
80
|
});
|
|
81
81
|
if (value.protocol !== 'OPENAI_RESPONSES' && value.responsesPreviousResponseId)
|
|
82
82
|
context.addIssue({ code: 'custom', message: 'Responses continuation requires Responses.' });
|
|
83
|
+
if (value.responsesPreviousResponseId && value.responsesTransport?.transport !== 'WEBSOCKET')
|
|
84
|
+
context.addIssue({
|
|
85
|
+
code: 'custom',
|
|
86
|
+
message: 'Responses continuation requires WebSocket transport.'
|
|
87
|
+
});
|
|
83
88
|
if (value.protocol !== 'OPENAI_RESPONSES' && value.responsesTransport !== undefined)
|
|
84
89
|
context.addIssue({ code: 'custom', message: 'Responses transport requires Responses.' });
|
|
85
90
|
if (value.protocol !== 'OPENAI_RESPONSES' && value.responsesEncoding !== undefined)
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import type { MarAgentModelConfiguration, MarAgentReasoningEffort, ModelCredentialAcquireReason } from './configuration.js';
|
|
2
2
|
export interface ModelContinuationCheckpoint {
|
|
3
|
-
readonly version:
|
|
3
|
+
readonly version: 2;
|
|
4
4
|
readonly protocol: 'OPENAI_RESPONSES';
|
|
5
5
|
readonly configurationHash: string;
|
|
6
6
|
readonly responseId: string;
|
|
7
|
-
readonly requestPropertiesHash: string;
|
|
8
7
|
readonly inputPrefixLength: number;
|
|
9
|
-
readonly inputPrefixHash: string;
|
|
10
8
|
}
|
|
11
9
|
export interface ClientToolDefinition {
|
|
12
10
|
name: string;
|
|
@@ -45,8 +45,7 @@ declare class OpenAiResponsesTurnSession implements ModelTurnSession {
|
|
|
45
45
|
private readonly adapter;
|
|
46
46
|
private readonly state;
|
|
47
47
|
private readonly cacheLease;
|
|
48
|
-
|
|
49
|
-
constructor(adapter: OpenAiResponsesAdapter, state: ResponsesSessionState, cacheLease: boolean, allowExternalContinuation: boolean);
|
|
48
|
+
constructor(adapter: OpenAiResponsesAdapter, state: ResponsesSessionState, cacheLease: boolean);
|
|
50
49
|
start(request: ModelRequest, signal: AbortSignal): AsyncIterable<ModelEvent>;
|
|
51
50
|
close(): Promise<void>;
|
|
52
51
|
resetContinuation(): void;
|
|
@@ -35,14 +35,21 @@ export class OpenAiResponsesAdapter {
|
|
|
35
35
|
if (this.#cacheLeased ||
|
|
36
36
|
this.#cachedState.lastRequest !== undefined ||
|
|
37
37
|
this.#cachedState.lastResponse !== undefined ||
|
|
38
|
+
!responsesPersistedContinuationEnabled(this.#configuration) ||
|
|
39
|
+
checkpoint.version !== 2 ||
|
|
38
40
|
checkpoint.protocol !== 'OPENAI_RESPONSES' ||
|
|
39
|
-
checkpoint.
|
|
41
|
+
typeof checkpoint.responseId !== 'string' ||
|
|
42
|
+
checkpoint.responseId.length === 0 ||
|
|
43
|
+
checkpoint.responseId.length > 1024 ||
|
|
44
|
+
!Number.isSafeInteger(checkpoint.inputPrefixLength) ||
|
|
45
|
+
checkpoint.inputPrefixLength < 0 ||
|
|
46
|
+
checkpoint.configurationHash !== responsesContinuationIdentityHash(this.#configuration))
|
|
40
47
|
return false;
|
|
41
48
|
this.#cachedState.restoredContinuation = checkpoint;
|
|
42
49
|
return true;
|
|
43
50
|
}
|
|
44
51
|
async *start(request, signal) {
|
|
45
|
-
const turn = new OpenAiResponsesTurnSession(this, {}, false
|
|
52
|
+
const turn = new OpenAiResponsesTurnSession(this, {}, false);
|
|
46
53
|
try {
|
|
47
54
|
yield* turn.start(request, signal);
|
|
48
55
|
}
|
|
@@ -59,7 +66,7 @@ export class OpenAiResponsesAdapter {
|
|
|
59
66
|
this.#cachedState = {};
|
|
60
67
|
this.#cacheLeased = true;
|
|
61
68
|
}
|
|
62
|
-
return new OpenAiResponsesTurnSession(this, state, cacheLease
|
|
69
|
+
return new OpenAiResponsesTurnSession(this, state, cacheLease);
|
|
63
70
|
}
|
|
64
71
|
async close() {
|
|
65
72
|
if (this.#closed)
|
|
@@ -546,17 +553,15 @@ class OpenAiResponsesTurnSession {
|
|
|
546
553
|
adapter;
|
|
547
554
|
state;
|
|
548
555
|
cacheLease;
|
|
549
|
-
allowExternalContinuation;
|
|
550
556
|
#active = false;
|
|
551
557
|
#closed = false;
|
|
552
558
|
#turnState;
|
|
553
559
|
#fullBody;
|
|
554
560
|
#connectionDiagnostic;
|
|
555
|
-
constructor(adapter, state, cacheLease
|
|
561
|
+
constructor(adapter, state, cacheLease) {
|
|
556
562
|
this.adapter = adapter;
|
|
557
563
|
this.state = state;
|
|
558
564
|
this.cacheLease = cacheLease;
|
|
559
|
-
this.allowExternalContinuation = allowExternalContinuation;
|
|
560
565
|
}
|
|
561
566
|
async *start(request, signal) {
|
|
562
567
|
if (this.#closed)
|
|
@@ -573,7 +578,7 @@ class OpenAiResponsesTurnSession {
|
|
|
573
578
|
this.#fullBody = prepared.fullBody;
|
|
574
579
|
if (this.adapter.configuration.responsesEncoding === 'LITE' &&
|
|
575
580
|
this.adapter.configuration.responsesTransport?.transport === 'WEBSOCKET' &&
|
|
576
|
-
this.adapter.configuration
|
|
581
|
+
responsesContinuationEnabled(this.adapter.configuration) &&
|
|
577
582
|
this.state.lastRequest === undefined &&
|
|
578
583
|
request.allowTools !== false &&
|
|
579
584
|
request.toolChoice !== 'none' &&
|
|
@@ -626,7 +631,7 @@ class OpenAiResponsesTurnSession {
|
|
|
626
631
|
completed = event.reason === 'completed' || event.reason === 'tool_use';
|
|
627
632
|
yield event;
|
|
628
633
|
}
|
|
629
|
-
if (this.adapter.configuration
|
|
634
|
+
if (responsesContinuationEnabled(this.adapter.configuration) &&
|
|
630
635
|
completed &&
|
|
631
636
|
responseId !== undefined &&
|
|
632
637
|
(outputItems.length > 0 || !sawUnrepresentedOutput)) {
|
|
@@ -656,19 +661,19 @@ class OpenAiResponsesTurnSession {
|
|
|
656
661
|
clearResponsesContinuation(this.state);
|
|
657
662
|
}
|
|
658
663
|
continuationCheckpoint() {
|
|
664
|
+
if (!responsesPersistedContinuationEnabled(this.adapter.configuration))
|
|
665
|
+
return undefined;
|
|
659
666
|
const request = this.state.lastRequest;
|
|
660
667
|
const response = this.state.lastResponse;
|
|
661
668
|
if (request === undefined || response === undefined)
|
|
662
669
|
return this.state.restoredContinuation;
|
|
663
670
|
const baseline = [...request.input, ...response.outputItems];
|
|
664
671
|
return {
|
|
665
|
-
version:
|
|
672
|
+
version: 2,
|
|
666
673
|
protocol: 'OPENAI_RESPONSES',
|
|
667
|
-
configurationHash:
|
|
674
|
+
configurationHash: responsesContinuationIdentityHash(this.adapter.configuration),
|
|
668
675
|
responseId: response.responseId,
|
|
669
|
-
|
|
670
|
-
inputPrefixLength: baseline.length,
|
|
671
|
-
inputPrefixHash: hashResponsesValue(baseline)
|
|
676
|
+
inputPrefixLength: baseline.length
|
|
672
677
|
};
|
|
673
678
|
}
|
|
674
679
|
prepareRequest(request) {
|
|
@@ -677,7 +682,7 @@ class OpenAiResponsesTurnSession {
|
|
|
677
682
|
this.state.socket.readyState !== WebSocket.OPEN)
|
|
678
683
|
invalidateResponsesWebSocket(this.state, false);
|
|
679
684
|
const fullBody = responsesRequestBody(this.adapter.configuration, request, request.messages, this.adapter.prefixIdentity);
|
|
680
|
-
if (this.adapter.configuration
|
|
685
|
+
if (responsesContinuationEnabled(this.adapter.configuration)) {
|
|
681
686
|
const incremental = incrementalResponsesInput(this.state, fullBody);
|
|
682
687
|
if (incremental !== undefined) {
|
|
683
688
|
const responseId = this.state.lastResponse.responseId;
|
|
@@ -692,20 +697,6 @@ class OpenAiResponsesTurnSession {
|
|
|
692
697
|
continuation: true
|
|
693
698
|
};
|
|
694
699
|
}
|
|
695
|
-
if (this.allowExternalContinuation &&
|
|
696
|
-
request.continuation !== undefined &&
|
|
697
|
-
this.adapter.configuration.responsesTransport?.transport !== 'WEBSOCKET') {
|
|
698
|
-
const deltaBody = responsesRequestBody(this.adapter.configuration, request, request.continuation.deltaMessages);
|
|
699
|
-
return {
|
|
700
|
-
fullBody,
|
|
701
|
-
body: {
|
|
702
|
-
...fullBody,
|
|
703
|
-
input: deltaBody.input,
|
|
704
|
-
previous_response_id: request.continuation.previousResponseId
|
|
705
|
-
},
|
|
706
|
-
continuation: true
|
|
707
|
-
};
|
|
708
|
-
}
|
|
709
700
|
}
|
|
710
701
|
return {
|
|
711
702
|
fullBody,
|
|
@@ -898,17 +889,11 @@ function incrementalResponsesInput(state, current) {
|
|
|
898
889
|
const completion = state.lastResponse;
|
|
899
890
|
if (previous === undefined || completion === undefined) {
|
|
900
891
|
const restored = state.restoredContinuation;
|
|
901
|
-
if (restored === undefined ||
|
|
902
|
-
restored.requestPropertiesHash !== responsesRequestPropertiesHash(current) ||
|
|
903
|
-
current.input.length < restored.inputPrefixLength ||
|
|
904
|
-
restored.inputPrefixHash !==
|
|
905
|
-
hashResponsesValue(current.input.slice(0, restored.inputPrefixLength)))
|
|
892
|
+
if (restored === undefined || current.input.length < restored.inputPrefixLength)
|
|
906
893
|
return undefined;
|
|
907
894
|
state.lastResponse = { responseId: restored.responseId, outputItems: [] };
|
|
908
895
|
return current.input.slice(restored.inputPrefixLength);
|
|
909
896
|
}
|
|
910
|
-
if (!responsesRequestPropertiesMatch(previous, current))
|
|
911
|
-
return undefined;
|
|
912
897
|
const baseline = [...previous.input, ...completion.outputItems];
|
|
913
898
|
if (current.input.length < baseline.length)
|
|
914
899
|
return undefined;
|
|
@@ -917,37 +902,27 @@ function incrementalResponsesInput(state, current) {
|
|
|
917
902
|
return undefined;
|
|
918
903
|
return current.input.slice(baseline.length);
|
|
919
904
|
}
|
|
920
|
-
function responsesRequestPropertiesMatch(previous, current) {
|
|
921
|
-
return RESPONSES_REQUEST_PROPERTY_KEYS.every((key) => isDeepStrictEqual(previous[key], current[key]));
|
|
922
|
-
}
|
|
923
905
|
function clearResponsesContinuation(state) {
|
|
924
906
|
delete state.lastRequest;
|
|
925
907
|
delete state.lastResponse;
|
|
926
908
|
delete state.restoredContinuation;
|
|
927
909
|
}
|
|
928
|
-
const RESPONSES_REQUEST_PROPERTY_KEYS = [
|
|
929
|
-
'model',
|
|
930
|
-
'instructions',
|
|
931
|
-
'tools',
|
|
932
|
-
'tool_choice',
|
|
933
|
-
'parallel_tool_calls',
|
|
934
|
-
'reasoning',
|
|
935
|
-
'store',
|
|
936
|
-
'stream',
|
|
937
|
-
'include',
|
|
938
|
-
'prompt_cache_key',
|
|
939
|
-
'text',
|
|
940
|
-
'client_metadata'
|
|
941
|
-
];
|
|
942
|
-
function responsesRequestPropertiesHash(request) {
|
|
943
|
-
return hashResponsesValue(Object.fromEntries(RESPONSES_REQUEST_PROPERTY_KEYS.map((key) => [key, request[key]])));
|
|
944
|
-
}
|
|
945
910
|
function hashResponsesValue(value) {
|
|
946
911
|
return createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
|
947
912
|
}
|
|
948
|
-
function
|
|
949
|
-
|
|
950
|
-
|
|
913
|
+
function responsesContinuationIdentityHash(configuration) {
|
|
914
|
+
return hashResponsesValue({
|
|
915
|
+
endpoint: new URL(responsesEndpoint(configuration.baseUrl)).toString(),
|
|
916
|
+
modelId: configuration.modelId,
|
|
917
|
+
responsesEncoding: configuration.responsesEncoding ?? 'STANDARD'
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
function responsesContinuationEnabled(configuration) {
|
|
921
|
+
return (configuration.responsesPreviousResponseId === true &&
|
|
922
|
+
configuration.responsesTransport?.transport === 'WEBSOCKET');
|
|
923
|
+
}
|
|
924
|
+
function responsesPersistedContinuationEnabled(configuration) {
|
|
925
|
+
return responsesContinuationEnabled(configuration) && configuration.responsesEncoding !== 'LITE';
|
|
951
926
|
}
|
|
952
927
|
function responsesCredentialFingerprint(credential) {
|
|
953
928
|
return hashResponsesValue({
|
|
@@ -1489,11 +1464,17 @@ function isInvalidPreviousResponseFields(code, message) {
|
|
|
1489
1464
|
normalizedCode === 'invalid_previous_response_id')
|
|
1490
1465
|
return true;
|
|
1491
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;
|
|
1492
1471
|
return (normalizedMessage.includes('previous_response_id') &&
|
|
1493
1472
|
(normalizedMessage.includes('invalid') ||
|
|
1494
1473
|
normalizedMessage.includes('not found') ||
|
|
1495
1474
|
normalizedMessage.includes('does not exist') ||
|
|
1496
1475
|
normalizedMessage.includes('expired') ||
|
|
1476
|
+
normalizedMessage.includes('not supported') ||
|
|
1477
|
+
normalizedMessage.includes('unsupported') ||
|
|
1497
1478
|
normalizedMessage.includes('unavailable')));
|
|
1498
1479
|
}
|
|
1499
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 ||
|
|
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
|
@@ -1600,14 +1600,12 @@ function isModelContinuationCheckpoint(value) {
|
|
|
1600
1600
|
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
1601
1601
|
return false;
|
|
1602
1602
|
const checkpoint = value;
|
|
1603
|
-
return (checkpoint.version ===
|
|
1603
|
+
return (checkpoint.version === 2 &&
|
|
1604
1604
|
checkpoint.protocol === 'OPENAI_RESPONSES' &&
|
|
1605
1605
|
isBoundedString(checkpoint.configurationHash, 64) &&
|
|
1606
1606
|
isBoundedString(checkpoint.responseId, 1_024) &&
|
|
1607
|
-
isBoundedString(checkpoint.requestPropertiesHash, 64) &&
|
|
1608
1607
|
Number.isSafeInteger(checkpoint.inputPrefixLength) &&
|
|
1609
|
-
checkpoint.inputPrefixLength >= 0
|
|
1610
|
-
isBoundedString(checkpoint.inputPrefixHash, 64));
|
|
1608
|
+
checkpoint.inputPrefixLength >= 0);
|
|
1611
1609
|
}
|
|
1612
1610
|
function isBoundedString(value, maximumLength) {
|
|
1613
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'
|
|
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,
|
|
@@ -33,7 +33,10 @@ function parseAgentMessageInput(value) {
|
|
|
33
33
|
return invalid();
|
|
34
34
|
const input = value;
|
|
35
35
|
if (Object.keys(input).some((key) => !['agentId', 'message', 'delivery'].includes(key)) ||
|
|
36
|
-
(input.delivery !== undefined &&
|
|
36
|
+
(input.delivery !== undefined &&
|
|
37
|
+
input.delivery !== null &&
|
|
38
|
+
input.delivery !== 'append' &&
|
|
39
|
+
input.delivery !== 'replace') ||
|
|
37
40
|
typeof input.agentId !== 'string' ||
|
|
38
41
|
input.agentId.trim().length === 0 ||
|
|
39
42
|
typeof input.message !== 'string' ||
|
|
@@ -43,7 +46,9 @@ function parseAgentMessageInput(value) {
|
|
|
43
46
|
return {
|
|
44
47
|
agentId: input.agentId.trim(),
|
|
45
48
|
message: input.message,
|
|
46
|
-
...(input.delivery ===
|
|
49
|
+
...(input.delivery === 'append' || input.delivery === 'replace'
|
|
50
|
+
? { delivery: input.delivery }
|
|
51
|
+
: {})
|
|
47
52
|
};
|
|
48
53
|
}
|
|
49
54
|
function invalid() {
|
|
@@ -62,6 +62,7 @@ function parseAgentStartInput(value) {
|
|
|
62
62
|
input.modelId !== null &&
|
|
63
63
|
(typeof input.modelId !== 'string' || input.modelId.trim().length === 0)) ||
|
|
64
64
|
(input.reasoningEffort !== undefined &&
|
|
65
|
+
input.reasoningEffort !== null &&
|
|
65
66
|
(typeof input.reasoningEffort !== 'string' ||
|
|
66
67
|
!marAgentReasoningEffortSchema.safeParse(input.reasoningEffort.trim()).success)) ||
|
|
67
68
|
(input.background !== undefined &&
|
package/dist/tools/agent-wait.js
CHANGED
|
@@ -46,11 +46,14 @@ function parseAgentWaitInput(value) {
|
|
|
46
46
|
const input = value;
|
|
47
47
|
if (Object.keys(input).some((key) => !['agentId', 'waitFor', 'waitMs'].includes(key)) ||
|
|
48
48
|
(input.agentId !== undefined &&
|
|
49
|
+
input.agentId !== null &&
|
|
49
50
|
(typeof input.agentId !== 'string' || input.agentId.trim().length === 0)) ||
|
|
50
51
|
(input.waitFor !== undefined &&
|
|
52
|
+
input.waitFor !== null &&
|
|
51
53
|
input.waitFor !== 'completion' &&
|
|
52
54
|
input.waitFor !== 'message') ||
|
|
53
55
|
(input.waitMs !== undefined &&
|
|
56
|
+
input.waitMs !== null &&
|
|
54
57
|
(!Number.isInteger(input.waitMs) ||
|
|
55
58
|
input.waitMs < TOOL_EXECUTION_LIMITS.agentWaitMinWaitMs ||
|
|
56
59
|
input.waitMs > TOOL_EXECUTION_LIMITS.agentWaitMaxWaitMs)))
|
package/dist/tools/exec.d.ts
CHANGED
|
@@ -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
|
|
5
|
+
action?: 'start';
|
|
6
6
|
command: string;
|
|
7
7
|
cwd?: string;
|
|
8
8
|
timeoutMs?: number;
|
package/dist/tools/exec.js
CHANGED
|
@@ -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: '
|
|
9
|
-
description: 'Unrestricted general system execution.
|
|
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: {
|
|
46
|
-
|
|
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: [
|
|
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 (
|
|
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
|
-
|
|
104
|
-
const
|
|
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
|
-
|
|
120
|
-
|
|
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.#
|
|
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.
|
|
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",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"build": "node ../scripts/clean-build-output.mjs dist && tsc -p tsconfig.json",
|
|
43
43
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
44
44
|
"test:unit": "vitest run --config vitest.config.ts test/unit",
|
|
45
|
+
"analyze:session": "node test/tools/session-analyzer.mjs",
|
|
45
46
|
"benchmark:compaction": "node test/e2e/compaction-benchmark.mjs",
|
|
46
47
|
"test:performance": "vitest run --config vitest.performance.config.ts",
|
|
47
48
|
"test:memory": "node --expose-gc ./node_modules/vitest/vitest.mjs run --config vitest.memory.config.ts",
|