@myagentroam/node 0.13.2 → 0.13.4
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/connection/direct-transfer-service.js +4 -1
- package/dist/hosting/hosted-conversation-node-service.js +6 -1
- package/dist/hosting/hosted-conversation-runs.js +3 -0
- package/dist/main.js +23 -2
- package/dist/marctl/package.json +1 -1
- package/dist/runner/mar-agent/execution-input.js +3 -1
- package/dist/runner/mar-agent/hosted-conversation-runtime.js +3 -0
- package/dist/runner/mar-agent/managed-run-controller.js +3 -0
- package/dist/runner/mar-agent/web-tools-extensions.js +3 -1
- package/dist/service/agent-network-invoke.js +12 -3
- package/dist/service/node-upgrade-coordinator.js +2 -0
- package/dist/service/supervised-node-child.js +48 -3
- package/dist/supervisor.js +82 -9
- package/dist/terminal.js +27 -2
- package/package.json +11 -6
- package/scripts/install-web-browser.mjs +6 -9
|
@@ -26,7 +26,10 @@ export class DirectTransferService {
|
|
|
26
26
|
disposed = false;
|
|
27
27
|
static async create(options) {
|
|
28
28
|
const range = options.udpPortRange?.() ?? { begin: 49_152, end: 49_215 };
|
|
29
|
-
const createIceUdpMuxListener = options.createIceUdpMuxListener ??
|
|
29
|
+
const createIceUdpMuxListener = options.createIceUdpMuxListener ??
|
|
30
|
+
((port) => new NativeIceUdpMuxListener(port, process.platform === 'win32' && process.env['MAR_QUICK_START_OWNER'] === '1'
|
|
31
|
+
? '0.0.0.0'
|
|
32
|
+
: undefined));
|
|
30
33
|
// Windows can accept an IPv6 mux bind while another IPv4 endpoint owns the port.
|
|
31
34
|
const probe = options.probeUdp4Port ?? (process.platform === 'win32' ? probeUdp4Port : undefined);
|
|
32
35
|
const listener = await bindIceUdpMuxListener(range, createIceUdpMuxListener, probe);
|
|
@@ -874,13 +874,15 @@ export class HostedConversationNodeService {
|
|
|
874
874
|
function hostedRequest(raw) {
|
|
875
875
|
if (!record(raw))
|
|
876
876
|
throw new Error('HOSTED_CONVERSATION_NOT_AUTHORIZED');
|
|
877
|
-
const { __usageGrant, __requestUserId, __template, __marAgentModel, __marAgentModels, __marAgentCompact, __marAgentRuntimeScopeId, __workspaceWatchSessionHash, __templateDeleted, __sourceTemplateId, ...data } = raw;
|
|
877
|
+
const { __usageGrant, __requestUserId, __template, __marAgentModel, __marAgentModels, __marAgentCompact, __marAgentRuntimeScopeId, __searchBackendAvailable, __workspaceWatchSessionHash, __templateDeleted, __sourceTemplateId, ...data } = raw;
|
|
878
878
|
if (typeof __requestUserId !== 'number' ||
|
|
879
879
|
!Number.isSafeInteger(__requestUserId) ||
|
|
880
880
|
__requestUserId <= 0)
|
|
881
881
|
throw new Error('HOSTED_CONVERSATION_NOT_AUTHORIZED');
|
|
882
882
|
if (__templateDeleted !== undefined && __templateDeleted !== true)
|
|
883
883
|
throw new Error('HOSTED_CONVERSATION_NOT_AUTHORIZED');
|
|
884
|
+
if (__searchBackendAvailable !== undefined && typeof __searchBackendAvailable !== 'boolean')
|
|
885
|
+
throw new Error('HOSTED_CONVERSATION_NOT_AUTHORIZED');
|
|
884
886
|
if (__sourceTemplateId !== undefined &&
|
|
885
887
|
(typeof __sourceTemplateId !== 'string' || __sourceTemplateId.length === 0))
|
|
886
888
|
throw new Error('HOSTED_CONVERSATION_NOT_AUTHORIZED');
|
|
@@ -889,6 +891,9 @@ function hostedRequest(raw) {
|
|
|
889
891
|
throw new Error('HOSTED_CONVERSATION_NOT_AUTHORIZED');
|
|
890
892
|
return {
|
|
891
893
|
data,
|
|
894
|
+
...(__searchBackendAvailable === undefined
|
|
895
|
+
? {}
|
|
896
|
+
: { searchBackendAvailable: __searchBackendAvailable }),
|
|
892
897
|
...(__sourceTemplateId === undefined ? {} : { sourceTemplateId: __sourceTemplateId }),
|
|
893
898
|
...(__usageGrant === undefined
|
|
894
899
|
? {}
|
|
@@ -140,6 +140,9 @@ export class HostedConversationRuns {
|
|
|
140
140
|
access: 'full-access',
|
|
141
141
|
modelId: selected.id,
|
|
142
142
|
models: networkModels(allowedModels(version, models), version.draft.networkMode),
|
|
143
|
+
...(payload.searchBackendAvailable === undefined
|
|
144
|
+
? {}
|
|
145
|
+
: { searchBackendAvailable: payload.searchBackendAvailable }),
|
|
143
146
|
...(payload.compact === undefined ? {} : { compact: payload.compact }),
|
|
144
147
|
...(payload.runtimeScopeId === undefined ? {} : { runtimeScopeId: payload.runtimeScopeId }),
|
|
145
148
|
systemInstruction: systemInstruction(version, generatedImagesLogicalPath),
|
package/dist/main.js
CHANGED
|
@@ -18,6 +18,23 @@ const browserPath = readOption(args, '--browser-cache');
|
|
|
18
18
|
if (browserPath !== undefined)
|
|
19
19
|
process.env['PLAYWRIGHT_BROWSERS_PATH'] = browserPath;
|
|
20
20
|
if (command === 'run') {
|
|
21
|
+
let earlyQuickStop = false;
|
|
22
|
+
let quickStop;
|
|
23
|
+
process.on('message', (message) => {
|
|
24
|
+
if (typeof message !== 'object' ||
|
|
25
|
+
message === null ||
|
|
26
|
+
!('type' in message) ||
|
|
27
|
+
message.type !== 'node-quick-start-stop')
|
|
28
|
+
return;
|
|
29
|
+
earlyQuickStop = true;
|
|
30
|
+
quickStop?.();
|
|
31
|
+
});
|
|
32
|
+
if (process.env['MAR_QUICK_START_OWNER'] === '1') {
|
|
33
|
+
process.once('disconnect', () => {
|
|
34
|
+
earlyQuickStop = true;
|
|
35
|
+
quickStop?.();
|
|
36
|
+
});
|
|
37
|
+
}
|
|
21
38
|
const nodeConfig = await loadNodeConfig(config);
|
|
22
39
|
let releaseOwner;
|
|
23
40
|
try {
|
|
@@ -59,7 +76,7 @@ if (command === 'run') {
|
|
|
59
76
|
});
|
|
60
77
|
};
|
|
61
78
|
const supervisorHandoff = new SupervisorUpgradeHandoff();
|
|
62
|
-
const supportsProgress = await supervisorHandoff.handshake();
|
|
79
|
+
const supportsProgress = process.env['MAR_QUICK_START_OWNER'] !== '1' && (await supervisorHandoff.handshake());
|
|
63
80
|
if (!supportsProgress)
|
|
64
81
|
supervisorHandoff.close();
|
|
65
82
|
connector = new NodeConnector({
|
|
@@ -71,8 +88,12 @@ if (command === 'run') {
|
|
|
71
88
|
for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
72
89
|
process.on(signal, () => shutdown());
|
|
73
90
|
}
|
|
91
|
+
quickStop = () => shutdown();
|
|
74
92
|
try {
|
|
75
|
-
|
|
93
|
+
if (earlyQuickStop)
|
|
94
|
+
shutdown();
|
|
95
|
+
else
|
|
96
|
+
await connector.start();
|
|
76
97
|
}
|
|
77
98
|
catch (error) {
|
|
78
99
|
nodeLog('node.start.failed', { code: safeErrorCode(error, 'NODE_START_FAILED') });
|
package/dist/marctl/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"type":"module","version":"0.13.
|
|
1
|
+
{"type":"module","version":"0.13.4"}
|
|
@@ -4,7 +4,8 @@ export function captureMarAgentExecutionInput(input) {
|
|
|
4
4
|
model: input.marAgentModel,
|
|
5
5
|
models: input.marAgentModels,
|
|
6
6
|
compact: input.marAgentCompact,
|
|
7
|
-
runtimeScopeId: input.marAgentRuntimeScopeId
|
|
7
|
+
runtimeScopeId: input.marAgentRuntimeScopeId,
|
|
8
|
+
searchBackendAvailable: input.searchBackendAvailable
|
|
8
9
|
});
|
|
9
10
|
}
|
|
10
11
|
/** Only the MAR boundary understands either the wire input or its captured form. */
|
|
@@ -24,6 +25,7 @@ export function resolveMarAgentExecutionInput(input, modelId) {
|
|
|
24
25
|
model: parsed.data,
|
|
25
26
|
models: resolveRunModels(parsed.data, input?.models),
|
|
26
27
|
compact: input?.compact,
|
|
28
|
+
searchBackendAvailable: input?.searchBackendAvailable !== false,
|
|
27
29
|
...(typeof input?.runtimeScopeId === 'string' ? { runtimeScopeId: input.runtimeScopeId } : {})
|
|
28
30
|
};
|
|
29
31
|
}
|
|
@@ -346,6 +346,9 @@ export class MarAgentHostedConversationRuntime {
|
|
|
346
346
|
const webAccess = 'webAccess' in input && input.webAccess;
|
|
347
347
|
const extensions = combineExecutionExtensions(imageExtensions, createWebToolsExtensions({
|
|
348
348
|
enabled: webAccess,
|
|
349
|
+
...('searchBackendAvailable' in input && input.searchBackendAvailable !== undefined
|
|
350
|
+
? { searchBackendAvailable: input.searchBackendAvailable }
|
|
351
|
+
: {}),
|
|
349
352
|
hostedSearchModelIds: models
|
|
350
353
|
.filter((model) => model.hostedWebSearch)
|
|
351
354
|
.map((model) => model.id),
|
|
@@ -323,6 +323,9 @@ export class MarAgentManagedRunController {
|
|
|
323
323
|
});
|
|
324
324
|
const extensions = combineExecutionExtensions(imageExtensions, createWebToolsExtensions({
|
|
325
325
|
enabled: true,
|
|
326
|
+
...(input.searchBackendAvailable === undefined
|
|
327
|
+
? {}
|
|
328
|
+
: { searchBackendAvailable: input.searchBackendAvailable }),
|
|
326
329
|
hostedSearchModelIds: executionModels
|
|
327
330
|
.filter((model) => model.hostedWebSearch)
|
|
328
331
|
.map((model) => model.id),
|
|
@@ -39,7 +39,9 @@ export const webFetchToolDefinition = {
|
|
|
39
39
|
export function createWebToolsExtensions(input) {
|
|
40
40
|
const tools = input.enabled
|
|
41
41
|
? [
|
|
42
|
-
...(
|
|
42
|
+
...(input.searchBackendAvailable !== false &&
|
|
43
|
+
(input.hostedSearchModelIds !== undefined || !input.hostedWebSearch) &&
|
|
44
|
+
input.webSearch
|
|
43
45
|
? [webSearchToolDefinition]
|
|
44
46
|
: []),
|
|
45
47
|
...(input.webFetch ? [webFetchToolDefinition] : [])
|
|
@@ -47,7 +47,10 @@ export function executionAgentActor(input) {
|
|
|
47
47
|
marAgentModel: captured.model,
|
|
48
48
|
marAgentModels: captured.models,
|
|
49
49
|
marAgentCompact: captured.compact,
|
|
50
|
-
marAgentRuntimeScopeId: captured.runtimeScopeId
|
|
50
|
+
marAgentRuntimeScopeId: captured.runtimeScopeId,
|
|
51
|
+
...(typeof captured.searchBackendAvailable === 'boolean'
|
|
52
|
+
? { searchBackendAvailable: captured.searchBackendAvailable }
|
|
53
|
+
: {})
|
|
51
54
|
}
|
|
52
55
|
};
|
|
53
56
|
}
|
|
@@ -120,7 +123,10 @@ export function agentNetworkExecutionCommand(input) {
|
|
|
120
123
|
marAgentModel: data.marAgentModel,
|
|
121
124
|
marAgentModels: data.marAgentModels,
|
|
122
125
|
marAgentCompact: data.marAgentCompact,
|
|
123
|
-
marAgentRuntimeScopeId: data.marAgentRuntimeScopeId
|
|
126
|
+
marAgentRuntimeScopeId: data.marAgentRuntimeScopeId,
|
|
127
|
+
...(typeof data.searchBackendAvailable === 'boolean'
|
|
128
|
+
? { searchBackendAvailable: data.searchBackendAvailable }
|
|
129
|
+
: {})
|
|
124
130
|
},
|
|
125
131
|
...(usageGrant.success ? { usageGrant: usageGrant.data } : {}),
|
|
126
132
|
...(typeof data.personalInstructions === 'string'
|
|
@@ -159,7 +165,10 @@ export function agentNetworkExecutionCommand(input) {
|
|
|
159
165
|
marAgentModel: resources.marAgentModel,
|
|
160
166
|
marAgentModels: resources.marAgentModels,
|
|
161
167
|
marAgentCompact: resources.marAgentCompact,
|
|
162
|
-
marAgentRuntimeScopeId: resources.marAgentRuntimeScopeId
|
|
168
|
+
marAgentRuntimeScopeId: resources.marAgentRuntimeScopeId,
|
|
169
|
+
...(typeof resources.searchBackendAvailable === 'boolean'
|
|
170
|
+
? { searchBackendAvailable: resources.searchBackendAvailable }
|
|
171
|
+
: {})
|
|
163
172
|
}
|
|
164
173
|
};
|
|
165
174
|
return invokeAgentNetwork(input.service, boundActor, data.action, data.input, undefined, data.__requestAbortSignal instanceof AbortSignal ? data.__requestAbortSignal : undefined);
|
|
@@ -37,6 +37,8 @@ export class NodeUpgradeCoordinator {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
request(data) {
|
|
40
|
+
if (process.env['MAR_QUICK_START_OWNER'] === '1')
|
|
41
|
+
throw new Error('NODE_UPGRADE_UNAVAILABLE');
|
|
40
42
|
const input = record(data);
|
|
41
43
|
const targetVersion = input.targetVersion;
|
|
42
44
|
if (typeof targetVersion !== 'string' || !STABLE_VERSION.test(targetVersion))
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { terminateProcessTree } from '../process-tree.js';
|
|
3
3
|
/** Wait for the child process itself, not inherited output handles held by descendants. */
|
|
4
|
-
export function runSupervisedNodeChild(entrypoint, configPath, accept, accepted, upgradeAttemptId) {
|
|
4
|
+
export function runSupervisedNodeChild(entrypoint, configPath, accept, accepted, upgradeAttemptId, stopSignal) {
|
|
5
5
|
return new Promise((resolve) => {
|
|
6
6
|
const child = spawn(process.execPath, [entrypoint, 'run', '--config', configPath], {
|
|
7
7
|
windowsHide: true,
|
|
8
8
|
stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
|
|
9
9
|
env: {
|
|
10
10
|
...process.env,
|
|
11
|
-
MAR_NODE_SUPERVISED: '1',
|
|
11
|
+
MAR_NODE_SUPERVISED: process.env['MAR_QUICK_START_OWNER'] === '1' ? '0' : '1',
|
|
12
12
|
...(upgradeAttemptId === undefined ? {} : { MAR_NODE_UPGRADE_ATTEMPT_ID: upgradeAttemptId })
|
|
13
13
|
},
|
|
14
14
|
detached: process.platform !== 'win32'
|
|
15
15
|
});
|
|
16
|
+
if (process.env['MAR_QUICK_START_OWNER'] === '1' &&
|
|
17
|
+
child.pid !== undefined &&
|
|
18
|
+
process.connected)
|
|
19
|
+
process.send?.({ type: 'node-quick-start-child', pid: child.pid }, () => { });
|
|
16
20
|
let done = false;
|
|
17
21
|
let signalled = false;
|
|
18
22
|
let channelId;
|
|
@@ -34,6 +38,7 @@ export function runSupervisedNodeChild(entrypoint, configPath, accept, accepted,
|
|
|
34
38
|
clearTimeout(reapDeadline);
|
|
35
39
|
process.off('SIGINT', onSigint);
|
|
36
40
|
process.off('SIGTERM', onSigterm);
|
|
41
|
+
stopSignal?.removeEventListener('abort', onQuickStop);
|
|
37
42
|
if (child.connected)
|
|
38
43
|
child.disconnect();
|
|
39
44
|
if (reclaimFailed)
|
|
@@ -54,21 +59,61 @@ export function runSupervisedNodeChild(entrypoint, configPath, accept, accepted,
|
|
|
54
59
|
};
|
|
55
60
|
const onSigint = () => forward('SIGINT');
|
|
56
61
|
const onSigterm = () => forward('SIGTERM');
|
|
62
|
+
const onQuickStop = () => {
|
|
63
|
+
if (done || signalled)
|
|
64
|
+
return;
|
|
65
|
+
signalled = true;
|
|
66
|
+
if (deadline !== undefined)
|
|
67
|
+
clearTimeout(deadline);
|
|
68
|
+
send(child, { type: 'node-quick-start-stop' });
|
|
69
|
+
deadline = setTimeout(() => {
|
|
70
|
+
forced = true;
|
|
71
|
+
reclaiming = true;
|
|
72
|
+
reapDeadline = setTimeout(() => finish(null, true), 10_000);
|
|
73
|
+
void terminateProcessTree(child)
|
|
74
|
+
.then(() => {
|
|
75
|
+
reclaimed = true;
|
|
76
|
+
if (exited !== undefined)
|
|
77
|
+
finish(exited.code);
|
|
78
|
+
})
|
|
79
|
+
.catch(() => finish(null, true));
|
|
80
|
+
if (process.platform !== 'win32' && child.pid !== undefined) {
|
|
81
|
+
try {
|
|
82
|
+
process.kill(-child.pid, 'SIGKILL');
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
child.kill('SIGKILL');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}, 30_000);
|
|
89
|
+
};
|
|
57
90
|
process.on('SIGINT', onSigint);
|
|
58
91
|
process.on('SIGTERM', onSigterm);
|
|
92
|
+
stopSignal?.addEventListener('abort', onQuickStop, { once: true });
|
|
93
|
+
if (stopSignal?.aborted)
|
|
94
|
+
onQuickStop();
|
|
59
95
|
child.on('error', () => {
|
|
60
96
|
if (child.pid === undefined)
|
|
61
97
|
finish(null);
|
|
62
98
|
});
|
|
63
99
|
child.once('exit', (code) => {
|
|
100
|
+
if (process.env['MAR_QUICK_START_OWNER'] === '1' &&
|
|
101
|
+
child.pid !== undefined &&
|
|
102
|
+
process.connected)
|
|
103
|
+
process.send?.({ type: 'node-quick-start-child-exited', pid: child.pid }, () => { });
|
|
64
104
|
exited = { code };
|
|
65
105
|
if (!reclaiming || reclaimed)
|
|
66
106
|
finish(code);
|
|
67
107
|
});
|
|
68
108
|
child.on('message', (message) => {
|
|
69
|
-
if (done ||
|
|
109
|
+
if (done || typeof message !== 'object' || message === null)
|
|
70
110
|
return;
|
|
71
111
|
const value = message;
|
|
112
|
+
if (signalled) {
|
|
113
|
+
if (stopSignal?.aborted && value['type'] === 'node-upgrade-hello')
|
|
114
|
+
send(child, { type: 'node-quick-start-stop' });
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
72
117
|
if (value['type'] === 'node-upgrade-hello' &&
|
|
73
118
|
channelId === undefined &&
|
|
74
119
|
typeof value['channelId'] === 'string') {
|
package/dist/supervisor.js
CHANGED
|
@@ -12,16 +12,50 @@ import { terminateProcessTree } from './process-tree.js';
|
|
|
12
12
|
const UPGRADE_EXIT_CODE = 75;
|
|
13
13
|
const STABLE_VERSION = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/u;
|
|
14
14
|
export async function superviseNode(configPath, bootstrapEntrypoint) {
|
|
15
|
+
const stop = new AbortController();
|
|
16
|
+
let ownerDisconnected = false;
|
|
17
|
+
const onMessage = (message) => {
|
|
18
|
+
if (typeof message === 'object' &&
|
|
19
|
+
message !== null &&
|
|
20
|
+
'type' in message &&
|
|
21
|
+
message.type === 'node-quick-start-stop')
|
|
22
|
+
stop.abort();
|
|
23
|
+
};
|
|
24
|
+
process.on('message', onMessage);
|
|
25
|
+
const onOwnerDisconnect = () => {
|
|
26
|
+
ownerDisconnected = true;
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
stop.abort();
|
|
29
|
+
};
|
|
30
|
+
if (process.env['MAR_QUICK_START_OWNER'] === '1')
|
|
31
|
+
process.once('disconnect', onOwnerDisconnect);
|
|
32
|
+
try {
|
|
33
|
+
await superviseNodeUntilStopped(configPath, bootstrapEntrypoint, stop.signal);
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
if (ownerDisconnected)
|
|
37
|
+
process.exitCode = 1;
|
|
38
|
+
process.off('message', onMessage);
|
|
39
|
+
process.off('disconnect', onOwnerDisconnect);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function superviseNodeUntilStopped(configPath, bootstrapEntrypoint, stopSignal) {
|
|
15
43
|
const config = await loadNodeConfig(configPath);
|
|
16
44
|
const dataDirectory = dirname(nodeDatabasePath(config, configPath));
|
|
17
45
|
const activePath = resolve(dataDirectory, 'node-active-entrypoint');
|
|
18
|
-
let entrypoint =
|
|
46
|
+
let entrypoint = process.env['MAR_QUICK_START_OWNER'] === '1'
|
|
47
|
+
? bootstrapEntrypoint
|
|
48
|
+
: await readActiveEntrypoint(activePath, bootstrapEntrypoint);
|
|
19
49
|
let reporter;
|
|
20
50
|
let awaitingOnline;
|
|
21
51
|
for (;;) {
|
|
52
|
+
if (stopSignal.aborted)
|
|
53
|
+
return;
|
|
22
54
|
const requestPath = nodeUpgradeRequestPath(nodeDatabasePath(config, configPath));
|
|
23
55
|
let reportingIdentity;
|
|
24
56
|
const childResult = await runSupervisedNodeChild(entrypoint, configPath, async (handoff) => {
|
|
57
|
+
if (process.env['MAR_QUICK_START_OWNER'] === '1')
|
|
58
|
+
return false;
|
|
25
59
|
const currentConfig = await loadNodeConfig(configPath);
|
|
26
60
|
if (currentConfig.nodeId === undefined ||
|
|
27
61
|
!STABLE_VERSION.test(handoff.targetVersion) ||
|
|
@@ -42,12 +76,15 @@ export async function superviseNode(configPath, bootstrapEntrypoint) {
|
|
|
42
76
|
reporter?.stop();
|
|
43
77
|
reporter = next;
|
|
44
78
|
next.update({ attemptId: handoff.attemptId, step: 'CLOSING_NODE' });
|
|
45
|
-
}, awaitingOnline);
|
|
79
|
+
}, awaitingOnline, stopSignal);
|
|
46
80
|
if (childResult.signalled) {
|
|
47
81
|
reporter?.stop();
|
|
48
|
-
process.exitCode =
|
|
82
|
+
process.exitCode =
|
|
83
|
+
childResult.forced || childResult.reclaimFailed ? 1 : (childResult.code ?? 1);
|
|
49
84
|
return;
|
|
50
85
|
}
|
|
86
|
+
if (stopSignal.aborted)
|
|
87
|
+
return;
|
|
51
88
|
if (childResult.reclaimFailed) {
|
|
52
89
|
if (childResult.handoff !== undefined)
|
|
53
90
|
await reporter?.finish({
|
|
@@ -90,6 +127,11 @@ export async function superviseNode(configPath, bootstrapEntrypoint) {
|
|
|
90
127
|
process.exitCode = code;
|
|
91
128
|
return;
|
|
92
129
|
}
|
|
130
|
+
if (process.env['MAR_QUICK_START_OWNER'] === '1' &&
|
|
131
|
+
(code === UPGRADE_EXIT_CODE || childResult.handoff !== undefined)) {
|
|
132
|
+
process.exitCode = 1;
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
93
135
|
if (code !== UPGRADE_EXIT_CODE && childResult.handoff === undefined) {
|
|
94
136
|
if (code === 0)
|
|
95
137
|
return;
|
|
@@ -110,7 +152,7 @@ export async function superviseNode(configPath, bootstrapEntrypoint) {
|
|
|
110
152
|
!STABLE_VERSION.test(targetVersion))
|
|
111
153
|
throw new Error('NODE_UPGRADE_REQUEST_INVALID');
|
|
112
154
|
const prefix = resolve(dataDirectory, 'node-versions', targetVersion);
|
|
113
|
-
const installDeadline = Date.now() +
|
|
155
|
+
const installDeadline = Date.now() + 20 * 60_000;
|
|
114
156
|
if (childResult.handoff !== undefined)
|
|
115
157
|
reporter?.update({ attemptId: childResult.handoff.attemptId, step: 'INSTALLING' });
|
|
116
158
|
failureStage = 'PREPARE_INSTALL_DIRECTORY';
|
|
@@ -126,7 +168,7 @@ export async function superviseNode(configPath, bootstrapEntrypoint) {
|
|
|
126
168
|
`@myagentroam/node@${targetVersion}`
|
|
127
169
|
]);
|
|
128
170
|
failureStage = 'NPM_INSTALL';
|
|
129
|
-
const installResult = await runProcess(invocation.command, invocation.args, false, installDeadline);
|
|
171
|
+
const installResult = await runProcess(invocation.command, invocation.args, false, installDeadline, stopSignal);
|
|
130
172
|
failureExitCode = installResult.code;
|
|
131
173
|
if (installResult.reclaimFailed) {
|
|
132
174
|
installReclaimFailed = true;
|
|
@@ -144,7 +186,7 @@ export async function superviseNode(configPath, bootstrapEntrypoint) {
|
|
|
144
186
|
const rebuild = platformCliInvocation('npm', ['rebuild', '--prefix', prefix, 'node-pty']);
|
|
145
187
|
failureStage = 'NATIVE_REBUILD';
|
|
146
188
|
failureExitCode = undefined;
|
|
147
|
-
const rebuildResult = await runProcess(rebuild.command, rebuild.args, false, installDeadline);
|
|
189
|
+
const rebuildResult = await runProcess(rebuild.command, rebuild.args, false, installDeadline, stopSignal);
|
|
148
190
|
failureExitCode = rebuildResult.code;
|
|
149
191
|
if (rebuildResult.reclaimFailed) {
|
|
150
192
|
installReclaimFailed = true;
|
|
@@ -163,7 +205,7 @@ export async function superviseNode(configPath, bootstrapEntrypoint) {
|
|
|
163
205
|
failureExitCode = undefined;
|
|
164
206
|
const browserInstall = await runProcess(process.execPath, [
|
|
165
207
|
resolve(prefix, 'node_modules', '@myagentroam', 'node', 'scripts', 'install-web-browser.mjs')
|
|
166
|
-
], true, installDeadline);
|
|
208
|
+
], true, installDeadline, stopSignal);
|
|
167
209
|
failureExitCode = browserInstall.code;
|
|
168
210
|
if (browserInstall.reclaimFailed) {
|
|
169
211
|
installReclaimFailed = true;
|
|
@@ -243,11 +285,16 @@ export async function superviseNode(configPath, bootstrapEntrypoint) {
|
|
|
243
285
|
code: 'NODE_UPGRADE_REQUEST_CLEANUP_FAILED'
|
|
244
286
|
});
|
|
245
287
|
}
|
|
246
|
-
if (stopping)
|
|
288
|
+
if (stopping) {
|
|
289
|
+
if (stopSignal.aborted)
|
|
290
|
+
process.exitCode = 1;
|
|
247
291
|
return;
|
|
292
|
+
}
|
|
248
293
|
}
|
|
249
294
|
}
|
|
250
|
-
async function runProcess(command, args, inherit, deadlineAt) {
|
|
295
|
+
async function runProcess(command, args, inherit, deadlineAt, stopSignal) {
|
|
296
|
+
if (stopSignal.aborted)
|
|
297
|
+
return { code: null, signalled: true, timedOut: false, reclaimFailed: false };
|
|
251
298
|
return new Promise((resolveProcess) => {
|
|
252
299
|
const child = spawn(command, args, {
|
|
253
300
|
windowsHide: true,
|
|
@@ -293,6 +340,28 @@ async function runProcess(command, args, inherit, deadlineAt) {
|
|
|
293
340
|
};
|
|
294
341
|
const onSigint = () => forward('SIGINT');
|
|
295
342
|
const onSigterm = () => forward('SIGTERM');
|
|
343
|
+
const onQuickStop = () => {
|
|
344
|
+
if (signalled || settled)
|
|
345
|
+
return;
|
|
346
|
+
signalled = true;
|
|
347
|
+
timedOut = true;
|
|
348
|
+
reapDeadline = setTimeout(() => finish(null, true), 10_000);
|
|
349
|
+
void terminateProcessTree(child)
|
|
350
|
+
.then(() => {
|
|
351
|
+
reclaimed = true;
|
|
352
|
+
if (exited !== undefined)
|
|
353
|
+
finish(exited.code);
|
|
354
|
+
})
|
|
355
|
+
.catch(() => finish(null, true));
|
|
356
|
+
if (process.platform !== 'win32' && child.pid !== undefined) {
|
|
357
|
+
try {
|
|
358
|
+
process.kill(-child.pid, 'SIGKILL');
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
child.kill('SIGKILL');
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
};
|
|
296
365
|
const finish = (code, reclaimFailed = false) => {
|
|
297
366
|
if (settled)
|
|
298
367
|
return;
|
|
@@ -302,12 +371,16 @@ async function runProcess(command, args, inherit, deadlineAt) {
|
|
|
302
371
|
clearTimeout(reapDeadline);
|
|
303
372
|
process.off('SIGINT', onSigint);
|
|
304
373
|
process.off('SIGTERM', onSigterm);
|
|
374
|
+
stopSignal.removeEventListener('abort', onQuickStop);
|
|
305
375
|
if (reclaimFailed)
|
|
306
376
|
child.unref();
|
|
307
377
|
resolveProcess({ code, signalled, timedOut, reclaimFailed });
|
|
308
378
|
};
|
|
309
379
|
process.on('SIGINT', onSigint);
|
|
310
380
|
process.on('SIGTERM', onSigterm);
|
|
381
|
+
stopSignal.addEventListener('abort', onQuickStop, { once: true });
|
|
382
|
+
if (stopSignal.aborted)
|
|
383
|
+
onQuickStop();
|
|
311
384
|
child.on('error', () => {
|
|
312
385
|
if (child.pid === undefined)
|
|
313
386
|
finish(null);
|
package/dist/terminal.js
CHANGED
|
@@ -6,9 +6,11 @@ import { homedir } from 'node:os';
|
|
|
6
6
|
import { dirname, join } from 'node:path';
|
|
7
7
|
import { terminalDataFrameSchema } from '@myagentroam/protocol';
|
|
8
8
|
import { nodeExecutionEnvironment } from './node-execution-environment.js';
|
|
9
|
+
import { nodeLog } from './operational.js';
|
|
9
10
|
import { terminateProcessTree } from './process-tree.js';
|
|
10
11
|
import { TerminalPtyProcess } from './terminal-pty.js';
|
|
11
12
|
import { TerminalPtyPool } from './terminal-pty-pool.js';
|
|
13
|
+
import { safeErrorCode } from './util/safe-error.js';
|
|
12
14
|
const MAX_FRAME_CHARACTERS = 65_536;
|
|
13
15
|
const TERMINAL_COMMAND_MAX_BYTES = 64 * 1024;
|
|
14
16
|
const DEFAULT_MAX_TERMINALS = 10;
|
|
@@ -78,7 +80,19 @@ export class TerminalManager {
|
|
|
78
80
|
this.pipeFactory = options.pipeFactory ?? nodePipeFactory;
|
|
79
81
|
this.terminate =
|
|
80
82
|
options.terminate ?? ((child, platform) => terminateProcessTree(child, platform));
|
|
81
|
-
this.idleTimer = setInterval(() =>
|
|
83
|
+
this.idleTimer = setInterval(() => {
|
|
84
|
+
void this.closeExpired().catch((error) => {
|
|
85
|
+
try {
|
|
86
|
+
nodeLog('terminal.cleanup.failed', {
|
|
87
|
+
source: 'idle',
|
|
88
|
+
errorCode: safeErrorCode(error, 'TERMINAL_CLEANUP_FAILED')
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Logging must not turn a handled background failure into another rejection.
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}, Math.min(this.idleTimeoutMs, 60_000));
|
|
82
96
|
this.idleTimer.unref();
|
|
83
97
|
}
|
|
84
98
|
availableProfiles() {
|
|
@@ -673,7 +687,18 @@ export class TerminalManager {
|
|
|
673
687
|
.sort((left, right) => left.lastUsedAt - right.lastUsedAt || left.createdAt - right.createdAt)[0];
|
|
674
688
|
if (candidate === undefined)
|
|
675
689
|
throw new Error('TERMINAL_LIMIT_REACHED');
|
|
676
|
-
void this.close(candidate.id)
|
|
690
|
+
void this.close(candidate.id).catch((error) => {
|
|
691
|
+
try {
|
|
692
|
+
nodeLog('terminal.cleanup.failed', {
|
|
693
|
+
source: 'capacity',
|
|
694
|
+
terminalId: candidate.id,
|
|
695
|
+
errorCode: safeErrorCode(error, 'TERMINAL_CLEANUP_FAILED')
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
catch {
|
|
699
|
+
// Logging must not turn a handled background failure into another rejection.
|
|
700
|
+
}
|
|
701
|
+
});
|
|
677
702
|
}
|
|
678
703
|
runningCountForWorkspace(workspaceId) {
|
|
679
704
|
return [...this.terminals.values()].filter((terminal) => terminal.state === 'RUNNING' && terminal.workspaceId === workspaceId).length;
|
package/package.json
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myagentroam/node",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.4",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "git+https://github.com/myagentroam/mar.git",
|
|
7
|
+
"directory": "mar-node"
|
|
8
|
+
},
|
|
4
9
|
"description": "MyAgentRoam Node runtime CLI.",
|
|
5
10
|
"license": "Apache-2.0 AND GPL-2.0-or-later",
|
|
6
11
|
"type": "module",
|
|
@@ -32,7 +37,7 @@
|
|
|
32
37
|
"dependencies": {
|
|
33
38
|
"@anthropic-ai/claude-agent-sdk": "0.3.233",
|
|
34
39
|
"@modelcontextprotocol/sdk": "1.32.0",
|
|
35
|
-
"@myagentroam/binaries": "0.10.
|
|
40
|
+
"@myagentroam/binaries": "0.10.2",
|
|
36
41
|
"@opencode-ai/sdk": "1.18.20",
|
|
37
42
|
"minimatch": "^10.2.6",
|
|
38
43
|
"node-datachannel": "0.33.4",
|
|
@@ -44,14 +49,14 @@
|
|
|
44
49
|
"turndown-plugin-gfm": "^1.0.2",
|
|
45
50
|
"ws": "^8.21.3",
|
|
46
51
|
"zod": "4.4.3",
|
|
47
|
-
"@myagentroam/agent": "0.13.
|
|
48
|
-
"@myagentroam/protocol": "0.13.
|
|
52
|
+
"@myagentroam/agent": "0.13.4",
|
|
53
|
+
"@myagentroam/protocol": "0.13.4"
|
|
49
54
|
},
|
|
50
55
|
"devDependencies": {
|
|
51
56
|
"@types/turndown": "^5.0.6",
|
|
52
57
|
"@types/ws": "^8.18.1",
|
|
53
|
-
"@myagentroam/
|
|
54
|
-
"@myagentroam/
|
|
58
|
+
"@myagentroam/marctl": "0.13.4",
|
|
59
|
+
"@myagentroam/code-navigation": "0.11.1"
|
|
55
60
|
},
|
|
56
61
|
"exports": {
|
|
57
62
|
"./config": {
|
|
@@ -25,15 +25,12 @@ export async function installWebBrowser(options = {}) {
|
|
|
25
25
|
}
|
|
26
26
|
const require = createRequire(import.meta.url);
|
|
27
27
|
const cli = resolve(dirname(require.resolve('playwright/package.json')), 'cli.js');
|
|
28
|
-
const run =
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
timeout: 300_000
|
|
35
|
-
}));
|
|
36
|
-
const result = run(process.execPath, [cli, 'install', 'chromium', '--no-shell']);
|
|
28
|
+
const run = options.run ?? spawnSync;
|
|
29
|
+
const result = run(process.execPath, [cli, 'install', 'chromium', '--no-shell'], {
|
|
30
|
+
stdio: 'inherit',
|
|
31
|
+
env: environment,
|
|
32
|
+
timeout: 12 * 60_000
|
|
33
|
+
});
|
|
37
34
|
if (result.error || result.signal || result.status !== 0)
|
|
38
35
|
throw new Error(
|
|
39
36
|
'NODE_WEB_BROWSER_INSTALL_FAILED: Chromium download failed. Check network access or preinstall the matching Playwright Chromium runtime.'
|