@myagentroam/agent 0.9.151 → 0.9.153
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/host/contracts.d.ts +1 -1
- package/dist/agent-core/host/local-agent-host.d.ts +1 -6
- package/dist/agent-core/host/local-agent-host.js +13 -14
- package/dist/agent-core/prompts/execution-budget.d.ts +1 -1
- package/dist/agent-core/prompts/execution-budget.js +3 -5
- package/dist/agent-core/runtime/execution.js +5 -7
- package/dist/agent-core/runtime/rollout-budget.d.ts +1 -0
- package/dist/agent-core/runtime/rollout-budget.js +4 -0
- package/dist/agent-core/sdk/session.js +9 -14
- package/dist/agent-core/session/jsonl-store.js +44 -9
- package/dist/agent-core/tools/contracts.d.ts +3 -1
- package/dist/agent-core/tools/local/registry.d.ts +1 -7
- package/dist/agent-core/tools/local/registry.js +9 -29
- package/dist/agent-core/tools/registry.js +1 -5
- package/dist/agent-core/tools/runtime-context.d.ts +1 -0
- package/dist/agent-core/tools/runtime.js +1 -0
- package/package.json +1 -1
- package/dist/agent-core/tools/local/web-fetch.d.ts +0 -4
- package/dist/agent-core/tools/local/web-fetch.js +0 -85
- package/dist/agent-core/tools/local/web-search.d.ts +0 -7
- package/dist/agent-core/tools/local/web-search.js +0 -34
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { MarAgentEvent } from '../runtime/events.js';
|
|
2
2
|
import type { ClientToolDefinition, McpToolCallContext, McpToolCatalog, McpToolCatalogContext, ToolCall, ToolExecutionContext, ToolExecutionResult } from '../tools/contracts.js';
|
|
3
3
|
export declare const MAR_AGENT_HOST_API_VERSION = "1.0";
|
|
4
|
-
export type MarAgentHostCapability = 'workspace.read' | 'workspace.patch' | 'process.manage' | 'code.execute' | 'user-input.request' | 'event.publish' | 'session.store' | 'mcp.tools' | 'image.read'
|
|
4
|
+
export type MarAgentHostCapability = 'workspace.read' | 'workspace.patch' | 'process.manage' | 'code.execute' | 'user-input.request' | 'event.publish' | 'session.store' | 'mcp.tools' | 'image.read';
|
|
5
5
|
export interface HostDescription {
|
|
6
6
|
apiVersion: string;
|
|
7
7
|
mode: 'normal' | 'node';
|
|
@@ -16,6 +16,7 @@ export interface LocalAgentExecutionMcpBinding extends LocalAgentMcpBinding {
|
|
|
16
16
|
export interface LocalAgentExecutionExtensions extends HostExecutionExtensions {
|
|
17
17
|
/** Host-private process environment, snapshotted when bound to an execution. */
|
|
18
18
|
readonly environment?: Readonly<Record<string, string>>;
|
|
19
|
+
isToolAvailable?(name: string, context: Pick<ToolExecutionContext, 'sessionId' | 'executionId' | 'modelId'>): boolean;
|
|
19
20
|
executeTool?(call: ToolCall, context: ToolExecutionContext): Promise<ToolExecutionResult>;
|
|
20
21
|
}
|
|
21
22
|
export declare class LocalAgentHost implements MarAgentHost {
|
|
@@ -36,12 +37,6 @@ export declare class LocalAgentHost implements MarAgentHost {
|
|
|
36
37
|
extensions?: LocalAgentExecutionExtensions;
|
|
37
38
|
environment?: NodeJS.ProcessEnv;
|
|
38
39
|
guaranteedCommands?: Readonly<Record<string, string>>;
|
|
39
|
-
webSearch?: (input: {
|
|
40
|
-
query: string;
|
|
41
|
-
maxResults: number;
|
|
42
|
-
}, signal: AbortSignal) => Promise<ToolExecutionResult>;
|
|
43
|
-
webFetch?: boolean;
|
|
44
|
-
allowUntrustedTls?: boolean;
|
|
45
40
|
resourceIdleTtlMs?: number;
|
|
46
41
|
processExecutor?: LocalProcessExecutor;
|
|
47
42
|
codeModeEngineFactory?: CodeModeEngineFactory;
|
|
@@ -25,8 +25,6 @@ export class LocalAgentHost {
|
|
|
25
25
|
#environment;
|
|
26
26
|
#guaranteedCommands;
|
|
27
27
|
#shell;
|
|
28
|
-
#hasWebSearch;
|
|
29
|
-
#hasWebFetch;
|
|
30
28
|
#resourceIdleTtlMs;
|
|
31
29
|
constructor(input) {
|
|
32
30
|
this.#home = resolve(input.homeDirectory);
|
|
@@ -34,15 +32,9 @@ export class LocalAgentHost {
|
|
|
34
32
|
this.#environment = input.environment ?? process.env;
|
|
35
33
|
this.#guaranteedCommands = input.guaranteedCommands ?? {};
|
|
36
34
|
this.#shell = selectExecutionShell(this.#environment);
|
|
37
|
-
this.#hasWebSearch = input.webSearch !== undefined;
|
|
38
|
-
this.#hasWebFetch = input.webFetch !== false;
|
|
39
35
|
this.#resourceIdleTtlMs = input.resourceIdleTtlMs;
|
|
40
36
|
const processExecutor = input.processExecutor ?? directLocalProcessExecutor;
|
|
41
37
|
this.#tools = new LocalToolRegistry(this.#workspace, this.#environment, this.#shell.path, {
|
|
42
|
-
...(input.webSearch ? { webSearch: input.webSearch } : {}),
|
|
43
|
-
webFetch: this.#hasWebFetch,
|
|
44
|
-
allowUntrustedTls: input.allowUntrustedTls ??
|
|
45
|
-
this.#environment['MAR_AGENT_WEB_FETCH_ALLOW_UNTRUSTED_TLS'] === '1',
|
|
46
38
|
...(input.codeModeEngineFactory === undefined
|
|
47
39
|
? {}
|
|
48
40
|
: { codeModeEngineFactory: input.codeModeEngineFactory }),
|
|
@@ -97,9 +89,7 @@ export class LocalAgentHost {
|
|
|
97
89
|
'code.execute',
|
|
98
90
|
'user-input.request',
|
|
99
91
|
'event.publish',
|
|
100
|
-
'session.store'
|
|
101
|
-
...(this.#hasWebFetch ? ['web.fetch'] : []),
|
|
102
|
-
...(this.#hasWebSearch ? ['web.search'] : [])
|
|
92
|
+
'session.store'
|
|
103
93
|
],
|
|
104
94
|
limits: {
|
|
105
95
|
maxProcesses: TOOL_EXECUTION_LIMITS.maxProcesses,
|
|
@@ -113,7 +103,9 @@ export class LocalAgentHost {
|
|
|
113
103
|
const binding = this.#executionMcp.get(context.executionId);
|
|
114
104
|
const extensions = binding?.extensions ?? this.#extensions;
|
|
115
105
|
if (extensions?.tools?.some((tool) => tool.name === call.name)) {
|
|
116
|
-
if (binding?.active === false ||
|
|
106
|
+
if (binding?.active === false ||
|
|
107
|
+
!extensions.executeTool ||
|
|
108
|
+
extensions.isToolAvailable?.(call.name, context) === false)
|
|
117
109
|
throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Host tool execution is unavailable.');
|
|
118
110
|
context.signal.throwIfAborted();
|
|
119
111
|
return extensions.executeTool(call, context);
|
|
@@ -141,8 +133,15 @@ export class LocalAgentHost {
|
|
|
141
133
|
}
|
|
142
134
|
async listExecutionExtensions(context) {
|
|
143
135
|
const binding = this.#executionBinding(context);
|
|
144
|
-
const { environment: _environment, ...extensions } = binding?.extensions ?? this.#extensions ?? {};
|
|
145
|
-
return
|
|
136
|
+
const { environment: _environment, isToolAvailable, ...extensions } = binding?.extensions ?? this.#extensions ?? {};
|
|
137
|
+
return {
|
|
138
|
+
...extensions,
|
|
139
|
+
...(extensions.tools === undefined
|
|
140
|
+
? {}
|
|
141
|
+
: {
|
|
142
|
+
tools: extensions.tools.filter((tool) => isToolAvailable?.(tool.name, context) !== false)
|
|
143
|
+
})
|
|
144
|
+
};
|
|
146
145
|
}
|
|
147
146
|
emit(event) {
|
|
148
147
|
this.events.push(event);
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { RolloutBudgetStage } from '../runtime/rollout-budget.js';
|
|
2
|
-
export type ExecutionBudgetEnvironmentStage = Exclude<RolloutBudgetStage, 'normal'> | 'inactive';
|
|
2
|
+
export type ExecutionBudgetEnvironmentStage = Exclude<RolloutBudgetStage, 'normal' | 'exhausted'> | 'inactive';
|
|
3
3
|
export declare function executionBudgetEnvironmentEntry(stage: ExecutionBudgetEnvironmentStage): string;
|
|
@@ -2,10 +2,8 @@ export function executionBudgetEnvironmentEntry(stage) {
|
|
|
2
2
|
if (stage === 'inactive')
|
|
3
3
|
return '<execution_guidance status="inactive" />';
|
|
4
4
|
const privateInstruction = 'This is internal runtime guidance. Never mention the budget, thresholds, remaining amount, or budget stage in user-visible progress or the final answer.';
|
|
5
|
-
const guidance = stage === '
|
|
6
|
-
? '
|
|
7
|
-
:
|
|
8
|
-
? 'Enter finalization now. Do not start new investigation or new subagents. Complete only the current atomic change, the minimum necessary verification, and the final answer as soon as possible.'
|
|
9
|
-
: 'Stop expanding scope and stop optional work. Prefer completing the current objective with the evidence already gathered, and avoid new investigation unless it is strictly required for correctness.';
|
|
5
|
+
const guidance = stage === 'finalize'
|
|
6
|
+
? 'Enter finalization now. Do not start new investigation or new subagents. Complete only the current atomic change, the minimum necessary verification, and the final answer as soon as possible.'
|
|
7
|
+
: 'Stop expanding scope and stop optional work. Prefer completing the current objective with the evidence already gathered, and avoid new investigation unless it is strictly required for correctness.';
|
|
10
8
|
return `<execution_guidance stage="${stage}">\n${privateInstruction}\n\n${guidance}\n</execution_guidance>`;
|
|
11
9
|
}
|
|
@@ -34,6 +34,7 @@ export class ExecutionLoop {
|
|
|
34
34
|
const pendingTools = [];
|
|
35
35
|
let incompleteToolCall = false;
|
|
36
36
|
const round = await input.prepareRound(pendingOutputContinuation);
|
|
37
|
+
input.rolloutBudget?.assertAvailable();
|
|
37
38
|
let sawModelSemanticEvent = false;
|
|
38
39
|
let requestStartedAt;
|
|
39
40
|
let firstModelResponseAt;
|
|
@@ -293,11 +294,6 @@ export class ExecutionLoop {
|
|
|
293
294
|
}
|
|
294
295
|
recoveringFromContextOverflow = false;
|
|
295
296
|
input.observeUsage(roundServerUsage);
|
|
296
|
-
if (pendingTools.length === 0 && input.mailbox.hasPending()) {
|
|
297
|
-
await input.mailbox.append();
|
|
298
|
-
this.finalAnswer = '';
|
|
299
|
-
continue;
|
|
300
|
-
}
|
|
301
297
|
const toolResults = await executeRoundTools(pendingTools, round.toolScope);
|
|
302
298
|
for (const result of toolResults) {
|
|
303
299
|
if ('images' in result && result.images?.length)
|
|
@@ -335,11 +331,12 @@ export class ExecutionLoop {
|
|
|
335
331
|
this.finalAnswer = '';
|
|
336
332
|
}
|
|
337
333
|
input.rolloutBudget?.recordUsage(latestUsage);
|
|
334
|
+
if (!input.signal.aborted)
|
|
335
|
+
input.rolloutBudget?.assertAvailable();
|
|
338
336
|
if (mode !== 'compact' && stop === 'max_output') {
|
|
339
337
|
const needsContinuation = !sawTool || incompleteToolCall;
|
|
340
338
|
if (needsContinuation) {
|
|
341
|
-
if (maxOutputContinuationUsed
|
|
342
|
-
(input.rolloutBudget?.active && input.rolloutBudget.stage === 'exhausted'))
|
|
339
|
+
if (maxOutputContinuationUsed)
|
|
343
340
|
throw new MarAgentError('MAR_AGENT_MODEL_OUTPUT_INCOMPLETE', 'Model output reached its limit before completing.');
|
|
344
341
|
maxOutputContinuationUsed = true;
|
|
345
342
|
pendingOutputContinuation = {
|
|
@@ -358,6 +355,7 @@ export class ExecutionLoop {
|
|
|
358
355
|
const aborted = toolResults.find((result) => result.failed)?.error;
|
|
359
356
|
throw aborted ?? new MarAgentError('MAR_AGENT_EXECUTION_CANCELLED', 'Execution cancelled.');
|
|
360
357
|
}
|
|
358
|
+
input.rolloutBudget?.assertAvailable();
|
|
361
359
|
if (input.mailbox.hasPending()) {
|
|
362
360
|
await input.mailbox.append();
|
|
363
361
|
this.finalAnswer = '';
|
|
@@ -19,6 +19,7 @@ export declare class RolloutBudget {
|
|
|
19
19
|
get stage(): RolloutBudgetStage;
|
|
20
20
|
recordUsage(usage: ModelUsage | undefined): number;
|
|
21
21
|
discard(): void;
|
|
22
|
+
assertAvailable(): void;
|
|
22
23
|
}
|
|
23
24
|
export declare function resolveRolloutBudgetOptions(options: false | RolloutBudgetOptions | undefined): ResolvedRolloutBudgetOptions | undefined;
|
|
24
25
|
export declare function createRolloutBudget(options: false | RolloutBudgetOptions | undefined): RolloutBudget | undefined;
|
|
@@ -48,6 +48,10 @@ export class RolloutBudget {
|
|
|
48
48
|
discard() {
|
|
49
49
|
this.#active = false;
|
|
50
50
|
}
|
|
51
|
+
assertAvailable() {
|
|
52
|
+
if (this.stage === 'exhausted')
|
|
53
|
+
throw new MarAgentError('MAR_AGENT_ROLLOUT_BUDGET_EXCEEDED', 'The shared execution token budget has been exhausted.');
|
|
54
|
+
}
|
|
51
55
|
}
|
|
52
56
|
export function resolveRolloutBudgetOptions(options) {
|
|
53
57
|
if (options === false)
|
|
@@ -234,7 +234,10 @@ export function createSessionOpener(services) {
|
|
|
234
234
|
let recordedEnvironment = restored.environmentState;
|
|
235
235
|
let projectedExecutionBudgetStage;
|
|
236
236
|
const appendExecutionBudgetEnvironment = () => {
|
|
237
|
+
rolloutBudget?.assertAvailable();
|
|
237
238
|
const currentStage = rolloutBudget?.active ? rolloutBudget.stage : 'inactive';
|
|
239
|
+
if (currentStage === 'exhausted')
|
|
240
|
+
return;
|
|
238
241
|
if (currentStage === projectedExecutionBudgetStage)
|
|
239
242
|
return;
|
|
240
243
|
const previousStage = projectedExecutionBudgetStage;
|
|
@@ -420,6 +423,7 @@ export function createSessionOpener(services) {
|
|
|
420
423
|
const catalogContext = {
|
|
421
424
|
sessionId,
|
|
422
425
|
executionId,
|
|
426
|
+
modelId: selectedModel.id,
|
|
423
427
|
...(sessionSource.type === 'subagent'
|
|
424
428
|
? internal.mcpExecutionScopeId === undefined
|
|
425
429
|
? {}
|
|
@@ -479,12 +483,8 @@ export function createSessionOpener(services) {
|
|
|
479
483
|
? selectedModelForTools.codeMode && description.capabilities.includes('code.execute')
|
|
480
484
|
: tool.name === 'question'
|
|
481
485
|
? mode === 'plan'
|
|
482
|
-
: tool.name
|
|
483
|
-
|
|
484
|
-
description.capabilities.includes('web.search')
|
|
485
|
-
: tool.name === 'view_image'
|
|
486
|
-
? selectedModelForTools.inputCapabilities.includes('IMAGE')
|
|
487
|
-
: tool.name !== 'web_fetch' || description.capabilities.includes('web.fetch');
|
|
486
|
+
: tool.name !== 'view_image' ||
|
|
487
|
+
selectedModelForTools.inputCapabilities.includes('IMAGE');
|
|
488
488
|
};
|
|
489
489
|
mcpRegistry.update(mcpCatalog, (tool) => toolEligible(tool.definition));
|
|
490
490
|
rebindMcpToolSearchOutputs(messages, mcpRegistry);
|
|
@@ -806,9 +806,7 @@ export function createSessionOpener(services) {
|
|
|
806
806
|
executionEnvironment = { ...currentEnvironment, ...currentLocalTimeContext() };
|
|
807
807
|
await recordEnvironment();
|
|
808
808
|
}
|
|
809
|
-
const
|
|
810
|
-
(!rolloutBudget?.active || rolloutBudget.stage !== 'exhausted');
|
|
811
|
-
const roundTools = mode === 'compact' ? executionTools : toolsAllowed ? executionTools : [];
|
|
809
|
+
const roundTools = executionTools;
|
|
812
810
|
const codeModeEnabled = roundTools.some((tool) => tool.name === 'code_exec');
|
|
813
811
|
const residentTools = mcpRegistry.projectDirect(baseTools());
|
|
814
812
|
const requestTools = (() => {
|
|
@@ -839,6 +837,7 @@ export function createSessionOpener(services) {
|
|
|
839
837
|
mode,
|
|
840
838
|
sessionId,
|
|
841
839
|
executionId,
|
|
840
|
+
modelId: selectedModel.id,
|
|
842
841
|
signal: controller.signal,
|
|
843
842
|
emit,
|
|
844
843
|
skills: instructionContext?.skills ?? new Map()
|
|
@@ -891,11 +890,7 @@ export function createSessionOpener(services) {
|
|
|
891
890
|
system: systemPrompt,
|
|
892
891
|
messages: modelMessages.map((message) => projectCodeModeSearchResult(message, codeModeEnabled)),
|
|
893
892
|
tools: requestTools,
|
|
894
|
-
...(mode === 'compact'
|
|
895
|
-
? { toolChoice: 'none' }
|
|
896
|
-
: toolsAllowed
|
|
897
|
-
? {}
|
|
898
|
-
: { allowTools: false }),
|
|
893
|
+
...(mode === 'compact' ? { toolChoice: 'none' } : {}),
|
|
899
894
|
promptCacheKey: sessionId,
|
|
900
895
|
reasoningEffort,
|
|
901
896
|
...(requestReasoningEffort === undefined ? {} : { requestReasoningEffort }),
|
|
@@ -11,6 +11,8 @@ import { contextGcBaseFromRecords, contextGcBaseSequence, contextGcMinimumUsageG
|
|
|
11
11
|
import { beginSessionStorageTransaction, isUnjournaledTransactionFile, recoverSessionStorageTransaction } from './transaction-recovery.js';
|
|
12
12
|
const MAX_SEGMENT_BYTES = 16 * 1024 * 1024;
|
|
13
13
|
const REVERSE_READ_CHUNK_BYTES = 64 * 1024;
|
|
14
|
+
const MAX_HISTORY_PAGE_FILES = 32;
|
|
15
|
+
const MAX_HISTORY_PAGE_OFFSETS = 128;
|
|
14
16
|
const MAX_INPUT_IMAGES = 5;
|
|
15
17
|
const MAX_INPUT_IMAGE_BYTES = 5 * 1024 * 1024;
|
|
16
18
|
const MAX_INPUT_IMAGES_BYTES = 10 * 1024 * 1024;
|
|
@@ -161,7 +163,8 @@ export class JsonlSessionStore {
|
|
|
161
163
|
recordWrites: new Map(),
|
|
162
164
|
writerLeases: new Map(),
|
|
163
165
|
segments: new Map(),
|
|
164
|
-
executionModelReads: new Map()
|
|
166
|
+
executionModelReads: new Map(),
|
|
167
|
+
historyPagePositions: new Map()
|
|
165
168
|
};
|
|
166
169
|
}
|
|
167
170
|
/** Merge dirty metadata with disk membership; only createdIds publish locally new entries. */
|
|
@@ -1171,7 +1174,7 @@ export class JsonlSessionStore {
|
|
|
1171
1174
|
if (segment !== undefined && segment.firstSequence >= before)
|
|
1172
1175
|
continue;
|
|
1173
1176
|
const needed = limit - page.length;
|
|
1174
|
-
const window = await readRecordPageFromTail(path, before, needed);
|
|
1177
|
+
const window = await readRecordPageFromTail(path, before, needed, this.#shared.historyPagePositions);
|
|
1175
1178
|
page.unshift(...window.records);
|
|
1176
1179
|
if (window.hasEarlier) {
|
|
1177
1180
|
hasEarlier = true;
|
|
@@ -2338,9 +2341,9 @@ async function repairSegmentTail(path) {
|
|
|
2338
2341
|
await handle.close();
|
|
2339
2342
|
}
|
|
2340
2343
|
}
|
|
2341
|
-
async function readRecordPageFromTail(path, beforeSequence, limit) {
|
|
2344
|
+
async function readRecordPageFromTail(path, beforeSequence, limit, positions) {
|
|
2342
2345
|
const newestFirst = [];
|
|
2343
|
-
for await (const record of readRecordsFromTail(path)) {
|
|
2346
|
+
for await (const record of readRecordsFromTail(path, { beforeSequence, limit, positions })) {
|
|
2344
2347
|
if (!(record.sequence < beforeSequence))
|
|
2345
2348
|
continue;
|
|
2346
2349
|
newestFirst.push(record);
|
|
@@ -2353,14 +2356,42 @@ async function readRecordPageFromTail(path, beforeSequence, limit) {
|
|
|
2353
2356
|
};
|
|
2354
2357
|
}
|
|
2355
2358
|
/** Decode complete records newest-first, releasing the file as soon as a reader stops. */
|
|
2356
|
-
async function* readRecordsFromTail(path) {
|
|
2359
|
+
async function* readRecordsFromTail(path, page) {
|
|
2357
2360
|
const handle = await open(path, 'r');
|
|
2358
2361
|
try {
|
|
2359
|
-
const
|
|
2362
|
+
const metadata = await handle.stat({ bigint: true });
|
|
2363
|
+
const size = Number(metadata.size);
|
|
2360
2364
|
let position = size;
|
|
2365
|
+
let cached;
|
|
2366
|
+
if (page !== undefined) {
|
|
2367
|
+
const signature = `${metadata.dev}:${metadata.ino}:${metadata.size}:${metadata.mtimeNs}:${metadata.ctimeNs}`;
|
|
2368
|
+
const previous = page.positions.get(path);
|
|
2369
|
+
cached = previous?.signature === signature ? previous : { signature, offsets: new Map() };
|
|
2370
|
+
page.positions.delete(path);
|
|
2371
|
+
page.positions.set(path, cached);
|
|
2372
|
+
if (page.positions.size > MAX_HISTORY_PAGE_FILES)
|
|
2373
|
+
page.positions.delete(page.positions.keys().next().value);
|
|
2374
|
+
let nearestSequence = Number.POSITIVE_INFINITY;
|
|
2375
|
+
for (const [sequence, offset] of cached.offsets) {
|
|
2376
|
+
if (sequence >= page.beforeSequence && sequence < nearestSequence) {
|
|
2377
|
+
nearestSequence = sequence;
|
|
2378
|
+
position = offset;
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
let accepted = 0;
|
|
2383
|
+
const remember = (record, offset) => {
|
|
2384
|
+
if (page === undefined || cached === undefined || record.sequence >= page.beforeSequence)
|
|
2385
|
+
return;
|
|
2386
|
+
if (++accepted !== page.limit)
|
|
2387
|
+
return;
|
|
2388
|
+
cached.offsets.set(record.sequence, offset);
|
|
2389
|
+
if (cached.offsets.size > MAX_HISTORY_PAGE_OFFSETS)
|
|
2390
|
+
cached.offsets.delete(cached.offsets.keys().next().value);
|
|
2391
|
+
};
|
|
2361
2392
|
let pendingLineParts = [];
|
|
2362
2393
|
let invalidFinalLineMayBeIgnored = false;
|
|
2363
|
-
let inspectedFileEnd =
|
|
2394
|
+
let inspectedFileEnd = position !== size;
|
|
2364
2395
|
const decode = (line, allowInvalidFinalLine) => {
|
|
2365
2396
|
if (line.length === 0)
|
|
2366
2397
|
return undefined;
|
|
@@ -2402,8 +2433,10 @@ async function* readRecordsFromTail(path) {
|
|
|
2402
2433
|
invalidFinalLineMayBeIgnored = false;
|
|
2403
2434
|
pendingLineParts = [];
|
|
2404
2435
|
lineEnd = index;
|
|
2405
|
-
if (record !== undefined)
|
|
2436
|
+
if (record !== undefined) {
|
|
2437
|
+
remember(record, start + index + 1);
|
|
2406
2438
|
yield record;
|
|
2439
|
+
}
|
|
2407
2440
|
}
|
|
2408
2441
|
if (lineEnd > 0)
|
|
2409
2442
|
pendingLineParts.unshift(Buffer.from(value.subarray(0, lineEnd)));
|
|
@@ -2411,8 +2444,10 @@ async function* readRecordsFromTail(path) {
|
|
|
2411
2444
|
}
|
|
2412
2445
|
if (pendingLineParts.length > 0) {
|
|
2413
2446
|
const record = decode(Buffer.concat(pendingLineParts), invalidFinalLineMayBeIgnored);
|
|
2414
|
-
if (record !== undefined)
|
|
2447
|
+
if (record !== undefined) {
|
|
2448
|
+
remember(record, 0);
|
|
2415
2449
|
yield record;
|
|
2450
|
+
}
|
|
2416
2451
|
}
|
|
2417
2452
|
}
|
|
2418
2453
|
finally {
|
|
@@ -25,6 +25,8 @@ export interface ToolExecutionContext {
|
|
|
25
25
|
signal: AbortSignal;
|
|
26
26
|
sessionId: string;
|
|
27
27
|
executionId: string;
|
|
28
|
+
/** Current model identity for neutral Host-owned tool policies. */
|
|
29
|
+
modelId?: string;
|
|
28
30
|
recentImages?: readonly {
|
|
29
31
|
readonly mimeType: string;
|
|
30
32
|
readonly data: Buffer;
|
|
@@ -39,7 +41,7 @@ export interface ToolExecutionContext {
|
|
|
39
41
|
invoke(call: ToolCall, signal: AbortSignal): Promise<ToolExecutionResult>;
|
|
40
42
|
};
|
|
41
43
|
}
|
|
42
|
-
export interface McpToolCatalogContext extends Pick<ToolExecutionContext, 'sessionId' | 'executionId'> {
|
|
44
|
+
export interface McpToolCatalogContext extends Pick<ToolExecutionContext, 'sessionId' | 'executionId' | 'modelId'> {
|
|
43
45
|
executionScopeId?: string;
|
|
44
46
|
claimExecutionScope?: boolean;
|
|
45
47
|
}
|
|
@@ -4,13 +4,7 @@ import type { ToolCall, ToolExecutionContext, ToolExecutionResult } from '../con
|
|
|
4
4
|
import { type WorkspacePathMount } from '../shared/path-resolver.js';
|
|
5
5
|
export declare class LocalToolRegistry {
|
|
6
6
|
#private;
|
|
7
|
-
constructor(workspace: string, environment?: NodeJS.ProcessEnv, shell?: string,
|
|
8
|
-
webSearch?: (input: {
|
|
9
|
-
query: string;
|
|
10
|
-
maxResults: number;
|
|
11
|
-
}, signal: AbortSignal) => Promise<ToolExecutionResult>;
|
|
12
|
-
webFetch?: boolean;
|
|
13
|
-
allowUntrustedTls?: boolean;
|
|
7
|
+
constructor(workspace: string, environment?: NodeJS.ProcessEnv, shell?: string, options?: {
|
|
14
8
|
processExecutor?: LocalProcessExecutor;
|
|
15
9
|
codeModeEngineFactory?: CodeModeEngineFactory;
|
|
16
10
|
restrictPaths?: boolean;
|
|
@@ -3,25 +3,25 @@ import { ApplyPatchTool, applyPatchToolInput } from './apply-patch.js';
|
|
|
3
3
|
import { CodeModeTool } from '../code-mode/index.js';
|
|
4
4
|
import { ExecTool, execToolInput } from './exec.js';
|
|
5
5
|
import { ReadTool, formatBatchReadOutput } from './read.js';
|
|
6
|
-
import { executeWebFetch } from './web-fetch.js';
|
|
7
|
-
import { executeWebSearch } from './web-search.js';
|
|
8
6
|
import { WorkspacePathResolver } from '../shared/path-resolver.js';
|
|
9
7
|
import { ViewImageTool } from './view-image.js';
|
|
10
8
|
export class LocalToolRegistry {
|
|
11
9
|
#exec;
|
|
12
10
|
#code;
|
|
13
11
|
#handlers;
|
|
14
|
-
constructor(workspace, environment = process.env, shell,
|
|
12
|
+
constructor(workspace, environment = process.env, shell, options = {}) {
|
|
15
13
|
const paths = new WorkspacePathResolver(workspace, {
|
|
16
|
-
...(
|
|
17
|
-
...(
|
|
18
|
-
|
|
14
|
+
...(options.restrictPaths === undefined ? {} : { restrictPaths: options.restrictPaths }),
|
|
15
|
+
...(options.workspaceWritable === undefined
|
|
16
|
+
? {}
|
|
17
|
+
: { rootWritable: options.workspaceWritable }),
|
|
18
|
+
...(options.pathMounts === undefined ? {} : { mounts: options.pathMounts })
|
|
19
19
|
});
|
|
20
20
|
const read = new ReadTool(paths);
|
|
21
21
|
const viewImage = new ViewImageTool(paths);
|
|
22
22
|
const patch = new ApplyPatchTool(paths);
|
|
23
|
-
this.#exec = new ExecTool(paths, undefined, undefined, environment, shell,
|
|
24
|
-
this.#code = new CodeModeTool(paths, environment,
|
|
23
|
+
this.#exec = new ExecTool(paths, undefined, undefined, environment, shell, options.processExecutor);
|
|
24
|
+
this.#code = new CodeModeTool(paths, environment, options.processExecutor, options.codeModeEngineFactory);
|
|
25
25
|
this.#handlers = new Map([
|
|
26
26
|
['code_exec', (arguments_, context) => this.#code.start(arguments_, context)],
|
|
27
27
|
['code_exec_wait', (arguments_, context) => this.#code.wait(arguments_, context)],
|
|
@@ -57,27 +57,7 @@ export class LocalToolRegistry {
|
|
|
57
57
|
});
|
|
58
58
|
return { content: JSON.stringify(result), data: result, truncated: result.truncated };
|
|
59
59
|
}
|
|
60
|
-
])
|
|
61
|
-
...(web.webSearch
|
|
62
|
-
? [
|
|
63
|
-
[
|
|
64
|
-
'web_search',
|
|
65
|
-
async (arguments_, context) => {
|
|
66
|
-
return executeWebSearch(arguments_, context.signal, (input, signal) => web.webSearch(input, signal));
|
|
67
|
-
}
|
|
68
|
-
]
|
|
69
|
-
]
|
|
70
|
-
: []),
|
|
71
|
-
...(web.webFetch === false
|
|
72
|
-
? []
|
|
73
|
-
: [
|
|
74
|
-
[
|
|
75
|
-
'web_fetch',
|
|
76
|
-
async (arguments_, context) => {
|
|
77
|
-
return executeWebFetch(arguments_, context.signal, web.allowUntrustedTls === true);
|
|
78
|
-
}
|
|
79
|
-
]
|
|
80
|
-
])
|
|
60
|
+
])
|
|
81
61
|
]);
|
|
82
62
|
}
|
|
83
63
|
execute(call, context) {
|
|
@@ -12,8 +12,6 @@ import { executeTodoRead, todoReadToolDefinition } from './agent/todo-read.js';
|
|
|
12
12
|
import { executeTodoWrite, todoWriteToolDefinition } from './agent/todo-write.js';
|
|
13
13
|
import { executeToolSearch, toolSearchToolDefinition } from './mcp/tool-search.js';
|
|
14
14
|
import { viewImageToolDefinition } from './local/view-image.js';
|
|
15
|
-
import { webFetchToolDefinition } from './local/web-fetch.js';
|
|
16
|
-
import { webSearchToolDefinition } from './local/web-search.js';
|
|
17
15
|
export const BUILTIN_TOOL_DEFINITIONS = [
|
|
18
16
|
skillToolDefinition,
|
|
19
17
|
toolSearchToolDefinition,
|
|
@@ -30,9 +28,7 @@ export const BUILTIN_TOOL_DEFINITIONS = [
|
|
|
30
28
|
agentStartToolDefinition,
|
|
31
29
|
agentWaitToolDefinition,
|
|
32
30
|
agentMessageToolDefinition,
|
|
33
|
-
agentCancelToolDefinition
|
|
34
|
-
webSearchToolDefinition,
|
|
35
|
-
webFetchToolDefinition
|
|
31
|
+
agentCancelToolDefinition
|
|
36
32
|
];
|
|
37
33
|
export const AGENT_TOOL_HANDLERS = new Map([
|
|
38
34
|
['skill', executeSkill],
|
|
@@ -44,6 +44,7 @@ export interface ToolRuntimeContext {
|
|
|
44
44
|
mode: ExecutionMode;
|
|
45
45
|
sessionId: string;
|
|
46
46
|
executionId: string;
|
|
47
|
+
modelId?: string;
|
|
47
48
|
signal: AbortSignal;
|
|
48
49
|
codeMode?: import('./contracts.js').ToolExecutionContext['codeMode'];
|
|
49
50
|
skills: ReadonlyMap<string, AgentSkill>;
|
|
@@ -20,6 +20,7 @@ export async function executeRegisteredTool(name, arguments_, callId, context) {
|
|
|
20
20
|
signal: context.signal,
|
|
21
21
|
sessionId: context.sessionId,
|
|
22
22
|
executionId: context.executionId,
|
|
23
|
+
...(context.modelId === undefined ? {} : { modelId: context.modelId }),
|
|
23
24
|
...(context.recentImages === undefined ? {} : { recentImages: context.recentImages }),
|
|
24
25
|
...(context.codeMode === undefined ? {} : { codeMode: context.codeMode })
|
|
25
26
|
});
|
package/package.json
CHANGED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { ToolExecutionResult } from '../contracts.js';
|
|
2
|
-
import type { ClientToolDefinition } from '../../model/contracts.js';
|
|
3
|
-
export declare const webFetchToolDefinition: ClientToolDefinition;
|
|
4
|
-
export declare function executeWebFetch(input: unknown, signal: AbortSignal, allowUntrustedTls?: boolean): Promise<ToolExecutionResult>;
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { request as httpRequest } from 'node:http';
|
|
2
|
-
import { request as httpsRequest } from 'node:https';
|
|
3
|
-
import { MarAgentError } from '../../error.js';
|
|
4
|
-
export const webFetchToolDefinition = {
|
|
5
|
-
name: 'web_fetch',
|
|
6
|
-
allowedCallers: ['direct', 'programmatic'],
|
|
7
|
-
parallelSafety: 'safe',
|
|
8
|
-
description: 'Fetch a specific HTTP or HTTPS URL, including explicitly permitted intranet URLs.',
|
|
9
|
-
inputSchema: {
|
|
10
|
-
type: 'object',
|
|
11
|
-
properties: { url: { type: 'string' } },
|
|
12
|
-
required: ['url'],
|
|
13
|
-
additionalProperties: false
|
|
14
|
-
}
|
|
15
|
-
};
|
|
16
|
-
export async function executeWebFetch(input, signal, allowUntrustedTls = false) {
|
|
17
|
-
const value = input;
|
|
18
|
-
if (typeof value?.url !== 'string')
|
|
19
|
-
throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'URL is required.');
|
|
20
|
-
let url;
|
|
21
|
-
try {
|
|
22
|
-
url = new URL(value.url);
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'URL is invalid.');
|
|
26
|
-
}
|
|
27
|
-
if (url.protocol !== 'http:' && url.protocol !== 'https:')
|
|
28
|
-
throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Only HTTP and HTTPS URLs are supported.');
|
|
29
|
-
return fetchWebUrl(url, signal, allowUntrustedTls);
|
|
30
|
-
}
|
|
31
|
-
async function fetchWebUrl(url, signal, allowUntrustedTls, redirects = 0) {
|
|
32
|
-
if (redirects > 5)
|
|
33
|
-
throw new MarAgentError('MAR_AGENT_WEB_FETCH_FAILED', 'Too many redirects.');
|
|
34
|
-
return new Promise((resolve, reject) => {
|
|
35
|
-
const request = (url.protocol === 'https:' ? httpsRequest : httpRequest)(url, {
|
|
36
|
-
signal,
|
|
37
|
-
headers: {
|
|
38
|
-
accept: 'text/html,application/json,text/plain;q=0.9,*/*;q=0.1',
|
|
39
|
-
'accept-encoding': 'identity',
|
|
40
|
-
'user-agent': 'MyAgentRoam/0.9'
|
|
41
|
-
},
|
|
42
|
-
...(url.protocol === 'https:' ? { rejectUnauthorized: !allowUntrustedTls } : {})
|
|
43
|
-
}, (response) => {
|
|
44
|
-
const status = response.statusCode ?? 0;
|
|
45
|
-
if (status >= 300 && status < 400 && response.headers.location) {
|
|
46
|
-
response.resume();
|
|
47
|
-
void fetchWebUrl(new URL(response.headers.location, url), signal, allowUntrustedTls, redirects + 1).then(resolve, reject);
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
if (status < 200 || status >= 300) {
|
|
51
|
-
response.resume();
|
|
52
|
-
reject(new MarAgentError('MAR_AGENT_WEB_FETCH_FAILED', `HTTP ${status}.`));
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
const contentType = String(response.headers['content-type'] ?? 'text/plain').toLowerCase();
|
|
56
|
-
if (!/^(text\/|application\/(json|xml|xhtml\+xml))/u.test(contentType)) {
|
|
57
|
-
response.resume();
|
|
58
|
-
reject(new MarAgentError('MAR_AGENT_WEB_FETCH_UNSUPPORTED_CONTENT', 'Response is not readable text.'));
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
const chunks = [];
|
|
62
|
-
let bytes = 0;
|
|
63
|
-
let truncated = false;
|
|
64
|
-
response.on('data', (chunk) => {
|
|
65
|
-
if (bytes >= 1_000_000) {
|
|
66
|
-
truncated = true;
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
const remaining = 1_000_000 - bytes;
|
|
70
|
-
chunks.push(chunk.subarray(0, remaining));
|
|
71
|
-
bytes += Math.min(chunk.length, remaining);
|
|
72
|
-
if (chunk.length > remaining)
|
|
73
|
-
truncated = true;
|
|
74
|
-
});
|
|
75
|
-
response.on('end', () => resolve({
|
|
76
|
-
content: Buffer.concat(chunks).toString('utf8'),
|
|
77
|
-
data: { url: url.href, status },
|
|
78
|
-
truncated
|
|
79
|
-
}));
|
|
80
|
-
response.on('error', reject);
|
|
81
|
-
});
|
|
82
|
-
request.on('error', reject);
|
|
83
|
-
request.end();
|
|
84
|
-
});
|
|
85
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { ClientToolDefinition } from '../../model/contracts.js';
|
|
2
|
-
import type { ToolExecutionResult } from '../contracts.js';
|
|
3
|
-
export declare const webSearchToolDefinition: ClientToolDefinition;
|
|
4
|
-
export declare function executeWebSearch(input: unknown, signal: AbortSignal, search: (input: {
|
|
5
|
-
query: string;
|
|
6
|
-
maxResults: number;
|
|
7
|
-
}, signal: AbortSignal) => Promise<ToolExecutionResult>): Promise<ToolExecutionResult>;
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { MarAgentError } from '../../error.js';
|
|
2
|
-
export const webSearchToolDefinition = {
|
|
3
|
-
name: 'web_search',
|
|
4
|
-
allowedCallers: ['direct', 'programmatic'],
|
|
5
|
-
parallelSafety: 'safe',
|
|
6
|
-
description: 'Search the web for current information. Use web_fetch to open a specific result.',
|
|
7
|
-
inputSchema: {
|
|
8
|
-
type: 'object',
|
|
9
|
-
properties: {
|
|
10
|
-
query: { type: 'string', description: 'Search query.' },
|
|
11
|
-
maxResults: { type: 'integer', minimum: 1, maximum: 10, default: 8 }
|
|
12
|
-
},
|
|
13
|
-
required: ['query'],
|
|
14
|
-
additionalProperties: false
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
export async function executeWebSearch(input, signal, search) {
|
|
18
|
-
const value = input;
|
|
19
|
-
if (typeof value?.query !== 'string' || value.query.trim().length === 0)
|
|
20
|
-
throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Search query is required.');
|
|
21
|
-
try {
|
|
22
|
-
return await search({
|
|
23
|
-
query: value.query.trim(),
|
|
24
|
-
maxResults: typeof value.maxResults === 'number'
|
|
25
|
-
? Math.min(10, Math.max(1, Math.trunc(value.maxResults)))
|
|
26
|
-
: 8
|
|
27
|
-
}, signal);
|
|
28
|
-
}
|
|
29
|
-
catch (error) {
|
|
30
|
-
if (error instanceof MarAgentError)
|
|
31
|
-
throw error;
|
|
32
|
-
throw new MarAgentError('MAR_AGENT_WEB_SEARCH_FAILED', 'Web search failed.', { cause: error });
|
|
33
|
-
}
|
|
34
|
-
}
|