@myagentroam/node 0.9.139 → 0.9.140
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/connector.js +2 -1
- package/dist/{marmgr.js → marctl.js} +1 -1
- package/dist/marnodes.js +1 -1
- package/dist/marscp.js +1 -1
- package/dist/marssh.js +1 -1
- package/dist/runner/abstract-runner.js +7 -7
- package/dist/runner/claude/managed-run-controller.js +1 -1
- package/dist/runner/codex/managed-run-controller.js +2 -2
- package/dist/runner/mar-agent/conversation-normalizer.js +11 -4
- package/dist/runner/mar-agent/managed-run-controller.js +15 -4
- package/dist/runner/opencode/managed-run-controller.js +2 -2
- package/dist/runtime-state.js +21 -0
- package/dist/service/run-event-service.js +1 -1
- package/dist/service/terminal-service.js +1 -1
- package/dist/service/workspace-file-service.js +4 -10
- package/dist/workspace.js +10 -11
- package/package.json +5 -5
package/dist/connector.js
CHANGED
|
@@ -316,7 +316,7 @@ export class NodeConnector {
|
|
|
316
316
|
waitMs
|
|
317
317
|
})
|
|
318
318
|
});
|
|
319
|
-
workspaceFileService = new WorkspaceFileService(this.workspaceState, (
|
|
319
|
+
workspaceFileService = new WorkspaceFileService(this.workspaceState, (workspaceId) => {
|
|
320
320
|
this.workspaceWorkbenchService?.invalidateWorkspaceFiles(workspaceId);
|
|
321
321
|
this.workspaceCoordinator.invalidate(workspaceId);
|
|
322
322
|
});
|
|
@@ -462,6 +462,7 @@ export class NodeConnector {
|
|
|
462
462
|
sequence: this.workbenchEventService.snapshotSequence(),
|
|
463
463
|
session: this.sessionPresentationService.present(session),
|
|
464
464
|
runs: this.runtime.listRunsForSession(session.id),
|
|
465
|
+
performance: this.runtime.sessionPerformance(session.id),
|
|
465
466
|
queue: this.workspaceQueueWorkbenchService.snapshot(session.id),
|
|
466
467
|
context,
|
|
467
468
|
conversation: this.conversationSegmentService.projectInitial(session, page, limit)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Keep the CLI implementation in its own package; the Node install exposes the same entrypoint.
|
|
3
|
-
const entrypoint = '@myagentroam/
|
|
3
|
+
const entrypoint = '@myagentroam/marctl/bin/marctl.mjs';
|
|
4
4
|
await import(entrypoint);
|
|
5
5
|
export {};
|
package/dist/marnodes.js
CHANGED
package/dist/marscp.js
CHANGED
package/dist/marssh.js
CHANGED
|
@@ -172,17 +172,17 @@ export class AbstractRunner {
|
|
|
172
172
|
function resolvedMcpServers(raw, secrets) {
|
|
173
173
|
if (!Array.isArray(raw))
|
|
174
174
|
return [];
|
|
175
|
-
const
|
|
175
|
+
const marctlEnvironment = {
|
|
176
176
|
MAR_USERNAME: secrets['MAR_USERNAME'] ?? '',
|
|
177
|
-
...(secrets['
|
|
177
|
+
...(secrets['MAR_CTL_SERVER_URL'] === undefined
|
|
178
178
|
? {}
|
|
179
|
-
: {
|
|
180
|
-
...(secrets['
|
|
179
|
+
: { MAR_CTL_SERVER_URL: secrets['MAR_CTL_SERVER_URL'] }),
|
|
180
|
+
...(secrets['MAR_CTL_USER_TOKEN'] === undefined
|
|
181
181
|
? {}
|
|
182
|
-
: {
|
|
183
|
-
|
|
182
|
+
: { MAR_CTL_USER_TOKEN: secrets['MAR_CTL_USER_TOKEN'] }),
|
|
183
|
+
MAR_CTL_CONTEXT_REQUIRED: '1'
|
|
184
184
|
};
|
|
185
|
-
const mcpEnvironment = (provided) => nodeExecutionEnvironment({ ...provided, ...
|
|
185
|
+
const mcpEnvironment = (provided) => nodeExecutionEnvironment({ ...provided, ...marctlEnvironment }, { PATH: process.env['PATH'], Path: process.env['Path'] });
|
|
186
186
|
const result = [];
|
|
187
187
|
for (const value of raw) {
|
|
188
188
|
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
@@ -173,7 +173,7 @@ export class ClaudeManagedRunController {
|
|
|
173
173
|
...(typeof payload.access === 'string' ? { access: payload.access } : {}),
|
|
174
174
|
environment: nodeExecutionEnvironment({
|
|
175
175
|
...secretEnvironment,
|
|
176
|
-
|
|
176
|
+
MAR_CTL_CONTEXT_REQUIRED: '1'
|
|
177
177
|
}, { PATH: process.env['PATH'], Path: process.env['Path'] }),
|
|
178
178
|
...(typeof payload.personalInstructions === 'string'
|
|
179
179
|
? { personalInstructions: payload.personalInstructions }
|
|
@@ -66,7 +66,7 @@ export class CodexManagedRunController {
|
|
|
66
66
|
throw new Error('RUNNER_COMMAND_UNAVAILABLE');
|
|
67
67
|
if (command.id === 'compact') {
|
|
68
68
|
const runId = crypto.randomUUID();
|
|
69
|
-
this.runner.prepareEnvironment(nodeExecutionEnvironment({ ...context.secretEnvironment,
|
|
69
|
+
this.runner.prepareEnvironment(nodeExecutionEnvironment({ ...context.secretEnvironment, MAR_CTL_CONTEXT_REQUIRED: '1' }, { PATH: process.env['PATH'], Path: process.env['Path'] }), runId);
|
|
70
70
|
try {
|
|
71
71
|
await this.runner.start(runId);
|
|
72
72
|
return await this.compact(session, threadId, runId, context.initiatedByUserId);
|
|
@@ -386,7 +386,7 @@ export class CodexManagedRunController {
|
|
|
386
386
|
return;
|
|
387
387
|
}
|
|
388
388
|
try {
|
|
389
|
-
this.runner.prepareEnvironment(nodeExecutionEnvironment({ ...secretEnvironment,
|
|
389
|
+
this.runner.prepareEnvironment(nodeExecutionEnvironment({ ...secretEnvironment, MAR_CTL_CONTEXT_REQUIRED: '1' }, { PATH: process.env['PATH'], Path: process.env['Path'] }), runId);
|
|
390
390
|
}
|
|
391
391
|
catch (error) {
|
|
392
392
|
this.host.emit(runId, 'run.rejected', { code: safeErrorCode(error, 'SECRET_ENVIRONMENT_INVALID') }, 'FAILED');
|
|
@@ -141,9 +141,14 @@ export class MarAgentConversationNormalizer {
|
|
|
141
141
|
}
|
|
142
142
|
#toolStarted(event) {
|
|
143
143
|
const ownItemId = itemId('tool', event.callId);
|
|
144
|
-
if (event.toolName === 'exec') {
|
|
144
|
+
if (event.toolName === 'exec' || event.toolName === 'exec_wait') {
|
|
145
145
|
const input = record(event.input) ?? {};
|
|
146
|
-
const action = input.action
|
|
146
|
+
const action = input.action ??
|
|
147
|
+
(event.toolName === 'exec_wait'
|
|
148
|
+
? 'poll'
|
|
149
|
+
: typeof input.command === 'string'
|
|
150
|
+
? 'start'
|
|
151
|
+
: undefined);
|
|
147
152
|
if (action === 'start' && typeof input.command === 'string') {
|
|
148
153
|
const command = {
|
|
149
154
|
itemId: ownItemId,
|
|
@@ -227,7 +232,8 @@ export class MarAgentConversationNormalizer {
|
|
|
227
232
|
}
|
|
228
233
|
#toolCompleted(event) {
|
|
229
234
|
const state = this.#tools.get(event.callId);
|
|
230
|
-
if (event.toolName === 'exec'
|
|
235
|
+
if ((event.toolName === 'exec' || event.toolName === 'exec_wait') &&
|
|
236
|
+
state?.commandOrigin !== undefined) {
|
|
231
237
|
const command = this.#commands.get(state.commandOrigin);
|
|
232
238
|
const result = execResult(event.data, event.summary, event.truncated === true);
|
|
233
239
|
if (command !== undefined && result !== undefined) {
|
|
@@ -293,7 +299,8 @@ export class MarAgentConversationNormalizer {
|
|
|
293
299
|
}
|
|
294
300
|
#toolFailed(event) {
|
|
295
301
|
const state = this.#tools.get(event.callId);
|
|
296
|
-
if (event.toolName === 'exec'
|
|
302
|
+
if ((event.toolName === 'exec' || event.toolName === 'exec_wait') &&
|
|
303
|
+
state?.commandOrigin !== undefined) {
|
|
297
304
|
const command = this.#commands.get(state.commandOrigin);
|
|
298
305
|
const input = record(state.input);
|
|
299
306
|
if (command !== undefined &&
|
|
@@ -273,7 +273,9 @@ export class MarAgentManagedRunController {
|
|
|
273
273
|
throw new Error('MAR_AGENT_SESSION_RUNTIME_ACTIVE');
|
|
274
274
|
runtime.activeRunId = input.runId;
|
|
275
275
|
await runtime.mcpLease.pool.refreshForExecution();
|
|
276
|
-
const executionMcpServers = input.mcpServers
|
|
276
|
+
const executionMcpServers = input.mcpServers
|
|
277
|
+
.filter(isExecutionScopedMcp)
|
|
278
|
+
.map((server) => ({ ...server, lifecycle: 'EXECUTION' }));
|
|
277
279
|
computerUseBinding = await this.#computerUse?.bind({
|
|
278
280
|
runId: input.runId,
|
|
279
281
|
sessionId: runtime.session.id,
|
|
@@ -311,6 +313,9 @@ export class MarAgentManagedRunController {
|
|
|
311
313
|
});
|
|
312
314
|
executionMcpBinding = runtime.host.bindExecutionExtensions({
|
|
313
315
|
...imageExtensions,
|
|
316
|
+
environment: input.secretEnvironment?.['MAR_CTL_USER_TOKEN'] === undefined
|
|
317
|
+
? {}
|
|
318
|
+
: { MAR_CTL_USER_TOKEN: input.secretEnvironment['MAR_CTL_USER_TOKEN'] },
|
|
314
319
|
skills: [...(imageExtensions.skills ?? []), computerUseHostSkill]
|
|
315
320
|
}, {
|
|
316
321
|
tools: [...nodeTools, ...userTools],
|
|
@@ -500,7 +505,7 @@ export class MarAgentManagedRunController {
|
|
|
500
505
|
async #createRuntime(input, sessionKey, fingerprint, hostConfiguration) {
|
|
501
506
|
const dynamicCredentials = input.models.some((model) => model.credential?.type === 'RUNTIME_TICKET' ||
|
|
502
507
|
model.imageGeneration?.credential?.type === 'RUNTIME_TICKET');
|
|
503
|
-
const sharedMcpServers = input.mcpServers.filter((server) => server
|
|
508
|
+
const sharedMcpServers = input.mcpServers.filter((server) => !isExecutionScopedMcp(server));
|
|
504
509
|
const mcpLease = await this.#mcpClients.acquire(sharedMcpServers, input.cwd, {
|
|
505
510
|
// Native Session IDs are assigned after the first lease; do not let that promotion
|
|
506
511
|
// change a confirmed actor's discovery identity when its warm runtime is recreated.
|
|
@@ -706,6 +711,10 @@ function contextUsageChanged(event) {
|
|
|
706
711
|
(event.type === 'usage.updated' &&
|
|
707
712
|
(event.contextInputTokens !== undefined || event.inputTokens !== undefined)));
|
|
708
713
|
}
|
|
714
|
+
function isExecutionScopedMcp(server) {
|
|
715
|
+
return (server.lifecycle === 'EXECUTION' ||
|
|
716
|
+
(server.transport === 'STDIO' && server.environment?.['MAR_CTL_USER_TOKEN'] !== undefined));
|
|
717
|
+
}
|
|
709
718
|
function sessionRuntimeFingerprint(input, hostConfiguration) {
|
|
710
719
|
return createHash('sha256')
|
|
711
720
|
.update(JSON.stringify({
|
|
@@ -713,7 +722,7 @@ function sessionRuntimeFingerprint(input, hostConfiguration) {
|
|
|
713
722
|
defaultModelId: input.model.id,
|
|
714
723
|
models: input.models.map(runtimeModelFingerprint),
|
|
715
724
|
personalInstructions: input.personalInstructions ?? '',
|
|
716
|
-
mcpServers: input.mcpServers.filter((server) => server
|
|
725
|
+
mcpServers: input.mcpServers.filter((server) => !isExecutionScopedMcp(server)),
|
|
717
726
|
environment: Object.entries(hostConfiguration.environment)
|
|
718
727
|
.filter((entry) => entry[1] !== undefined)
|
|
719
728
|
.sort(([left], [right]) => left.localeCompare(right)),
|
|
@@ -727,8 +736,10 @@ export function allowsUntrustedWebFetchTls(environment) {
|
|
|
727
736
|
async function resolveMarAgentHostRuntimeConfiguration(secretEnvironment = {}) {
|
|
728
737
|
const environment = nodeExecutionEnvironment({
|
|
729
738
|
...secretEnvironment,
|
|
730
|
-
|
|
739
|
+
MAR_CTL_CONTEXT_REQUIRED: '1'
|
|
731
740
|
});
|
|
741
|
+
// CLI credentials are bound to each execution, never retained as Host configuration.
|
|
742
|
+
delete environment['MAR_CTL_USER_TOKEN'];
|
|
732
743
|
const ripgrep = await availableBundledRipgrep();
|
|
733
744
|
return {
|
|
734
745
|
environment: ripgrep === undefined ? environment : prependBundledRipgrepToPath(environment, ripgrep),
|
|
@@ -50,7 +50,7 @@ export class OpenCodeManagedRunController {
|
|
|
50
50
|
this.host.hasActiveSessionLease(session.id))
|
|
51
51
|
throw new Error('RUNNER_COMMAND_UNAVAILABLE');
|
|
52
52
|
const runId = crypto.randomUUID();
|
|
53
|
-
const client = this.runner.managedClient(nodeExecutionEnvironment({ ...environment,
|
|
53
|
+
const client = this.runner.managedClient(nodeExecutionEnvironment({ ...environment, MAR_CTL_CONTEXT_REQUIRED: '1' }, { PATH: process.env['PATH'], Path: process.env['Path'] }));
|
|
54
54
|
this.host.adopt({
|
|
55
55
|
runId,
|
|
56
56
|
sessionId: session.id,
|
|
@@ -111,7 +111,7 @@ export class OpenCodeManagedRunController {
|
|
|
111
111
|
try {
|
|
112
112
|
environment = nodeExecutionEnvironment({
|
|
113
113
|
...parseSecretEnvironment(payload.secretEnvironment),
|
|
114
|
-
|
|
114
|
+
MAR_CTL_CONTEXT_REQUIRED: '1'
|
|
115
115
|
}, { PATH: process.env['PATH'], Path: process.env['Path'] });
|
|
116
116
|
mcpSecretEnvironment = parseSecretEnvironment(payload.mcpSecretEnvironment);
|
|
117
117
|
mcp = this.runner.mcpConfiguration(payload.mcpInstallations, {
|
package/dist/runtime-state.js
CHANGED
|
@@ -13,6 +13,7 @@ export class NodeRuntimeState {
|
|
|
13
13
|
static MAX_COMPLETED_TURNS_PER_SESSION = 10;
|
|
14
14
|
sessions = new Map();
|
|
15
15
|
runs = new Map();
|
|
16
|
+
runPerformance = new Map();
|
|
16
17
|
recentUserMessages = new Map();
|
|
17
18
|
messageSubmissions = new Map();
|
|
18
19
|
failedMessageSubmissions = new Map();
|
|
@@ -553,6 +554,21 @@ export class NodeRuntimeState {
|
|
|
553
554
|
const canonical = this.executions.get(id)?.runId ?? id;
|
|
554
555
|
return this.runs.get(canonical);
|
|
555
556
|
}
|
|
557
|
+
/** Only the current execution may replace an active Run's ephemeral metrics. */
|
|
558
|
+
updateRunPerformance(id, performance) {
|
|
559
|
+
const run = this.getRun(id);
|
|
560
|
+
if (run === undefined || terminal(run.status) || performance.runId !== run.id)
|
|
561
|
+
return false;
|
|
562
|
+
if (id !== run.id && this.currentExecutionId(run.id) !== id)
|
|
563
|
+
return false;
|
|
564
|
+
this.runPerformance.set(run.id, { ...performance });
|
|
565
|
+
return true;
|
|
566
|
+
}
|
|
567
|
+
sessionPerformance(sessionId) {
|
|
568
|
+
const run = this.activeSessionRun(sessionId);
|
|
569
|
+
const performance = run === undefined ? undefined : this.runPerformance.get(run.id);
|
|
570
|
+
return performance === undefined ? undefined : { ...performance };
|
|
571
|
+
}
|
|
556
572
|
findMessageRun(sessionId, clientMessageId) {
|
|
557
573
|
const submission = this.messageSubmissions.get(`${sessionId}\u0000${clientMessageId}`);
|
|
558
574
|
return submission?.kind === 'run' ? this.runs.get(submission.id) : undefined;
|
|
@@ -599,6 +615,8 @@ export class NodeRuntimeState {
|
|
|
599
615
|
updatedAt: now
|
|
600
616
|
};
|
|
601
617
|
this.runs.set(runId, value);
|
|
618
|
+
if (terminal(next))
|
|
619
|
+
this.runPerformance.delete(runId);
|
|
602
620
|
this.emitRunChanged(value, this.requireSession(value.sessionId));
|
|
603
621
|
if (terminal(next) && this.leases.get(value.sessionId) === value.id)
|
|
604
622
|
this.leases.delete(value.sessionId);
|
|
@@ -894,6 +912,7 @@ export class NodeRuntimeState {
|
|
|
894
912
|
}
|
|
895
913
|
this.pruneRetainedExecutionTurns(run.sessionId, retainedTurnIds);
|
|
896
914
|
this.runs.delete(runId);
|
|
915
|
+
this.runPerformance.delete(runId);
|
|
897
916
|
this.recentUserMessages.delete(runId);
|
|
898
917
|
this.runSequences.delete(runId);
|
|
899
918
|
for (const [key, candidate] of this.messageSubmissions)
|
|
@@ -931,6 +950,7 @@ export class NodeRuntimeState {
|
|
|
931
950
|
this.leases.delete(sessionId);
|
|
932
951
|
for (const runId of runIds) {
|
|
933
952
|
this.runs.delete(runId);
|
|
953
|
+
this.runPerformance.delete(runId);
|
|
934
954
|
this.recentUserMessages.delete(runId);
|
|
935
955
|
this.runSequences.delete(runId);
|
|
936
956
|
}
|
|
@@ -994,6 +1014,7 @@ export class NodeRuntimeState {
|
|
|
994
1014
|
}
|
|
995
1015
|
for (const runId of runIds) {
|
|
996
1016
|
this.runs.delete(runId);
|
|
1017
|
+
this.runPerformance.delete(runId);
|
|
997
1018
|
this.recentUserMessages.delete(runId);
|
|
998
1019
|
this.runSequences.delete(runId);
|
|
999
1020
|
}
|
|
@@ -15,7 +15,7 @@ export class RunEventService {
|
|
|
15
15
|
if (run === undefined || !isPlainRecord(payload))
|
|
16
16
|
return;
|
|
17
17
|
const parsed = runPerformanceSchema.safeParse({ ...payload, runId: run.id });
|
|
18
|
-
if (parsed.success) {
|
|
18
|
+
if (parsed.success && this.options.runtime.updateRunPerformance(runId, parsed.data)) {
|
|
19
19
|
this.flushText(runId);
|
|
20
20
|
this.options.emitWorkbench('conversation', {
|
|
21
21
|
sessionId: run.sessionId,
|
|
@@ -61,7 +61,7 @@ export class TerminalService {
|
|
|
61
61
|
const options = {
|
|
62
62
|
...input,
|
|
63
63
|
workspaceId: typeof input.workspaceId === 'string' ? input.workspaceId : null,
|
|
64
|
-
environment: { ...secretEnvironment,
|
|
64
|
+
environment: { ...secretEnvironment, MAR_CTL_CONTEXT_REQUIRED: '1' },
|
|
65
65
|
...(cwd === undefined ? {} : { cwd })
|
|
66
66
|
};
|
|
67
67
|
const terminal = commandMode
|
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
import { tmpdir } from 'node:os';
|
|
2
1
|
import { isAbsolute, relative, resolve, win32 } from 'node:path';
|
|
3
2
|
import { realpath } from 'node:fs/promises';
|
|
4
|
-
import { listWorkspaceFiles, mutateWorkspaceFile, normalizeFilePreviewPath,
|
|
3
|
+
import { listWorkspaceFiles, mutateWorkspaceFile, normalizeFilePreviewPath, readAbsoluteFileContentRange, readWorkspaceFileContentRange, readAbsoluteTextFile, readWorkspaceTextFile, writeWorkspaceTextFile, WorkspaceFileIndex } from '../workspace.js';
|
|
5
4
|
const INDEX_CACHE_LIMIT = 32;
|
|
6
5
|
export class WorkspaceFileService {
|
|
7
6
|
state;
|
|
8
|
-
allowedRoots;
|
|
9
7
|
onInvalidated;
|
|
10
8
|
mutationTails = new Map();
|
|
11
|
-
constructor(state,
|
|
9
|
+
constructor(state, onInvalidated) {
|
|
12
10
|
this.state = state;
|
|
13
|
-
this.allowedRoots = allowedRoots;
|
|
14
11
|
this.onInvalidated = onInvalidated;
|
|
15
12
|
}
|
|
16
13
|
async list(workspace, input) {
|
|
@@ -38,7 +35,7 @@ export class WorkspaceFileService {
|
|
|
38
35
|
if (workspacePath !== workspace.path && isFilePreviewAbsolutePath(path))
|
|
39
36
|
throw new Error('FILE_PATH_ESCAPE');
|
|
40
37
|
return isFilePreviewAbsolutePath(path)
|
|
41
|
-
?
|
|
38
|
+
? readAbsoluteTextFile(path, offset, limit)
|
|
42
39
|
: readWorkspaceTextFile(workspacePath, path, offset, limit);
|
|
43
40
|
}
|
|
44
41
|
async readContent(workspace, input, workspacePath = workspace.path) {
|
|
@@ -69,7 +66,7 @@ export class WorkspaceFileService {
|
|
|
69
66
|
const offset = typeof input.offset === 'number' ? input.offset : 0;
|
|
70
67
|
const limit = typeof input.limit === 'number' ? input.limit : 512 * 1024;
|
|
71
68
|
return isFilePreviewAbsolutePath(path)
|
|
72
|
-
?
|
|
69
|
+
? readAbsoluteFileContentRange(path, offset, limit)
|
|
73
70
|
: readWorkspaceFileContentRange(workspacePath, path, offset, limit);
|
|
74
71
|
}
|
|
75
72
|
readDirect(workspace, input, workspacePath = workspace.path) {
|
|
@@ -218,9 +215,6 @@ export class WorkspaceFileService {
|
|
|
218
215
|
index.dispose();
|
|
219
216
|
this.state.fileIndexes.clear();
|
|
220
217
|
}
|
|
221
|
-
previewRoots() {
|
|
222
|
-
return [...new Set([...this.allowedRoots(), tmpdir()])];
|
|
223
|
-
}
|
|
224
218
|
}
|
|
225
219
|
function isFilePreviewAbsolutePath(path) {
|
|
226
220
|
return isAbsolute(path) || win32.isAbsolute(path);
|
package/dist/workspace.js
CHANGED
|
@@ -757,15 +757,15 @@ export async function readWorkspaceFileContentRange(workspaceRoot, requestedPath
|
|
|
757
757
|
await handle.close();
|
|
758
758
|
}
|
|
759
759
|
}
|
|
760
|
-
/** Reads one absolute file range
|
|
761
|
-
export async function
|
|
760
|
+
/** Reads one regular absolute file range using the Node account's filesystem permissions. */
|
|
761
|
+
export async function readAbsoluteFileContentRange(requestedPath, offset = 0, limit = FILE_READ_LIMIT) {
|
|
762
762
|
if (!Number.isSafeInteger(offset) ||
|
|
763
763
|
offset < 0 ||
|
|
764
764
|
!Number.isSafeInteger(limit) ||
|
|
765
765
|
limit < 1 ||
|
|
766
766
|
limit > FILE_READ_LIMIT)
|
|
767
767
|
throw new Error('FILE_RANGE_INVALID');
|
|
768
|
-
const { file, metadata } = await
|
|
768
|
+
const { file, metadata } = await resolveAbsoluteFile(requestedPath);
|
|
769
769
|
if (offset > metadata.size)
|
|
770
770
|
throw new Error('FILE_RANGE_INVALID');
|
|
771
771
|
const length = Math.min(limit, metadata.size - offset);
|
|
@@ -792,9 +792,9 @@ function fileSourceRevision(metadata) {
|
|
|
792
792
|
.update([metadata.dev, metadata.ino, metadata.size, metadata.mtimeMs, metadata.ctimeMs].join(':'))
|
|
793
793
|
.digest('hex');
|
|
794
794
|
}
|
|
795
|
-
/** Reads one absolute file
|
|
796
|
-
export async function
|
|
797
|
-
const { file, metadata } = await
|
|
795
|
+
/** Reads one regular absolute file using the Node account's filesystem permissions. */
|
|
796
|
+
export async function readAbsoluteTextFile(requestedPath, offset = 0, limit = FILE_READ_LIMIT) {
|
|
797
|
+
const { file, metadata } = await resolveAbsoluteFile(requestedPath);
|
|
798
798
|
if (metadata.size > FILE_READ_LIMIT * 16)
|
|
799
799
|
throw new Error('FILE_TOO_LARGE');
|
|
800
800
|
if (!Number.isSafeInteger(offset) ||
|
|
@@ -859,7 +859,7 @@ export function normalizeFilePreviewPath(requestedPath) {
|
|
|
859
859
|
throw new Error('FILE_PATH_ESCAPE');
|
|
860
860
|
return normalized;
|
|
861
861
|
}
|
|
862
|
-
async function
|
|
862
|
+
async function resolveAbsoluteFile(requestedPath) {
|
|
863
863
|
if (!isAbsolute(requestedPath))
|
|
864
864
|
throw new Error('FILE_PATH_ESCAPE');
|
|
865
865
|
const file = await realpath(requestedPath).catch((error) => {
|
|
@@ -867,9 +867,6 @@ async function resolveAllowedFile(requestedPath, allowedRoots) {
|
|
|
867
867
|
throw new Error('FILE_NOT_FOUND');
|
|
868
868
|
throw error;
|
|
869
869
|
});
|
|
870
|
-
const roots = await Promise.all(allowedRoots.map((root) => realpath(root)));
|
|
871
|
-
if (!isWithinAllowedRoot(file, roots))
|
|
872
|
-
throw new Error('FILE_PATH_ESCAPE');
|
|
873
870
|
const metadata = await lstat(file);
|
|
874
871
|
if (!metadata.isFile())
|
|
875
872
|
throw new Error('FILE_NOT_REGULAR');
|
|
@@ -1019,7 +1016,9 @@ export function normalizeWorkspacePath(input, platform = hostPlatform()) {
|
|
|
1019
1016
|
if (!win32.isAbsolute(normalized)) {
|
|
1020
1017
|
throw new Error('WORKSPACE_PATH_NOT_ABSOLUTE');
|
|
1021
1018
|
}
|
|
1022
|
-
|
|
1019
|
+
// A drive root must remain absolute instead of becoming drive-relative.
|
|
1020
|
+
const root = win32.parse(normalized).root;
|
|
1021
|
+
return (normalized === root ? root : normalized.replace(/\\+$/, '')).toLowerCase();
|
|
1023
1022
|
}
|
|
1024
1023
|
if (platform !== 'linux' && platform !== 'darwin') {
|
|
1025
1024
|
throw new Error('WORKSPACE_PLATFORM_UNSUPPORTED');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myagentroam/node",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.140",
|
|
4
4
|
"description": "MyAgentRoam Node runtime CLI.",
|
|
5
5
|
"license": "Apache-2.0 AND GPL-2.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
],
|
|
14
14
|
"bin": {
|
|
15
15
|
"mar-node": "./dist/main.js",
|
|
16
|
-
"
|
|
16
|
+
"marctl": "./dist/marctl.js",
|
|
17
17
|
"marnodes": "./dist/marnodes.js",
|
|
18
18
|
"marssh": "./dist/marssh.js",
|
|
19
19
|
"marscp": "./dist/marscp.js"
|
|
@@ -34,10 +34,10 @@
|
|
|
34
34
|
"npm": "11.12.1",
|
|
35
35
|
"ws": "^8.21.3",
|
|
36
36
|
"zod": "4.4.3",
|
|
37
|
-
"@myagentroam/
|
|
38
|
-
"@myagentroam/
|
|
37
|
+
"@myagentroam/marctl": "0.9.120",
|
|
38
|
+
"@myagentroam/protocol": "0.9.140",
|
|
39
39
|
"@myagentroam/code-navigation": "0.9.129",
|
|
40
|
-
"@myagentroam/
|
|
40
|
+
"@myagentroam/agent": "0.9.140"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/ws": "^8.18.1"
|