@myagentroam/agent 0.9.158 → 0.9.160
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/agent-core/extensions/agents.d.ts +69 -0
- package/dist/agent-core/extensions/agents.js +160 -0
- package/dist/agent-core/extensions/contracts.d.ts +260 -0
- package/dist/agent-core/extensions/contracts.js +1 -0
- package/dist/agent-core/extensions/data.d.ts +84 -0
- package/dist/agent-core/extensions/data.js +83 -0
- package/dist/agent-core/extensions/environment.d.ts +27 -0
- package/dist/agent-core/extensions/environment.js +52 -0
- package/dist/agent-core/extensions/files.d.ts +64 -0
- package/dist/agent-core/extensions/files.js +26 -0
- package/dist/agent-core/extensions/hooks.d.ts +32 -0
- package/dist/agent-core/extensions/hooks.js +135 -0
- package/dist/agent-core/extensions/manager.d.ts +39 -0
- package/dist/agent-core/extensions/manager.js +458 -0
- package/dist/agent-core/extensions/mcp.d.ts +99 -0
- package/dist/agent-core/extensions/mcp.js +433 -0
- package/dist/agent-core/extensions/model-request.d.ts +60 -0
- package/dist/agent-core/extensions/model-request.js +167 -0
- package/dist/agent-core/extensions/model-stream.d.ts +9 -0
- package/dist/agent-core/extensions/model-stream.js +101 -0
- package/dist/agent-core/extensions/models.d.ts +43 -0
- package/dist/agent-core/extensions/models.js +1 -0
- package/dist/agent-core/extensions/network.d.ts +32 -0
- package/dist/agent-core/extensions/network.js +97 -0
- package/dist/agent-core/extensions/operation.d.ts +29 -0
- package/dist/agent-core/extensions/operation.js +98 -0
- package/dist/agent-core/extensions/processes.d.ts +52 -0
- package/dist/agent-core/extensions/processes.js +283 -0
- package/dist/agent-core/extensions/prompt-composition.d.ts +24 -0
- package/dist/agent-core/extensions/prompt-composition.js +106 -0
- package/dist/agent-core/extensions/prompt-input.d.ts +23 -0
- package/dist/agent-core/extensions/prompt-input.js +46 -0
- package/dist/agent-core/extensions/resources.d.ts +27 -0
- package/dist/agent-core/extensions/resources.js +221 -0
- package/dist/agent-core/extensions/services.d.ts +31 -0
- package/dist/agent-core/extensions/services.js +61 -0
- package/dist/agent-core/extensions/session-input.d.ts +151 -0
- package/dist/agent-core/extensions/session-input.js +351 -0
- package/dist/agent-core/extensions/session-messages.d.ts +41 -0
- package/dist/agent-core/extensions/session-messages.js +76 -0
- package/dist/agent-core/extensions/session.d.ts +68 -0
- package/dist/agent-core/extensions/session.js +593 -0
- package/dist/agent-core/extensions/settings.d.ts +59 -0
- package/dist/agent-core/extensions/settings.js +115 -0
- package/dist/agent-core/extensions/store.d.ts +10 -0
- package/dist/agent-core/extensions/store.js +131 -0
- package/dist/agent-core/extensions/tools.d.ts +61 -0
- package/dist/agent-core/extensions/tools.js +266 -0
- package/dist/agent-core/host/contracts.d.ts +25 -0
- package/dist/agent-core/host/local-agent-host.d.ts +10 -1
- package/dist/agent-core/host/local-agent-host.js +132 -20
- package/dist/agent-core/host/local-file-access.d.ts +30 -0
- package/dist/agent-core/host/local-file-access.js +107 -0
- package/dist/agent-core/host/local-network-access.d.ts +6 -0
- package/dist/agent-core/host/local-network-access.js +97 -0
- package/dist/agent-core/host/local-process-executor.d.ts +5 -1
- package/dist/agent-core/host/local-session-environment.d.ts +18 -0
- package/dist/agent-core/host/local-session-environment.js +102 -0
- package/dist/agent-core/model/anthropic-messages.js +73 -23
- package/dist/agent-core/model/contracts.d.ts +21 -0
- package/dist/agent-core/model/openai-responses.d.ts +3 -1
- package/dist/agent-core/model/openai-responses.js +248 -97
- package/dist/agent-core/model/text-blocks.d.ts +7 -0
- package/dist/agent-core/model/text-blocks.js +40 -0
- package/dist/agent-core/prompts/index.d.ts +6 -2
- package/dist/agent-core/prompts/index.js +38 -24
- package/dist/agent-core/runtime/context/gc-tokens.js +6 -1
- package/dist/agent-core/runtime/context/normalize.js +21 -1
- package/dist/agent-core/runtime/context/projection.js +35 -1
- package/dist/agent-core/runtime/context/request-budget.js +5 -2
- package/dist/agent-core/runtime/context/restore.d.ts +5 -0
- package/dist/agent-core/runtime/context/restore.js +47 -4
- package/dist/agent-core/runtime/context/usage.js +2 -3
- package/dist/agent-core/runtime/contracts.d.ts +2 -0
- package/dist/agent-core/runtime/execution.js +5 -2
- package/dist/agent-core/sdk/agent.js +177 -40
- package/dist/agent-core/sdk/extension-agents.d.ts +9 -0
- package/dist/agent-core/sdk/extension-agents.js +51 -0
- package/dist/agent-core/sdk/extension-models.d.ts +13 -0
- package/dist/agent-core/sdk/extension-models.js +128 -0
- package/dist/agent-core/sdk/extension-session.d.ts +30 -0
- package/dist/agent-core/sdk/extension-session.js +103 -0
- package/dist/agent-core/sdk/extension-tools.d.ts +16 -0
- package/dist/agent-core/sdk/extension-tools.js +126 -0
- package/dist/agent-core/sdk/session.d.ts +26 -2
- package/dist/agent-core/sdk/session.js +1215 -506
- package/dist/agent-core/sdk/types.d.ts +3 -0
- package/dist/agent-core/session/context-gc-checkpoint.js +1 -1
- package/dist/agent-core/session/jsonl-store.d.ts +28 -8
- package/dist/agent-core/session/jsonl-store.js +124 -84
- package/dist/agent-core/session/types.d.ts +4 -1
- package/dist/agent-core/storage/file-lock.d.ts +4 -0
- package/dist/agent-core/storage/file-lock.js +83 -0
- package/dist/agent-core/subagent/contracts.d.ts +11 -1
- package/dist/agent-core/subagent/scheduler.d.ts +8 -0
- package/dist/agent-core/subagent/session-controller.d.ts +22 -1
- package/dist/agent-core/subagent/session-controller.js +355 -98
- package/dist/agent-core/tools/agent/agent-start.d.ts +1 -1
- package/dist/agent-core/tools/code-mode/cells.js +5 -15
- package/dist/agent-core/tools/code-mode/dispatch.d.ts +1 -0
- package/dist/agent-core/tools/code-mode/dispatch.js +13 -1
- package/dist/agent-core/tools/code-mode/index.js +1 -2
- package/dist/agent-core/tools/code-mode/observations.d.ts +5 -0
- package/dist/agent-core/tools/code-mode/observations.js +16 -5
- package/dist/agent-core/tools/code-mode/quickjs-engine.js +10 -5
- package/dist/agent-core/tools/execution.d.ts +11 -2
- package/dist/agent-core/tools/execution.js +58 -21
- package/dist/agent-core/tools/input.d.ts +2 -0
- package/dist/agent-core/tools/input.js +36 -0
- package/dist/agent-core/tools/local/apply-patch.js +21 -17
- package/dist/agent-core/tools/local/exec.d.ts +4 -4
- package/dist/agent-core/tools/local/exec.js +234 -62
- package/dist/agent-core/tools/local/registry.d.ts +6 -0
- package/dist/agent-core/tools/local/registry.js +60 -9
- package/dist/agent-core/tools/local/view-image.js +108 -2
- package/dist/agent-core/tools/mcp/catalog.d.ts +6 -2
- package/dist/agent-core/tools/mcp/catalog.js +4 -4
- package/dist/agent-core/tools/mcp/history.js +2 -2
- package/dist/agent-core/tools/mcp/tool-search.d.ts +3 -1
- package/dist/agent-core/tools/mcp/tool-search.js +2 -2
- package/dist/agent-core/tools/output-projection.d.ts +3 -1
- package/dist/agent-core/tools/output-projection.js +11 -2
- package/dist/agent-core/tools/runtime-context.d.ts +1 -0
- package/dist/agent-core/tools/runtime.d.ts +3 -1
- package/dist/agent-core/tools/runtime.js +22 -7
- package/dist/agent-core/tools/scheduler.d.ts +2 -0
- package/dist/agent-core/tools/scheduler.js +43 -6
- package/dist/index.d.ts +21 -0
- package/dist/sandbox/linux/bubblewrap-sandbox-executor.js +5 -2
- package/package.json +1 -1
|
@@ -6,8 +6,13 @@ import { MarAgentError } from '../error.js';
|
|
|
6
6
|
import { TOOL_EXECUTION_LIMITS } from '../tools/execution-limits.js';
|
|
7
7
|
import { LocalToolRegistry } from '../tools/local/registry.js';
|
|
8
8
|
import { MAR_AGENT_HOST_API_VERSION } from './contracts.js';
|
|
9
|
+
import { LocalNetworkAccess } from './local-network-access.js';
|
|
10
|
+
import { LocalSessionEnvironment } from './local-session-environment.js';
|
|
9
11
|
import { directLocalProcessExecutor } from './local-process-executor.js';
|
|
12
|
+
import { BUILTIN_TOOL_DEFINITIONS } from '../tools/registry.js';
|
|
13
|
+
import { assertMcpToolCatalog } from '../tools/mcp/catalog.js';
|
|
10
14
|
export class LocalAgentHost {
|
|
15
|
+
extensionPorts;
|
|
11
16
|
#home;
|
|
12
17
|
#workspace;
|
|
13
18
|
#tools;
|
|
@@ -16,6 +21,10 @@ export class LocalAgentHost {
|
|
|
16
21
|
#mcpTools;
|
|
17
22
|
#mcpCatalog;
|
|
18
23
|
#mcpSource = randomUUID();
|
|
24
|
+
#toolSource = randomUUID();
|
|
25
|
+
#operationTools;
|
|
26
|
+
#beforeOperationToolExecute;
|
|
27
|
+
#callMcpOperation;
|
|
19
28
|
#callMcp;
|
|
20
29
|
#executionMcp = new Map();
|
|
21
30
|
#currentExecutionMcp;
|
|
@@ -23,6 +32,7 @@ export class LocalAgentHost {
|
|
|
23
32
|
events = [];
|
|
24
33
|
#mode;
|
|
25
34
|
#environment;
|
|
35
|
+
#sessionEnvironment;
|
|
26
36
|
#guaranteedCommands;
|
|
27
37
|
#shell;
|
|
28
38
|
#resourceIdleTtlMs;
|
|
@@ -34,28 +44,16 @@ export class LocalAgentHost {
|
|
|
34
44
|
this.#shell = selectExecutionShell(this.#environment);
|
|
35
45
|
this.#resourceIdleTtlMs = input.resourceIdleTtlMs;
|
|
36
46
|
const processExecutor = input.processExecutor ?? directLocalProcessExecutor;
|
|
47
|
+
this.#sessionEnvironment = new LocalSessionEnvironment(this.#environment, (executionId) => {
|
|
48
|
+
const binding = executionId ? this.#executionMcp.get(executionId) : undefined;
|
|
49
|
+
return binding?.active ? binding.extensions?.environment : undefined;
|
|
50
|
+
});
|
|
37
51
|
this.#tools = new LocalToolRegistry(this.#workspace, this.#environment, this.#shell.path, {
|
|
38
52
|
...(input.codeModeEngineFactory === undefined
|
|
39
53
|
? {}
|
|
40
54
|
: { codeModeEngineFactory: input.codeModeEngineFactory }),
|
|
41
55
|
processExecutor: {
|
|
42
|
-
spawn: (request) =>
|
|
43
|
-
const binding = request.owner && this.#executionMcp.get(request.owner.executionId);
|
|
44
|
-
const environment = binding?.active ? binding.extensions?.environment : undefined;
|
|
45
|
-
return processExecutor.spawn(environment === undefined
|
|
46
|
-
? request
|
|
47
|
-
: {
|
|
48
|
-
...request,
|
|
49
|
-
environment: { ...request.environment, ...environment },
|
|
50
|
-
...(request.environmentKeys === undefined
|
|
51
|
-
? {}
|
|
52
|
-
: {
|
|
53
|
-
environmentKeys: [
|
|
54
|
-
...new Set([...request.environmentKeys, ...Object.keys(environment)])
|
|
55
|
-
]
|
|
56
|
-
})
|
|
57
|
-
});
|
|
58
|
-
}
|
|
56
|
+
spawn: (request) => processExecutor.spawn(this.#sessionEnvironment.forProcess(request))
|
|
59
57
|
},
|
|
60
58
|
...(input.restrictPaths === undefined ? {} : { restrictPaths: input.restrictPaths }),
|
|
61
59
|
...(input.workspaceWritable === undefined
|
|
@@ -63,7 +61,20 @@ export class LocalAgentHost {
|
|
|
63
61
|
: { workspaceWritable: input.workspaceWritable }),
|
|
64
62
|
...(input.pathMounts === undefined ? {} : { pathMounts: input.pathMounts })
|
|
65
63
|
});
|
|
64
|
+
this.extensionPorts = {
|
|
65
|
+
tools: {
|
|
66
|
+
catalog: (sessionId, executionId) => this.#operationToolSources(sessionId, executionId)
|
|
67
|
+
},
|
|
68
|
+
environment: this.#sessionEnvironment,
|
|
69
|
+
...(input.mcp === undefined ? {} : { mcp: input.mcp }),
|
|
70
|
+
processes: this.#tools.processes,
|
|
71
|
+
files: this.#tools.files,
|
|
72
|
+
...(input.network === false ? {} : { network: input.network ?? new LocalNetworkAccess() })
|
|
73
|
+
};
|
|
66
74
|
this.#beforeToolExecute = input.beforeToolExecute;
|
|
75
|
+
this.#operationTools = input.operationTools;
|
|
76
|
+
this.#beforeOperationToolExecute = input.beforeOperationToolExecute;
|
|
77
|
+
this.#callMcpOperation = input.callMcpOperation;
|
|
67
78
|
this.#mode = input.mode ?? 'normal';
|
|
68
79
|
this.#question = input.question;
|
|
69
80
|
this.#mcpTools = input.mcpTools ?? [];
|
|
@@ -112,6 +123,94 @@ export class LocalAgentHost {
|
|
|
112
123
|
}
|
|
113
124
|
return this.#tools.execute(call, context);
|
|
114
125
|
}
|
|
126
|
+
#operationToolSources(sessionId, executionId) {
|
|
127
|
+
const available = (call) => call.sessionId === sessionId &&
|
|
128
|
+
(!this.#beforeToolExecute || this.#beforeOperationToolExecute !== undefined);
|
|
129
|
+
const policy = async (input, call) => {
|
|
130
|
+
call.signal.throwIfAborted();
|
|
131
|
+
if (!available(call))
|
|
132
|
+
throw new MarAgentError('MAR_AGENT_TOOL_NOT_AVAILABLE', 'Operation tool policy is unavailable.');
|
|
133
|
+
await this.#beforeOperationToolExecute?.(input, call);
|
|
134
|
+
call.signal.throwIfAborted();
|
|
135
|
+
};
|
|
136
|
+
const local = BUILTIN_TOOL_DEFINITIONS.filter((definition) => this.#tools.names().includes(definition.name)).map((definition) => ({
|
|
137
|
+
definition,
|
|
138
|
+
source: {
|
|
139
|
+
kind: 'builtin',
|
|
140
|
+
id: `${this.#toolSource}:${definition.name}`,
|
|
141
|
+
revision: createHash('sha256').update(JSON.stringify(definition)).digest('hex')
|
|
142
|
+
},
|
|
143
|
+
available: (call) => available(call) && definition.name !== 'code_exec' && definition.name !== 'code_exec_wait',
|
|
144
|
+
execute: async (input, call) => {
|
|
145
|
+
await policy(input, call);
|
|
146
|
+
return this.#tools.executeOperation(input, call);
|
|
147
|
+
}
|
|
148
|
+
}));
|
|
149
|
+
const additional = (this.#operationTools?.catalog(sessionId, executionId) ?? []).map((source) => {
|
|
150
|
+
const execute = source.execute.bind(source);
|
|
151
|
+
const check = source.available?.bind(source);
|
|
152
|
+
return {
|
|
153
|
+
definition: source.definition,
|
|
154
|
+
source: { ...source.source },
|
|
155
|
+
...(source.mcp === undefined ? {} : { mcp: source.mcp }),
|
|
156
|
+
available: (call) => available(call) && check?.(call) !== false,
|
|
157
|
+
execute: async (input, call) => {
|
|
158
|
+
await policy(input, call);
|
|
159
|
+
if (check?.(call) === false)
|
|
160
|
+
throw new MarAgentError('MAR_AGENT_TOOL_NOT_AVAILABLE', 'Host tool source is unavailable.');
|
|
161
|
+
return execute(input, call);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
});
|
|
165
|
+
const sharedCatalog = () => structuredClone(this.#mcpCatalog?.() ?? {
|
|
166
|
+
revision: '',
|
|
167
|
+
servers: [{ id: 'legacy', name: 'External MCP' }],
|
|
168
|
+
tools: this.#mcpTools.map((definition) => ({
|
|
169
|
+
definition,
|
|
170
|
+
serverId: 'legacy',
|
|
171
|
+
rawName: definition.name,
|
|
172
|
+
sourceIdentity: this.#mcpSource,
|
|
173
|
+
definitionRevision: createHash('sha256')
|
|
174
|
+
.update(JSON.stringify(definition))
|
|
175
|
+
.digest('hex'),
|
|
176
|
+
lifetime: 'session',
|
|
177
|
+
exposure: 'deferred'
|
|
178
|
+
}))
|
|
179
|
+
});
|
|
180
|
+
const catalog = this.#callMcpOperation && executionId === undefined
|
|
181
|
+
? sharedCatalog()
|
|
182
|
+
: { revision: '', servers: [], tools: [] };
|
|
183
|
+
assertMcpToolCatalog(catalog);
|
|
184
|
+
const current = (tool) => {
|
|
185
|
+
const next = sharedCatalog().tools.find((value) => value.definition.name === tool.definition.name);
|
|
186
|
+
return (next?.lifetime === 'session' &&
|
|
187
|
+
next.sourceIdentity === tool.sourceIdentity &&
|
|
188
|
+
next.definitionRevision === tool.definitionRevision);
|
|
189
|
+
};
|
|
190
|
+
const mcp = this.#callMcpOperation
|
|
191
|
+
? catalog.tools
|
|
192
|
+
.filter((tool) => tool.lifetime === 'session')
|
|
193
|
+
.map((tool) => ({
|
|
194
|
+
definition: tool.definition,
|
|
195
|
+
mcp: tool,
|
|
196
|
+
source: { kind: 'mcp', id: tool.sourceIdentity, revision: tool.definitionRevision },
|
|
197
|
+
available: (call) => available(call) && current(tool),
|
|
198
|
+
execute: async (input, call) => {
|
|
199
|
+
await policy(input, call);
|
|
200
|
+
if (!current(tool))
|
|
201
|
+
throw new MarAgentError('MAR_AGENT_MCP_TOOL_DEFINITION_CHANGED', 'MCP tool source changed.');
|
|
202
|
+
return this.#callMcpOperation(input, call, {
|
|
203
|
+
sourceIdentity: tool.sourceIdentity,
|
|
204
|
+
definitionRevision: tool.definitionRevision
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}))
|
|
208
|
+
: [];
|
|
209
|
+
const sources = [...local, ...additional, ...mcp];
|
|
210
|
+
if (new Set(sources.map((source) => source.definition.name)).size !== sources.length)
|
|
211
|
+
throw new MarAgentError('MAR_AGENT_EXTENSION_TOOL_CONFLICT', 'Host tool sources conflict.');
|
|
212
|
+
return sources;
|
|
213
|
+
}
|
|
115
214
|
bindExecutionExtensions(extensions, mcp) {
|
|
116
215
|
const binding = {
|
|
117
216
|
tools: mcp?.tools ?? [],
|
|
@@ -143,6 +242,9 @@ export class LocalAgentHost {
|
|
|
143
242
|
})
|
|
144
243
|
};
|
|
145
244
|
}
|
|
245
|
+
async listSessionSkills(_context) {
|
|
246
|
+
return Object.freeze([...(this.#extensions?.skills ?? [])]);
|
|
247
|
+
}
|
|
146
248
|
emit(event) {
|
|
147
249
|
this.events.push(event);
|
|
148
250
|
}
|
|
@@ -269,11 +371,21 @@ export class LocalAgentHost {
|
|
|
269
371
|
codeCells: this.#tools.listCodeCells(sessionId)
|
|
270
372
|
});
|
|
271
373
|
}
|
|
272
|
-
disposeSessionResources(sessionId) {
|
|
273
|
-
|
|
374
|
+
async disposeSessionResources(sessionId) {
|
|
375
|
+
try {
|
|
376
|
+
await this.#tools.disposeSessionResources(sessionId);
|
|
377
|
+
}
|
|
378
|
+
finally {
|
|
379
|
+
this.#sessionEnvironment.clearSession(sessionId);
|
|
380
|
+
}
|
|
274
381
|
}
|
|
275
382
|
async dispose() {
|
|
276
|
-
|
|
383
|
+
try {
|
|
384
|
+
await this.#tools.dispose();
|
|
385
|
+
}
|
|
386
|
+
finally {
|
|
387
|
+
this.#sessionEnvironment.clear();
|
|
388
|
+
}
|
|
277
389
|
}
|
|
278
390
|
}
|
|
279
391
|
export function selectExecutionShell(environment = process.env, platform = process.platform, available = existsSync) {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { CoreFilesBackend, CoreFilePath, CoreFileWrite, CoreFileRemove, CoreFileTransfer, CoreFileType } from '../extensions/files.js';
|
|
2
|
+
import type { CoreOperationContext } from '../extensions/operation.js';
|
|
3
|
+
import { WorkspacePathResolver } from '../tools/shared/path-resolver.js';
|
|
4
|
+
/** Raw native access shares the exact resolver used by the Host's model-facing tools. */
|
|
5
|
+
export declare class LocalFileAccess implements CoreFilesBackend {
|
|
6
|
+
#private;
|
|
7
|
+
readonly paths: WorkspacePathResolver;
|
|
8
|
+
constructor(paths: WorkspacePathResolver);
|
|
9
|
+
read(input: CoreFilePath, call: CoreOperationContext): Promise<Uint8Array<ArrayBuffer>>;
|
|
10
|
+
write(input: CoreFileWrite, call: CoreOperationContext): Promise<{
|
|
11
|
+
bytesWritten: number;
|
|
12
|
+
}>;
|
|
13
|
+
stat(input: CoreFilePath, call: CoreOperationContext): Promise<{
|
|
14
|
+
path: string;
|
|
15
|
+
type: CoreFileType;
|
|
16
|
+
size: number;
|
|
17
|
+
modifiedAtMs: number;
|
|
18
|
+
createdAtMs: number;
|
|
19
|
+
}>;
|
|
20
|
+
list(input: CoreFilePath, call: CoreOperationContext): Promise<{
|
|
21
|
+
name: string;
|
|
22
|
+
type: CoreFileType;
|
|
23
|
+
}[]>;
|
|
24
|
+
mkdir(input: CoreFilePath & {
|
|
25
|
+
readonly recursive?: boolean;
|
|
26
|
+
}, call: CoreOperationContext): Promise<void>;
|
|
27
|
+
remove(input: CoreFileRemove, call: CoreOperationContext): Promise<void>;
|
|
28
|
+
move(input: CoreFileTransfer, call: CoreOperationContext): Promise<void>;
|
|
29
|
+
copy(input: CoreFileTransfer, call: CoreOperationContext): Promise<void>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { copyFile, lstat, mkdir, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { MarAgentError } from '../error.js';
|
|
4
|
+
/** Raw native access shares the exact resolver used by the Host's model-facing tools. */
|
|
5
|
+
export class LocalFileAccess {
|
|
6
|
+
paths;
|
|
7
|
+
constructor(paths) {
|
|
8
|
+
this.paths = paths;
|
|
9
|
+
}
|
|
10
|
+
read(input, call) {
|
|
11
|
+
return this.#execute('read', call, async () => new Uint8Array(await readFile(await this.#path(input.path), { signal: call.signal })));
|
|
12
|
+
}
|
|
13
|
+
write(input, call) {
|
|
14
|
+
return this.#execute('write', call, async () => {
|
|
15
|
+
if (!(input.bytes instanceof Uint8Array))
|
|
16
|
+
throw new MarAgentError('MAR_AGENT_FILE_INPUT_INVALID', 'File bytes must be a Uint8Array.');
|
|
17
|
+
const bytes = Buffer.from(input.bytes);
|
|
18
|
+
const path = await this.#path(input.path, true);
|
|
19
|
+
call.signal.throwIfAborted();
|
|
20
|
+
if (input.createParents)
|
|
21
|
+
await mkdir(dirname(path), { recursive: true });
|
|
22
|
+
await writeFile(path, bytes, { signal: call.signal });
|
|
23
|
+
return { bytesWritten: bytes.byteLength };
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
stat(input, call) {
|
|
27
|
+
return this.#execute('stat', call, async () => {
|
|
28
|
+
const path = await this.paths.resolveWithDisplay(this.#validatePath(input.path));
|
|
29
|
+
const info = await lstat(path.physicalPath);
|
|
30
|
+
return {
|
|
31
|
+
path: path.displayPath,
|
|
32
|
+
type: fileType(info),
|
|
33
|
+
size: info.size,
|
|
34
|
+
modifiedAtMs: info.mtimeMs,
|
|
35
|
+
createdAtMs: info.birthtimeMs
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
list(input, call) {
|
|
40
|
+
return this.#execute('list', call, async () => (await readdir(await this.#path(input.path), { withFileTypes: true })).map((entry) => ({
|
|
41
|
+
name: entry.name,
|
|
42
|
+
type: fileType(entry)
|
|
43
|
+
})));
|
|
44
|
+
}
|
|
45
|
+
mkdir(input, call) {
|
|
46
|
+
return this.#execute('mkdir', call, async () => {
|
|
47
|
+
const path = await this.#path(input.path, true);
|
|
48
|
+
call.signal.throwIfAborted();
|
|
49
|
+
await mkdir(path, { recursive: input.recursive ?? false });
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
remove(input, call) {
|
|
53
|
+
return this.#execute('remove', call, async () => {
|
|
54
|
+
const path = await this.#path(input.path, true);
|
|
55
|
+
call.signal.throwIfAborted();
|
|
56
|
+
await rm(path, { recursive: input.recursive ?? false, force: input.force ?? false });
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
move(input, call) {
|
|
60
|
+
return this.#execute('move', call, async () => {
|
|
61
|
+
const from = await this.#path(input.from, true);
|
|
62
|
+
const to = await this.#path(input.to, true);
|
|
63
|
+
call.signal.throwIfAborted();
|
|
64
|
+
await rename(from, to);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
copy(input, call) {
|
|
68
|
+
return this.#execute('copy', call, async () => {
|
|
69
|
+
const from = await this.#path(input.from);
|
|
70
|
+
const to = await this.#path(input.to, true);
|
|
71
|
+
call.signal.throwIfAborted();
|
|
72
|
+
await copyFile(from, to);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
#validatePath(path) {
|
|
76
|
+
if (typeof path !== 'string')
|
|
77
|
+
throw new MarAgentError('MAR_AGENT_FILE_INPUT_INVALID', 'File path must be a string.');
|
|
78
|
+
return path;
|
|
79
|
+
}
|
|
80
|
+
#path(path, write = false) {
|
|
81
|
+
return this.paths.resolve(this.#validatePath(path), write, write ? 'write' : 'read');
|
|
82
|
+
}
|
|
83
|
+
async #execute(operation, call, action) {
|
|
84
|
+
call.signal.throwIfAborted();
|
|
85
|
+
try {
|
|
86
|
+
return await action();
|
|
87
|
+
}
|
|
88
|
+
catch (cause) {
|
|
89
|
+
call.signal.throwIfAborted();
|
|
90
|
+
if (cause instanceof MarAgentError)
|
|
91
|
+
throw cause;
|
|
92
|
+
throw new MarAgentError('MAR_AGENT_FILE_OPERATION_FAILED', 'Native file operation failed.', {
|
|
93
|
+
details: { operation, reason: cause.code ?? 'UNKNOWN' },
|
|
94
|
+
cause
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function fileType(info) {
|
|
100
|
+
return info.isSymbolicLink()
|
|
101
|
+
? 'symlink'
|
|
102
|
+
: info.isFile()
|
|
103
|
+
? 'file'
|
|
104
|
+
: info.isDirectory()
|
|
105
|
+
? 'directory'
|
|
106
|
+
: 'other';
|
|
107
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { CoreHttpRequest, CoreHttpResponse, CoreNetworkBackend } from '../extensions/network.js';
|
|
2
|
+
import type { CoreOperationContext } from '../extensions/operation.js';
|
|
3
|
+
/** Raw HTTP transport; presentation, retries, and browser fetch remain separate tools. */
|
|
4
|
+
export declare class LocalNetworkAccess implements CoreNetworkBackend {
|
|
5
|
+
fetch(input: CoreHttpRequest, call: CoreOperationContext): Promise<CoreHttpResponse>;
|
|
6
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { MarAgentError } from '../error.js';
|
|
2
|
+
/** Raw HTTP transport; presentation, retries, and browser fetch remain separate tools. */
|
|
3
|
+
export class LocalNetworkAccess {
|
|
4
|
+
async fetch(input, call) {
|
|
5
|
+
call.signal.throwIfAborted();
|
|
6
|
+
if (!input || typeof input.url !== 'string')
|
|
7
|
+
throw new MarAgentError('MAR_AGENT_NETWORK_REQUEST_INVALID', 'HTTP URL is required.');
|
|
8
|
+
let url;
|
|
9
|
+
try {
|
|
10
|
+
url = new URL(input.url);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
throw new MarAgentError('MAR_AGENT_NETWORK_REQUEST_INVALID', 'HTTP URL is invalid.');
|
|
14
|
+
}
|
|
15
|
+
if (!['http:', 'https:'].includes(url.protocol))
|
|
16
|
+
throw new MarAgentError('MAR_AGENT_NETWORK_REQUEST_INVALID', 'An HTTP or HTTPS URL is required.');
|
|
17
|
+
const controller = new AbortController();
|
|
18
|
+
const signal = AbortSignal.any([
|
|
19
|
+
call.signal,
|
|
20
|
+
controller.signal,
|
|
21
|
+
...(input.signal ? [input.signal] : [])
|
|
22
|
+
]);
|
|
23
|
+
let response;
|
|
24
|
+
try {
|
|
25
|
+
response = await globalThis.fetch(url, {
|
|
26
|
+
...(input.method === undefined ? {} : { method: input.method }),
|
|
27
|
+
...(input.headers === undefined ? {} : { headers: input.headers.map((pair) => [...pair]) }),
|
|
28
|
+
...(input.body === undefined
|
|
29
|
+
? {}
|
|
30
|
+
: { body: typeof input.body === 'string' ? input.body : Buffer.from(input.body) }),
|
|
31
|
+
...(input.redirect === undefined ? {} : { redirect: input.redirect }),
|
|
32
|
+
signal
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
signal.throwIfAborted();
|
|
37
|
+
const cause = error && typeof error === 'object'
|
|
38
|
+
? error.cause
|
|
39
|
+
: undefined;
|
|
40
|
+
throw new MarAgentError('MAR_AGENT_NETWORK_FAILED', 'HTTP request failed.', {
|
|
41
|
+
...(typeof cause?.code === 'string' ? { details: { reasonCode: cause.code } } : {})
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
const reader = response.body?.getReader();
|
|
45
|
+
let closing;
|
|
46
|
+
const close = () => {
|
|
47
|
+
closing ??= (async () => {
|
|
48
|
+
signal.removeEventListener('abort', onAbort);
|
|
49
|
+
controller.abort();
|
|
50
|
+
try {
|
|
51
|
+
await reader?.cancel();
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
// An already-aborted Fetch reader rejects cancel with that same cancellation reason.
|
|
55
|
+
if (!signal.aborted || error !== signal.reason)
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
reader?.releaseLock();
|
|
60
|
+
}
|
|
61
|
+
})();
|
|
62
|
+
return closing;
|
|
63
|
+
};
|
|
64
|
+
const onAbort = () => {
|
|
65
|
+
void close().catch(() => undefined);
|
|
66
|
+
};
|
|
67
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
68
|
+
if (signal.aborted) {
|
|
69
|
+
await close();
|
|
70
|
+
signal.throwIfAborted();
|
|
71
|
+
}
|
|
72
|
+
const body = async function* () {
|
|
73
|
+
try {
|
|
74
|
+
while (reader) {
|
|
75
|
+
signal.throwIfAborted();
|
|
76
|
+
const next = await reader.read();
|
|
77
|
+
signal.throwIfAborted();
|
|
78
|
+
if (next.done)
|
|
79
|
+
return;
|
|
80
|
+
yield next.value;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
await close();
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
return {
|
|
88
|
+
url: response.url,
|
|
89
|
+
status: response.status,
|
|
90
|
+
statusText: response.statusText,
|
|
91
|
+
redirected: response.redirected,
|
|
92
|
+
headers: [...response.headers.entries()],
|
|
93
|
+
body: body(),
|
|
94
|
+
close
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -5,13 +5,17 @@ export interface LocalProcessSpawnRequest {
|
|
|
5
5
|
readonly cwd: string;
|
|
6
6
|
readonly shell?: boolean | string;
|
|
7
7
|
readonly environment: NodeJS.ProcessEnv;
|
|
8
|
+
/** Explicit per-start values take precedence over execution-bound Host environment. */
|
|
9
|
+
readonly environmentOverrides?: Readonly<Record<string, string>>;
|
|
8
10
|
readonly environmentKeys?: readonly string[];
|
|
9
11
|
/** Trusted, bounded read-only worker assets; never supplied by model tool input. */
|
|
10
12
|
readonly runtimeDirectories?: readonly string[];
|
|
11
13
|
readonly detached: boolean;
|
|
12
14
|
readonly owner?: {
|
|
13
15
|
readonly sessionId: string;
|
|
14
|
-
readonly executionId
|
|
16
|
+
readonly executionId?: string;
|
|
17
|
+
readonly operationId?: string;
|
|
18
|
+
readonly ownerInstanceId?: string;
|
|
15
19
|
};
|
|
16
20
|
readonly signal?: AbortSignal;
|
|
17
21
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { CoreEnvironmentBackend, CoreEnvironmentKey, CoreEnvironmentSet } from '../extensions/environment.js';
|
|
2
|
+
import type { CoreOperationContext } from '../extensions/operation.js';
|
|
3
|
+
import type { LocalProcessSpawnRequest } from './local-process-executor.js';
|
|
4
|
+
/** Session-owned overlay, shared by native and model/programmatic process starts. */
|
|
5
|
+
export declare class LocalSessionEnvironment implements CoreEnvironmentBackend {
|
|
6
|
+
#private;
|
|
7
|
+
private readonly base;
|
|
8
|
+
private readonly executionEnvironment;
|
|
9
|
+
private readonly platform;
|
|
10
|
+
constructor(base: NodeJS.ProcessEnv, executionEnvironment: (executionId?: string) => Readonly<Record<string, string>> | undefined, platform?: NodeJS.Platform);
|
|
11
|
+
get(input: CoreEnvironmentKey, call: CoreOperationContext): Promise<string | undefined>;
|
|
12
|
+
set(input: CoreEnvironmentSet, call: CoreOperationContext): Promise<{
|
|
13
|
+
changed: boolean;
|
|
14
|
+
}>;
|
|
15
|
+
forProcess(request: LocalProcessSpawnRequest): LocalProcessSpawnRequest;
|
|
16
|
+
clearSession(sessionId: string): void;
|
|
17
|
+
clear(): void;
|
|
18
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { validateEnvironmentInput } from '../extensions/environment.js';
|
|
2
|
+
/** Session-owned overlay, shared by native and model/programmatic process starts. */
|
|
3
|
+
export class LocalSessionEnvironment {
|
|
4
|
+
base;
|
|
5
|
+
executionEnvironment;
|
|
6
|
+
platform;
|
|
7
|
+
#sessions = new Map();
|
|
8
|
+
constructor(base, executionEnvironment, platform = process.platform) {
|
|
9
|
+
this.base = base;
|
|
10
|
+
this.executionEnvironment = executionEnvironment;
|
|
11
|
+
this.platform = platform;
|
|
12
|
+
}
|
|
13
|
+
#key(key) {
|
|
14
|
+
return this.platform === 'win32' ? key.toUpperCase() : key;
|
|
15
|
+
}
|
|
16
|
+
#read(environment, key) {
|
|
17
|
+
if (this.platform !== 'win32')
|
|
18
|
+
return Object.hasOwn(environment, key) ? environment[key] : undefined;
|
|
19
|
+
const name = Object.keys(environment).find((name) => this.#key(name) === this.#key(key));
|
|
20
|
+
return name === undefined ? undefined : environment[name];
|
|
21
|
+
}
|
|
22
|
+
async get(input, call) {
|
|
23
|
+
validateEnvironmentInput(input);
|
|
24
|
+
call.signal.throwIfAborted();
|
|
25
|
+
const overlay = this.#sessions.get(call.sessionId)?.get(this.#key(input.key));
|
|
26
|
+
if (overlay)
|
|
27
|
+
return overlay.value;
|
|
28
|
+
const execution = this.executionEnvironment(call.executionId);
|
|
29
|
+
if (execution && Object.keys(execution).some((key) => this.#key(key) === this.#key(input.key)))
|
|
30
|
+
return this.#read(execution, input.key);
|
|
31
|
+
return this.#read(this.base, input.key);
|
|
32
|
+
}
|
|
33
|
+
async set(input, call) {
|
|
34
|
+
validateEnvironmentInput(input);
|
|
35
|
+
call.signal.throwIfAborted();
|
|
36
|
+
const key = this.#key(input.key);
|
|
37
|
+
const entries = this.#sessions.get(call.sessionId) ?? new Map();
|
|
38
|
+
const before = entries.get(key);
|
|
39
|
+
const changed = !before || before.value !== input.value;
|
|
40
|
+
// The tombstone is a real change even if the current lower layer happens to be unset.
|
|
41
|
+
entries.set(key, { key: input.key, value: input.value });
|
|
42
|
+
this.#sessions.set(call.sessionId, entries);
|
|
43
|
+
return { changed };
|
|
44
|
+
}
|
|
45
|
+
forProcess(request) {
|
|
46
|
+
const execution = this.executionEnvironment(request.owner?.executionId);
|
|
47
|
+
const entries = request.owner && this.#sessions.get(request.owner.sessionId);
|
|
48
|
+
if (execution === undefined &&
|
|
49
|
+
!entries?.size &&
|
|
50
|
+
(this.platform !== 'win32' || request.environmentOverrides === undefined))
|
|
51
|
+
return request;
|
|
52
|
+
const environment = { ...request.environment };
|
|
53
|
+
const assign = (key, value) => {
|
|
54
|
+
if (this.platform === 'win32') {
|
|
55
|
+
for (const current of Object.keys(environment))
|
|
56
|
+
if (this.#key(current) === this.#key(key))
|
|
57
|
+
delete environment[current];
|
|
58
|
+
}
|
|
59
|
+
if (value === undefined)
|
|
60
|
+
delete environment[key];
|
|
61
|
+
else
|
|
62
|
+
Object.defineProperty(environment, key, {
|
|
63
|
+
value,
|
|
64
|
+
enumerable: true,
|
|
65
|
+
writable: true,
|
|
66
|
+
configurable: true
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
for (const [key, value] of Object.entries(execution ?? {}))
|
|
70
|
+
assign(key, value);
|
|
71
|
+
for (const entry of entries?.values() ?? [])
|
|
72
|
+
assign(entry.key, entry.value);
|
|
73
|
+
for (const [key, value] of Object.entries(request.environmentOverrides ?? {}))
|
|
74
|
+
assign(key, value);
|
|
75
|
+
return {
|
|
76
|
+
...request,
|
|
77
|
+
environment,
|
|
78
|
+
...(request.environmentKeys === undefined
|
|
79
|
+
? {}
|
|
80
|
+
: {
|
|
81
|
+
environmentKeys: [
|
|
82
|
+
...new Set([
|
|
83
|
+
...request.environmentKeys,
|
|
84
|
+
...Object.keys(execution ?? {}),
|
|
85
|
+
...Object.keys(request.environmentOverrides ?? {}),
|
|
86
|
+
...[...(entries?.values() ?? [])].map((entry) => entry.key)
|
|
87
|
+
]
|
|
88
|
+
.map((key) => this.platform === 'win32'
|
|
89
|
+
? Object.keys(environment).find((name) => this.#key(name) === this.#key(key))
|
|
90
|
+
: key)
|
|
91
|
+
.filter((key) => key !== undefined && environment[key] !== undefined))
|
|
92
|
+
]
|
|
93
|
+
})
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
clearSession(sessionId) {
|
|
97
|
+
this.#sessions.delete(sessionId);
|
|
98
|
+
}
|
|
99
|
+
clear() {
|
|
100
|
+
this.#sessions.clear();
|
|
101
|
+
}
|
|
102
|
+
}
|