@myagentroam/agent 0.9.138 → 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/host/local-agent-host.d.ts +3 -1
- package/dist/host/local-agent-host.js +29 -3
- package/dist/prompts/resources.js +4 -2
- package/dist/prompts/workflow.js +2 -0
- package/dist/runtime/context-gc.js +25 -8
- package/dist/sdk/agent.js +40 -38
- package/dist/session/context-gc-checkpoint.d.ts +1 -1
- package/dist/session/context-gc-checkpoint.js +8 -5
- package/dist/session/context-gc-replacements.d.ts +1 -1
- package/dist/session/context-gc-replacements.js +5 -3
- package/dist/tools/agent-wait.js +1 -1
- package/dist/tools/apply-patch.js +1 -1
- package/dist/tools/callers.js +1 -1
- package/dist/tools/code-mode/cells.d.ts +7 -1
- package/dist/tools/code-mode/cells.js +32 -8
- package/dist/tools/code-mode/engine.d.ts +5 -2
- package/dist/tools/code-mode/quickjs-engine.js +106 -18
- package/dist/tools/code-mode/text-output.d.ts +21 -0
- package/dist/tools/code-mode/text-output.js +75 -0
- package/dist/tools/code-mode/worker.js +1 -1
- package/dist/tools/code-mode.js +36 -18
- package/dist/tools/exec.d.ts +3 -0
- package/dist/tools/exec.js +56 -15
- package/dist/tools/execution-limits.d.ts +4 -1
- package/dist/tools/execution-limits.js +4 -1
- package/dist/tools/local-registry.js +5 -5
- package/dist/tools/registry.js +2 -1
- package/dist/tools/skill.js +1 -1
- package/dist/tools/tool-search.js +5 -3
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CodeModeEngineFactory } from '../tools/code-mode/engine.js';
|
|
2
2
|
import type { HostDescription } from './contracts.js';
|
|
3
|
-
import type
|
|
3
|
+
import { type LocalProcessExecutor } from './local-process-executor.js';
|
|
4
4
|
import type { WorkspacePathMount } from '../tools/shared/path-resolver.js';
|
|
5
5
|
import type { MarAgentEvent, MarAgentHost, HostExecutionExtensions, McpToolCatalogContext, McpToolCatalog, McpToolCallContext, ToolCall, ToolExecutionContext, ToolExecutionResult } from '../sdk/types.js';
|
|
6
6
|
export interface LocalAgentMcpBinding {
|
|
@@ -13,6 +13,8 @@ export interface LocalAgentExecutionMcpBinding extends LocalAgentMcpBinding {
|
|
|
13
13
|
readonly extensions?: LocalAgentExecutionExtensions;
|
|
14
14
|
}
|
|
15
15
|
export interface LocalAgentExecutionExtensions extends HostExecutionExtensions {
|
|
16
|
+
/** Host-private process environment, snapshotted when bound to an execution. */
|
|
17
|
+
readonly environment?: Readonly<Record<string, string>>;
|
|
16
18
|
executeTool?(call: ToolCall, context: ToolExecutionContext): Promise<ToolExecutionResult>;
|
|
17
19
|
}
|
|
18
20
|
export declare class LocalAgentHost implements MarAgentHost {
|
|
@@ -2,6 +2,7 @@ import { existsSync } from 'node:fs';
|
|
|
2
2
|
import { createHash, randomUUID } from 'node:crypto';
|
|
3
3
|
import { mkdir } from 'node:fs/promises';
|
|
4
4
|
import { basename, delimiter, isAbsolute, join, resolve, win32 } from 'node:path';
|
|
5
|
+
import { directLocalProcessExecutor } from './local-process-executor.js';
|
|
5
6
|
import { MAR_AGENT_HOST_API_VERSION } from './contracts.js';
|
|
6
7
|
import { MarAgentError } from '../error.js';
|
|
7
8
|
import { LocalToolRegistry } from '../tools/local-registry.js';
|
|
@@ -36,6 +37,7 @@ export class LocalAgentHost {
|
|
|
36
37
|
this.#hasWebSearch = input.webSearch !== undefined;
|
|
37
38
|
this.#hasWebFetch = input.webFetch !== false;
|
|
38
39
|
this.#resourceIdleTtlMs = input.resourceIdleTtlMs;
|
|
40
|
+
const processExecutor = input.processExecutor ?? directLocalProcessExecutor;
|
|
39
41
|
this.#tools = new LocalToolRegistry(this.#workspace, this.#environment, this.#shell.path, {
|
|
40
42
|
...(input.webSearch ? { webSearch: input.webSearch } : {}),
|
|
41
43
|
webFetch: this.#hasWebFetch,
|
|
@@ -44,7 +46,25 @@ export class LocalAgentHost {
|
|
|
44
46
|
...(input.codeModeEngineFactory === undefined
|
|
45
47
|
? {}
|
|
46
48
|
: { codeModeEngineFactory: input.codeModeEngineFactory }),
|
|
47
|
-
|
|
49
|
+
processExecutor: {
|
|
50
|
+
spawn: (request) => {
|
|
51
|
+
const binding = request.owner && this.#executionMcp.get(request.owner.executionId);
|
|
52
|
+
const environment = binding?.active ? binding.extensions?.environment : undefined;
|
|
53
|
+
return processExecutor.spawn(environment === undefined
|
|
54
|
+
? request
|
|
55
|
+
: {
|
|
56
|
+
...request,
|
|
57
|
+
environment: { ...request.environment, ...environment },
|
|
58
|
+
...(request.environmentKeys === undefined
|
|
59
|
+
? {}
|
|
60
|
+
: {
|
|
61
|
+
environmentKeys: [
|
|
62
|
+
...new Set([...request.environmentKeys, ...Object.keys(environment)])
|
|
63
|
+
]
|
|
64
|
+
})
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
},
|
|
48
68
|
...(input.restrictPaths === undefined ? {} : { restrictPaths: input.restrictPaths }),
|
|
49
69
|
...(input.workspaceWritable === undefined
|
|
50
70
|
? {}
|
|
@@ -108,7 +128,12 @@ export class LocalAgentHost {
|
|
|
108
128
|
(async () => {
|
|
109
129
|
throw new MarAgentError('MAR_AGENT_MCP_FAILED', 'MCP execution is unavailable.');
|
|
110
130
|
}),
|
|
111
|
-
extensions
|
|
131
|
+
extensions: {
|
|
132
|
+
...extensions,
|
|
133
|
+
...(extensions.environment === undefined
|
|
134
|
+
? {}
|
|
135
|
+
: { environment: { ...extensions.environment } })
|
|
136
|
+
},
|
|
112
137
|
active: true
|
|
113
138
|
};
|
|
114
139
|
this.#currentExecutionMcp = binding;
|
|
@@ -116,7 +141,8 @@ export class LocalAgentHost {
|
|
|
116
141
|
}
|
|
117
142
|
async listExecutionExtensions(context) {
|
|
118
143
|
const binding = this.#executionBinding(context);
|
|
119
|
-
|
|
144
|
+
const { environment: _environment, ...extensions } = binding?.extensions ?? this.#extensions ?? {};
|
|
145
|
+
return extensions;
|
|
120
146
|
}
|
|
121
147
|
emit(event) {
|
|
122
148
|
this.events.push(event);
|
|
@@ -12,7 +12,9 @@ export function retainedSessionResourcesSnapshot(input) {
|
|
|
12
12
|
]
|
|
13
13
|
: []),
|
|
14
14
|
'These resources belong to this Session and survived the previous Execution. Reuse their stable IDs instead of starting duplicate processes or subagents.',
|
|
15
|
-
processes.length === 0
|
|
15
|
+
processes.length === 0
|
|
16
|
+
? ''
|
|
17
|
+
: 'Use exec_wait with processId to wait, write stdin or cancel; in Code Mode use tools.exec_wait. Cancelling a wait leaves the process running; explicit cancel releases the process resource.',
|
|
16
18
|
subagents.length === 0
|
|
17
19
|
? ''
|
|
18
20
|
: 'agent_cancel stops only the current child turn and retains its Session for later messages or explicit deletion.',
|
|
@@ -40,7 +42,7 @@ export function retainedSessionResourcesUpdate(snapshot, previousSnapshot, previ
|
|
|
40
42
|
return undefined;
|
|
41
43
|
if (!snapshot)
|
|
42
44
|
return previousSnapshot || previousMayContainResources
|
|
43
|
-
? 'The previously provided retained Session resources no longer apply. No retained processes, code cells or subagents remain.'
|
|
45
|
+
? 'The previously provided retained Session resources no longer apply. No retained processes, code cells or subagents remain. Historical resource IDs are not live handles; do not poll or wait on them. Use already recorded results or saved artifacts to recover completed work before starting anything again.'
|
|
44
46
|
: undefined;
|
|
45
47
|
return previousSnapshot || previousMayContainResources
|
|
46
48
|
? `This replaces all previously provided retained Session resources.\n\n${snapshot}`
|
package/dist/prompts/workflow.js
CHANGED
|
@@ -16,6 +16,8 @@ export function toolUsagePrompt(tools, directTools = tools) {
|
|
|
16
16
|
clauses.push('If the request names or clearly matches an available Skill description, call skill before generic tools and follow the loaded workflow; semantic matches do not require the user to know the Skill name. Do not load unrelated Skills or claim to use one without calling skill.');
|
|
17
17
|
if (names.has('exec'))
|
|
18
18
|
clauses.push('Use exec for platform-native commands, scripts, verification, and mechanical edits. Prefer rg or rg --files for search. Batch independent lookups and already-decided commands, preserving failure ordering. Use scripts for mechanical substitutions instead of reproducing long unchanged text in a patch; inspect actual effects afterward.');
|
|
19
|
+
if (names.has('exec_wait'))
|
|
20
|
+
clauses.push('Use exec_wait with processId for subsequent process waits, stdin writes or cancellation; in Code Mode use tools.exec_wait. exec only starts commands.');
|
|
19
21
|
if (names.has('read'))
|
|
20
22
|
clauses.push('Use read to batch known files and ranges. Batch only already-located, bounded ranges that are likely to fit together; for large files or documents, locate relevant sections before reading them. Read only what can affect the next decision, continue only relevant incomplete results using returned metadata, and avoid rereading unchanged content. Do not edit from an incomplete oversized-line preview. Obtain exact source with exec; if it still cannot fit, use a preconditioned deterministic replacement and verify the result.');
|
|
21
23
|
if (names.has('apply_patch'))
|
|
@@ -690,7 +690,7 @@ function collectExecCandidates(input, groups, existingTargets, usageRatio) {
|
|
|
690
690
|
let protectedTokens = 0;
|
|
691
691
|
let retryAfterRollouts = Infinity;
|
|
692
692
|
const execGroups = [...groups.values()]
|
|
693
|
-
.filter((group) => group.toolName === 'exec')
|
|
693
|
+
.filter((group) => group.toolName === 'exec' || group.toolName === 'exec_wait')
|
|
694
694
|
.sort((left, right) => (left.call?.record.sequence ?? Number.MAX_SAFE_INTEGER) -
|
|
695
695
|
(right.call?.record.sequence ?? Number.MAX_SAFE_INTEGER));
|
|
696
696
|
const invocations = new Map();
|
|
@@ -713,15 +713,32 @@ function collectExecCandidates(input, groups, existingTargets, usageRatio) {
|
|
|
713
713
|
}
|
|
714
714
|
for (const startGroup of execGroups) {
|
|
715
715
|
const start = invocations.get(startGroup.callId);
|
|
716
|
-
if (!start
|
|
717
|
-
(start.input.action !== 'start' &&
|
|
718
|
-
!(start.input.action == null && typeof start.input.command === 'string')))
|
|
716
|
+
if (!start)
|
|
719
717
|
continue;
|
|
718
|
+
const isStart = start.group.toolName === 'exec' &&
|
|
719
|
+
(start.input.action === 'start' ||
|
|
720
|
+
(start.input.action == null && typeof start.input.command === 'string'));
|
|
721
|
+
const processId = typeof start.input.processId === 'string'
|
|
722
|
+
? start.input.processId
|
|
723
|
+
: typeof start.result.processId === 'string'
|
|
724
|
+
? start.result.processId
|
|
725
|
+
: undefined;
|
|
726
|
+
if (!isStart) {
|
|
727
|
+
if (start.group.toolName !== 'exec_wait' || !processId)
|
|
728
|
+
continue;
|
|
729
|
+
// A process may start inside a cell or before the visible history boundary.
|
|
730
|
+
// Begin with its first visible wait, but never split an unfinished lifecycle.
|
|
731
|
+
const previous = execGroups.findLast((group) => (group.call?.record.sequence ?? Infinity) < start.group.call.record.sequence &&
|
|
732
|
+
((isRecord(group.call?.message.arguments) &&
|
|
733
|
+
group.call.message.arguments.processId === processId) ||
|
|
734
|
+
invocations.get(group.callId)?.result.processId === processId));
|
|
735
|
+
const previousStatus = previous && invocations.get(previous.callId)?.result.status;
|
|
736
|
+
if (previous && previousStatus !== 'completed' && previousStatus !== 'cancelled')
|
|
737
|
+
continue;
|
|
738
|
+
}
|
|
720
739
|
const lifecycle = [start];
|
|
721
|
-
let processId;
|
|
722
740
|
let terminal = start.result.status !== 'running';
|
|
723
741
|
if (!terminal) {
|
|
724
|
-
processId = typeof start.result.processId === 'string' ? start.result.processId : undefined;
|
|
725
742
|
if (!processId)
|
|
726
743
|
continue;
|
|
727
744
|
const relatedGroups = execGroups.filter((group) => (group.call?.record.sequence ?? 0) > start.group.call.record.sequence &&
|
|
@@ -765,10 +782,10 @@ function collectExecCandidates(input, groups, existingTargets, usageRatio) {
|
|
|
765
782
|
if (!existingTargets.has(contextGcTargetKey(callTarget)))
|
|
766
783
|
replacements.push({
|
|
767
784
|
kind: 'TOOL_CALL_INPUT',
|
|
768
|
-
toolName:
|
|
785
|
+
toolName: invocation.group.toolName,
|
|
769
786
|
callId: invocation.group.callId,
|
|
770
787
|
target: callTarget,
|
|
771
|
-
replacementArguments: contextGcExecInputReplacement(invocation.input),
|
|
788
|
+
replacementArguments: contextGcExecInputReplacement(invocation.input, 3, invocation.group.toolName),
|
|
772
789
|
providerAliasTargets: invocation.group.aliases
|
|
773
790
|
.map(({ record, message }) => targetFor(record, message))
|
|
774
791
|
.filter((target) => target !== undefined),
|
package/dist/sdk/agent.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import { isDeepStrictEqual } from 'node:util';
|
|
3
3
|
import { describeMarAgentError, MarAgentError } from '../error.js';
|
|
4
|
+
import { CodeCellExecutionError } from '../tools/code-mode/cells.js';
|
|
4
5
|
import { negotiateHost } from '../host/contracts.js';
|
|
5
6
|
import { AnthropicMessagesAdapter, isAnthropicRecoverableStreamError } from '../model/anthropic-messages.js';
|
|
6
7
|
import { marAgentReasoningEffortSchema, resolveModelDefaultReasoningEffort, usesResponsesConfigurationUpdates } from '../model/configuration.js';
|
|
@@ -37,7 +38,6 @@ import { createSessionToolScheduler, scheduleToolCallBatch } from '../tools/sche
|
|
|
37
38
|
import { projectCodeModeSearchResult, projectCodeModeTools } from '../tools/code-mode.js';
|
|
38
39
|
import { allowsDirectTool, allowsToolCaller, toolCallers } from '../tools/callers.js';
|
|
39
40
|
import { SubagentSessionController } from '../subagent/session-controller.js';
|
|
40
|
-
const MODEL_PERFORMANCE_MIN_INTERVAL_MS = 1_000;
|
|
41
41
|
/** Retry only classified recoverable failures within the attempt budget; never retry cancellation. */
|
|
42
42
|
async function recoverModelStream(error, retryState, signal) {
|
|
43
43
|
if (signal.aborted ||
|
|
@@ -612,7 +612,14 @@ export async function createMarAgentWithAdapter(options) {
|
|
|
612
612
|
instructionContent: renderAgentInstructions(instructionEntries, compactMayContainAgentInstructions ||
|
|
613
613
|
contextualWorldState.instructionSnapshot !== undefined),
|
|
614
614
|
resourceSnapshot: currentResourceSnapshot,
|
|
615
|
-
previousMayContainResources: compactMayContainRetainedResources
|
|
615
|
+
previousMayContainResources: (!contextualWorldState.initialized && compactMayContainRetainedResources) ||
|
|
616
|
+
(options.host.listSessionResources !== undefined &&
|
|
617
|
+
currentResourceSnapshot === undefined &&
|
|
618
|
+
messages
|
|
619
|
+
.slice(contextualWorldState.entries.findLast((entry) => entry.kind === 'resources')
|
|
620
|
+
?.index ?? 0)
|
|
621
|
+
.some((message) => message.role === 'tool_call' &&
|
|
622
|
+
['exec', 'code_exec', 'code_mode', 'agent_start'].includes(message.name ?? '')))
|
|
616
623
|
});
|
|
617
624
|
sessionRuntime.contextualWorldState = contextualWorldState;
|
|
618
625
|
if (!isDeepStrictEqual(previousContextualWorldState, contextualWorldState))
|
|
@@ -900,38 +907,29 @@ export async function createMarAgentWithAdapter(options) {
|
|
|
900
907
|
let firstModelResponseAt;
|
|
901
908
|
let requestCompletedAt;
|
|
902
909
|
let requestOutputTokens;
|
|
903
|
-
let outputTokenRevision = 0;
|
|
904
|
-
let lastPerformanceEmittedAt;
|
|
905
|
-
let lastPublishedOutputTokenRevision = 0;
|
|
906
|
-
let performanceEmitted = false;
|
|
907
910
|
const roundMessageCount = messages.length;
|
|
908
|
-
const
|
|
911
|
+
const emitLatency = async () => {
|
|
909
912
|
if (mode === 'compact' || firstModelResponseAt === undefined)
|
|
910
913
|
return;
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
outputTokenRevision === lastPublishedOutputTokenRevision))
|
|
914
|
+
await emit({
|
|
915
|
+
type: 'model.performance',
|
|
916
|
+
latencyMs: Math.max(0, Math.round(firstModelResponseAt - requestStartedAt))
|
|
917
|
+
});
|
|
918
|
+
};
|
|
919
|
+
const emitFinalPerformance = async (completedAt) => {
|
|
920
|
+
if (mode === 'compact' || firstModelResponseAt === undefined)
|
|
919
921
|
return;
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
922
|
+
const outputDurationMs = Math.max(0, Math.round(completedAt - firstModelResponseAt));
|
|
923
|
+
const outputTokens = requestOutputTokens;
|
|
924
|
+
if (outputTokens === undefined || outputDurationMs <= 0)
|
|
923
925
|
return;
|
|
924
926
|
await emit({
|
|
925
927
|
type: 'model.performance',
|
|
926
928
|
latencyMs: Math.max(0, Math.round(firstModelResponseAt - requestStartedAt)),
|
|
927
|
-
|
|
929
|
+
outputTokens,
|
|
928
930
|
outputDurationMs,
|
|
929
|
-
|
|
931
|
+
tokensPerSecond: Number(((outputTokens * 1000) / outputDurationMs).toFixed(2))
|
|
930
932
|
});
|
|
931
|
-
performanceEmitted = true;
|
|
932
|
-
lastPerformanceEmittedAt = sampledAt;
|
|
933
|
-
if (tokensPerSecond !== undefined)
|
|
934
|
-
lastPublishedOutputTokenRevision = outputTokenRevision;
|
|
935
933
|
};
|
|
936
934
|
const persistToolCalls = async (calls) => {
|
|
937
935
|
for (const call of calls) {
|
|
@@ -979,6 +977,7 @@ export async function createMarAgentWithAdapter(options) {
|
|
|
979
977
|
onAttemptDiagnostic: recordModelAttempt(mode === 'compact' ? 'COMPACTION' : 'EXECUTION')
|
|
980
978
|
})) {
|
|
981
979
|
const modelResponseAt = mode !== 'compact' && isModelResponseEvent(event) ? performance.now() : undefined;
|
|
980
|
+
const isFirstModelResponse = modelResponseAt !== undefined && firstModelResponseAt === undefined;
|
|
982
981
|
if (modelResponseAt !== undefined) {
|
|
983
982
|
sawModelSemanticEvent = true;
|
|
984
983
|
firstModelResponseAt ??= modelResponseAt;
|
|
@@ -1070,7 +1069,6 @@ export async function createMarAgentWithAdapter(options) {
|
|
|
1070
1069
|
: undefined;
|
|
1071
1070
|
if (outputTokens !== undefined) {
|
|
1072
1071
|
requestOutputTokens = outputTokens;
|
|
1073
|
-
outputTokenRevision++;
|
|
1074
1072
|
}
|
|
1075
1073
|
const reportedContext = event.usage.contextInputTokens ?? event.usage.inputTokens;
|
|
1076
1074
|
if (reportedContext !== undefined &&
|
|
@@ -1121,12 +1119,12 @@ export async function createMarAgentWithAdapter(options) {
|
|
|
1121
1119
|
stop = event.reason;
|
|
1122
1120
|
requestCompletedAt = performance.now();
|
|
1123
1121
|
}
|
|
1124
|
-
if (
|
|
1125
|
-
await
|
|
1122
|
+
if (isFirstModelResponse)
|
|
1123
|
+
await emitLatency();
|
|
1126
1124
|
}
|
|
1127
1125
|
if (mode !== 'compact' && firstModelResponseAt !== undefined) {
|
|
1128
1126
|
const completedAt = requestCompletedAt ?? performance.now();
|
|
1129
|
-
await
|
|
1127
|
+
await emitFinalPerformance(completedAt);
|
|
1130
1128
|
}
|
|
1131
1129
|
pendingOutputContinuation = undefined;
|
|
1132
1130
|
incompleteToolCall = pendingTools.some((call) => call.argumentError !== undefined);
|
|
@@ -1291,6 +1289,7 @@ export async function createMarAgentWithAdapter(options) {
|
|
|
1291
1289
|
}
|
|
1292
1290
|
catch (error) {
|
|
1293
1291
|
const diagnostic = describeMarAgentError(error, 'MAR_AGENT_HOST_OPERATION_FAILED');
|
|
1292
|
+
const partial = error instanceof CodeCellExecutionError ? error.result : undefined;
|
|
1294
1293
|
await emit({
|
|
1295
1294
|
type: 'tool.failed',
|
|
1296
1295
|
toolName: call.name,
|
|
@@ -1301,13 +1300,16 @@ export async function createMarAgentWithAdapter(options) {
|
|
|
1301
1300
|
});
|
|
1302
1301
|
return {
|
|
1303
1302
|
call,
|
|
1304
|
-
content:
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1303
|
+
content: partial === undefined
|
|
1304
|
+
? JSON.stringify({
|
|
1305
|
+
error: diagnostic.code,
|
|
1306
|
+
message: diagnostic.message,
|
|
1307
|
+
...(error instanceof MarAgentError && error.retryable
|
|
1308
|
+
? { retryable: true }
|
|
1309
|
+
: {})
|
|
1310
|
+
})
|
|
1311
|
+
: boundedModelToolOutput(call.name, partial.content, partial.data).content,
|
|
1312
|
+
...(partial?.images?.length ? { images: partial.images } : {}),
|
|
1311
1313
|
failed: true,
|
|
1312
1314
|
error
|
|
1313
1315
|
};
|
|
@@ -2237,7 +2239,7 @@ function updateContextualWorldState(state, index, current) {
|
|
|
2237
2239
|
if (current.instructionContent !== undefined)
|
|
2238
2240
|
entries.push({ index, kind: 'instructions', content: current.instructionContent });
|
|
2239
2241
|
}
|
|
2240
|
-
const resourceContent = retainedSessionResourcesUpdate(current.resourceSnapshot, state.initialized ? state.resourceSnapshot : undefined,
|
|
2242
|
+
const resourceContent = retainedSessionResourcesUpdate(current.resourceSnapshot, state.initialized ? state.resourceSnapshot : undefined, current.previousMayContainResources);
|
|
2241
2243
|
if (resourceContent !== undefined)
|
|
2242
2244
|
entries.push({ index, kind: 'resources', content: resourceContent });
|
|
2243
2245
|
return {
|
|
@@ -2283,7 +2285,7 @@ function estimatedRequestTokens(system, messages, tools, images) {
|
|
|
2283
2285
|
transientImageCount * AGENT_EXECUTION_POLICY.estimatedTokensPerImage);
|
|
2284
2286
|
}
|
|
2285
2287
|
function boundedModelToolOutput(toolName, value, data) {
|
|
2286
|
-
const execDiagnostics = toolName === 'exec' &&
|
|
2288
|
+
const execDiagnostics = (toolName === 'exec' || toolName === 'exec_wait') &&
|
|
2287
2289
|
isRecord(data) &&
|
|
2288
2290
|
typeof data.stdout === 'string' &&
|
|
2289
2291
|
typeof data.stderr === 'string'
|
|
@@ -2390,7 +2392,7 @@ function structuredToolEventData(toolName, value) {
|
|
|
2390
2392
|
typeof value.sha256 === 'string' &&
|
|
2391
2393
|
/^[a-f0-9]{64}$/u.test(value.sha256))
|
|
2392
2394
|
return value;
|
|
2393
|
-
if (toolName === 'exec' &&
|
|
2395
|
+
if ((toolName === 'exec' || toolName === 'exec_wait') &&
|
|
2394
2396
|
(value.status === 'running' || value.status === 'completed' || value.status === 'cancelled') &&
|
|
2395
2397
|
typeof value.stdout === 'string' &&
|
|
2396
2398
|
typeof value.stderr === 'string' &&
|
|
@@ -50,7 +50,7 @@ export type ContextGcReplacement = {
|
|
|
50
50
|
readonly evidenceSequences: readonly number[];
|
|
51
51
|
} | {
|
|
52
52
|
readonly kind: 'TOOL_CALL_INPUT';
|
|
53
|
-
readonly toolName: 'apply_patch' | 'exec' | 'code_mode' | 'code_exec';
|
|
53
|
+
readonly toolName: 'apply_patch' | 'exec' | 'exec_wait' | 'code_mode' | 'code_exec';
|
|
54
54
|
readonly callId: string;
|
|
55
55
|
readonly target: ContextGcTarget;
|
|
56
56
|
readonly replacementArguments: unknown;
|
|
@@ -350,8 +350,9 @@ function replacementMatchesRecords(replacement, records, recordsBySequence, work
|
|
|
350
350
|
if (!isSuccessfulExecLifecycleEvidence(replacement, source, records, recordsBySequence))
|
|
351
351
|
return false;
|
|
352
352
|
if (replacement.kind === 'TOOL_CALL_INPUT')
|
|
353
|
-
return isDeepStrictEqual(replacement.replacementArguments, contextGcExecInputReplacement(source.payload.arguments, execInputReplacementFormatVersion(replacement.replacementArguments)));
|
|
354
|
-
const targetEvidence = successfulToolEvidence(records, recordsBySequence, source.sequence, 'exec')
|
|
353
|
+
return isDeepStrictEqual(replacement.replacementArguments, contextGcExecInputReplacement(source.payload.arguments, execInputReplacementFormatVersion(replacement.replacementArguments), replacement.toolName));
|
|
354
|
+
const targetEvidence = successfulToolEvidence(records, recordsBySequence, source.sequence, 'exec') ??
|
|
355
|
+
successfulToolEvidence(records, recordsBySequence, source.sequence, 'exec_wait');
|
|
355
356
|
const result = normalizeContextGcExecResult(source.payload.content, targetEvidence?.completed.payload);
|
|
356
357
|
return (result !== undefined &&
|
|
357
358
|
replacement.replacementContent ===
|
|
@@ -474,7 +475,8 @@ function successfulPatchEvidenceForPath(records, recordsBySequence, evidenceSequ
|
|
|
474
475
|
: undefined;
|
|
475
476
|
}
|
|
476
477
|
function isSuccessfulExecLifecycleEvidence(replacement, source, records, recordsBySequence) {
|
|
477
|
-
const evidence = successfulToolEvidence(records, recordsBySequence, replacement.evidenceSequence, 'exec')
|
|
478
|
+
const evidence = successfulToolEvidence(records, recordsBySequence, replacement.evidenceSequence, 'exec') ??
|
|
479
|
+
successfulToolEvidence(records, recordsBySequence, replacement.evidenceSequence, 'exec_wait');
|
|
478
480
|
if (!evidence || !isRecord(evidence.call.payload) || !isRecord(evidence.result.payload))
|
|
479
481
|
return false;
|
|
480
482
|
const finalResult = normalizeContextGcExecResult(evidence.result.payload.content, evidence.completed.payload);
|
|
@@ -487,7 +489,7 @@ function isSuccessfulExecLifecycleEvidence(replacement, source, records, records
|
|
|
487
489
|
targetCall.sequence > evidence.call.sequence ||
|
|
488
490
|
!isRecord(targetCall.payload) ||
|
|
489
491
|
targetCall.payload.role !== 'tool_call' ||
|
|
490
|
-
targetCall.payload.name !== 'exec' ||
|
|
492
|
+
(targetCall.payload.name !== 'exec' && targetCall.payload.name !== 'exec_wait') ||
|
|
491
493
|
targetCall.payload.callId !== replacement.callId)
|
|
492
494
|
return false;
|
|
493
495
|
const targetResult = replacement.kind === 'EXEC_OUTPUT'
|
|
@@ -495,7 +497,7 @@ function isSuccessfulExecLifecycleEvidence(replacement, source, records, records
|
|
|
495
497
|
: modelToolResultForCall(records, targetCall, evidence.result.sequence);
|
|
496
498
|
if (!targetResult)
|
|
497
499
|
return false;
|
|
498
|
-
const targetEvidence = successfulToolEvidence(records, recordsBySequence, targetResult.sequence,
|
|
500
|
+
const targetEvidence = successfulToolEvidence(records, recordsBySequence, targetResult.sequence, targetCall.payload.name);
|
|
499
501
|
if (!targetEvidence || targetEvidence.call.sequence !== targetCall.sequence)
|
|
500
502
|
return false;
|
|
501
503
|
if (targetEvidence.callId === evidence.callId)
|
|
@@ -631,6 +633,7 @@ function isContextGcReplacement(value) {
|
|
|
631
633
|
value.target.role === 'tool_call' &&
|
|
632
634
|
(value.toolName === 'apply_patch' ||
|
|
633
635
|
value.toolName === 'exec' ||
|
|
636
|
+
value.toolName === 'exec_wait' ||
|
|
634
637
|
value.toolName === 'code_mode' ||
|
|
635
638
|
value.toolName === 'code_exec') &&
|
|
636
639
|
value.target.toolName === value.toolName &&
|
|
@@ -56,7 +56,7 @@ export declare function contextGcStaleReadReplacement(input: {
|
|
|
56
56
|
readonly formatVersion?: 1 | 2;
|
|
57
57
|
}): string;
|
|
58
58
|
export declare function contextGcWebOutputReplacement(toolName: ContextGcWebToolName, originalContent: string, truncated: boolean): string;
|
|
59
|
-
export declare function contextGcExecInputReplacement(arguments_: unknown, formatVersion?: 1 | 2 | 3): unknown;
|
|
59
|
+
export declare function contextGcExecInputReplacement(arguments_: unknown, formatVersion?: 1 | 2 | 3, toolName?: string): unknown;
|
|
60
60
|
export declare function contextGcExecOutputReplacement(result: ContextGcExecResult, formatVersion?: 1 | 2 | 3): string;
|
|
61
61
|
export declare function normalizeContextGcExecResult(modelContent: unknown, completedPayload: unknown): ContextGcExecResult | undefined;
|
|
62
62
|
export declare function parseContextGcExecResult(value: unknown): ContextGcExecResult | undefined;
|
|
@@ -97,14 +97,16 @@ export function contextGcWebOutputReplacement(toolName, originalContent, truncat
|
|
|
97
97
|
contextGc: 'Detailed web tool output was reclaimed after six successful rollouts. An incomplete preview is not the complete search result or page, and GC did not access the network.'
|
|
98
98
|
});
|
|
99
99
|
}
|
|
100
|
-
export function contextGcExecInputReplacement(arguments_, formatVersion = 3) {
|
|
100
|
+
export function contextGcExecInputReplacement(arguments_, formatVersion = 3, toolName = 'exec') {
|
|
101
101
|
if (!isRecord(arguments_))
|
|
102
102
|
return `<context_gc kind="terminal_exec_input"${formatVersion === 3 ? ' formatVersion="3"' : ''} originalBytes="${Buffer.byteLength(stableJson(arguments_))}"${formatVersion === 3 ? '' : ` originalSha256="${sha256(stableJson(arguments_))}"`} />`;
|
|
103
103
|
const action = typeof arguments_.action === 'string'
|
|
104
104
|
? arguments_.action
|
|
105
105
|
: typeof arguments_.command === 'string'
|
|
106
106
|
? 'start'
|
|
107
|
-
: '
|
|
107
|
+
: toolName === 'exec_wait'
|
|
108
|
+
? 'poll'
|
|
109
|
+
: 'unknown';
|
|
108
110
|
const original = stableJson(arguments_);
|
|
109
111
|
const retained = {
|
|
110
112
|
action,
|
|
@@ -190,7 +192,7 @@ export function normalizeContextGcExecResult(modelContent, completedPayload) {
|
|
|
190
192
|
if (typeof modelContent !== 'string' ||
|
|
191
193
|
!isRecord(completedPayload) ||
|
|
192
194
|
completedPayload.type !== 'tool.completed' ||
|
|
193
|
-
completedPayload.toolName !== 'exec' ||
|
|
195
|
+
(completedPayload.toolName !== 'exec' && completedPayload.toolName !== 'exec_wait') ||
|
|
194
196
|
completedPayload.truncated !== true)
|
|
195
197
|
return undefined;
|
|
196
198
|
const data = completedPayload.data;
|
package/dist/tools/agent-wait.js
CHANGED
|
@@ -4,7 +4,7 @@ export const agentWaitToolDefinition = {
|
|
|
4
4
|
name: 'agent_wait',
|
|
5
5
|
allowedCallers: ['direct', 'programmatic'],
|
|
6
6
|
parallelSafety: 'serial',
|
|
7
|
-
description: 'Read or wait a bounded time for a child agent result. waitFor defaults to "completion"; use "message" only when a fresh public commentary/final message would change the next decision. Omit agentId to wait for whichever live child returns first, matching Codex-style untargeted waiting and avoiding UUID transcription; provide the exact agentId returned by agent_start only when observing a specific child. If no child is live, untargeted waiting reads the most recently created child. For an exec processId, use
|
|
7
|
+
description: 'Read or wait a bounded time for a child agent result. waitFor defaults to "completion"; use "message" only when a fresh public commentary/final message would change the next decision. Omit agentId to wait for whichever live child returns first, matching Codex-style untargeted waiting and avoiding UUID transcription; provide the exact agentId returned by agent_start only when observing a specific child. If no child is live, untargeted waiting reads the most recently created child. For an exec processId, use exec_wait instead (tools.exec_wait in Code Mode). Omitting waitMs waits up to 30 seconds by default; waitMs=0 returns the current snapshot immediately. Positive waits range up to 5 minutes, and positive values below 10 seconds are raised to 10 seconds. When blocked on a live child result, prefer one 300000 ms completion wait rather than repeated short waits. Every result includes waitOutcome as snapshot, message, completion, or timeout plus the real agentId and description. A message, timeout with unread progress, or first terminal completion result includes the newly observed latest public commentary/final message; snapshots and repeated observations omit previously observed message text. latestActivity can still identify a public message or bounded tool started/completed/failed status. A queued or running result is not completion. A message wait consumes only the latest unread public message in the current Session runtime; tool activity does not wake it, and parent cancellation preserves unread progress for a later Execution. Do not use message waits as heartbeat checks or request progress merely to confirm that a child is still running. First terminal results include bounded summary, changedFiles, verification, and read evidence with path/line ranges; use that evidence to avoid repeating the full exploration, while independently checking consequential edits and claims.',
|
|
8
8
|
inputSchema: {
|
|
9
9
|
type: 'object',
|
|
10
10
|
properties: {
|
|
@@ -8,7 +8,7 @@ export const applyPatchToolDefinition = {
|
|
|
8
8
|
name: 'apply_patch',
|
|
9
9
|
allowedCallers: ['direct', 'programmatic'],
|
|
10
10
|
parallelSafety: 'serial',
|
|
11
|
-
description: `Preferred for precise, reviewable local edits. Do not batch all task files merely to reduce tool calls. Group only a few tightly coupled edits whose combined patch is quick to generate and review; split broad or independent work into successive bounded patches before composing them. Submit a raw *** Begin Patch / *** End Patch patch. Supports Add File, Update File, Delete File, and Move to. Add File may replace an existing regular file, and Move to may replace an existing regular destination; duplicate operations on the same resolved path remain invalid. Update hunks accept @@ or @@ <change context>; context-only hunks may also be used as ordered anchors before a later changing hunk. Context is searched forward with Codex-style exact, whitespace-tolerant, then common Unicode punctuation-tolerant matching. Syntax and resolved paths are validated before writing, then file units are committed in order. Each file unit is atomic, and a Move keeps its source and destination atomic. On MAR_AGENT_PATCH_PARTIAL, earlier files remain applied; the error lists applied, failed, and pending paths, so retry only failed and pending paths. Existing file permissions, line endings, and final-newline state are preserved. Returns changed file paths and operations with per-file and total line counts when all units succeed. In Code Mode, the Promise resolves on success and rejects with code/message on failure; the returned envelope has data.files and data.totals, not an ok or success flag. Oversized patches are rejected without advertising defensive limits as recommended patch sizes. A first-unit MAR_AGENT_PATCH_CONFLICT applies nothing; hunk match conflicts identify the affected path and hunk so you can reread only that file before retrying.`,
|
|
11
|
+
description: `Preferred for precise, reviewable local edits. Do not batch all task files merely to reduce tool calls. Group only a few tightly coupled edits whose combined patch is quick to generate and review; split broad or independent work into successive bounded patches before composing them. Submit a raw *** Begin Patch / *** End Patch patch. Supports Add File, Update File, Delete File, and Move to. Add File may replace an existing regular file, and Move to may replace an existing regular destination; duplicate operations on the same resolved path remain invalid. Update hunks accept @@ or @@ <change context>; context-only hunks may also be used as ordered anchors before a later changing hunk. Omit unchanged file sections: an Update File must change or move the file, and its last hunk must not be context-only. Context is searched forward with Codex-style exact, whitespace-tolerant, then common Unicode punctuation-tolerant matching. Syntax and resolved paths are validated before writing, then file units are committed in order. Each file unit is atomic, and a Move keeps its source and destination atomic. On MAR_AGENT_PATCH_PARTIAL, earlier files remain applied; the error lists applied, failed, and pending paths, so retry only failed and pending paths. Existing file permissions, line endings, and final-newline state are preserved. Returns changed file paths and operations with per-file and total line counts when all units succeed. In Code Mode, the Promise resolves on success and rejects with code/message on failure; the returned envelope has data.files and data.totals, not an ok or success flag. Oversized patches are rejected without advertising defensive limits as recommended patch sizes. A first-unit MAR_AGENT_PATCH_CONFLICT applies nothing; hunk match conflicts identify the affected path and hunk so you can reread only that file before retrying.`,
|
|
12
12
|
inputMode: 'freeform',
|
|
13
13
|
outputSchema: {
|
|
14
14
|
type: 'object',
|
package/dist/tools/callers.js
CHANGED
|
@@ -13,7 +13,7 @@ export function toolCallers(tool) {
|
|
|
13
13
|
export function allowsToolCaller(tool, caller) {
|
|
14
14
|
// Control tools cannot be widened by a Host override or historical definition.
|
|
15
15
|
if (caller === 'programmatic' &&
|
|
16
|
-
['code_exec', 'code_exec_wait', 'code_mode', 'tool_search', 'question'].includes(tool.name))
|
|
16
|
+
['code_exec', 'code_exec_wait', 'code_mode', 'tool_search', 'question', 'skill'].includes(tool.name))
|
|
17
17
|
return false;
|
|
18
18
|
return toolCallers(tool).includes(caller);
|
|
19
19
|
}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
import { MarAgentError } from '../../error.js';
|
|
1
2
|
import type { ToolExecutionContext, ToolExecutionResult } from '../../sdk/types.js';
|
|
2
3
|
export interface CellOutput {
|
|
3
|
-
text(value: string): void;
|
|
4
|
+
text(value: string, originalBytes?: number): void;
|
|
4
5
|
image(value: NonNullable<ToolExecutionResult['images']>[number]): void;
|
|
5
6
|
yield(): void;
|
|
6
7
|
}
|
|
8
|
+
/** Keeps a failed tool call observable without discarding earlier explicit output. */
|
|
9
|
+
export declare class CodeCellExecutionError extends MarAgentError {
|
|
10
|
+
readonly result: ToolExecutionResult;
|
|
11
|
+
constructor(error: unknown, result: ToolExecutionResult);
|
|
12
|
+
}
|
|
7
13
|
type Status = 'running' | 'completed' | 'failed' | 'cancelled';
|
|
8
14
|
/** Session-owned execution, separate from the cancellable model-tool wait. */
|
|
9
15
|
export declare class CodeCells {
|
|
@@ -2,6 +2,19 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { MarAgentError, describeMarAgentError } from '../../error.js';
|
|
3
3
|
import { TOOL_EXECUTION_LIMITS as limits } from '../execution-limits.js';
|
|
4
4
|
import { truncateModelText } from '../../runtime/token-budget.js';
|
|
5
|
+
import { BoundedTextOutput, renderCapturedText } from './text-output.js';
|
|
6
|
+
/** Keeps a failed tool call observable without discarding earlier explicit output. */
|
|
7
|
+
export class CodeCellExecutionError extends MarAgentError {
|
|
8
|
+
result;
|
|
9
|
+
constructor(error, result) {
|
|
10
|
+
const diagnostic = describeMarAgentError(error, 'MAR_AGENT_CODE_FAILED');
|
|
11
|
+
super(diagnostic.code, diagnostic.message.replace(`${diagnostic.code}: `, ''), {
|
|
12
|
+
cause: error,
|
|
13
|
+
...(error instanceof MarAgentError ? { retryable: error.retryable } : {})
|
|
14
|
+
});
|
|
15
|
+
this.result = result;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
5
18
|
function options(value, wait) {
|
|
6
19
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
7
20
|
throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Code Exec options must be an object.');
|
|
@@ -11,11 +24,11 @@ function options(value, wait) {
|
|
|
11
24
|
: ['yieldMs', 'maxOutputTokens'];
|
|
12
25
|
if (Object.keys(input).some((key) => !keys.includes(key)))
|
|
13
26
|
throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Unknown Code Exec option.');
|
|
14
|
-
const yieldMs = input.yieldMs ?? limits.codeDefaultYieldMs;
|
|
27
|
+
const yieldMs = input.yieldMs ?? (wait ? limits.codeDefaultWaitYieldMs : limits.codeDefaultYieldMs);
|
|
15
28
|
const maxOutputTokens = input.maxOutputTokens ?? limits.codeMaxOutputTokens;
|
|
16
29
|
if (!Number.isSafeInteger(yieldMs) ||
|
|
17
30
|
Number(yieldMs) < 0 ||
|
|
18
|
-
Number(yieldMs) > limits.codeMaxYieldMs ||
|
|
31
|
+
Number(yieldMs) > (wait ? limits.codeMaxWaitYieldMs : limits.codeMaxYieldMs) ||
|
|
19
32
|
!Number.isSafeInteger(maxOutputTokens) ||
|
|
20
33
|
Number(maxOutputTokens) < 1 ||
|
|
21
34
|
Number(maxOutputTokens) > limits.codeMaxOutputTokens ||
|
|
@@ -73,9 +86,10 @@ export class CodeCells {
|
|
|
73
86
|
executionId: context.executionId,
|
|
74
87
|
controller,
|
|
75
88
|
status: 'running',
|
|
76
|
-
text:
|
|
89
|
+
text: new BoundedTextOutput(limits.codeMaxOutputBytes),
|
|
77
90
|
images: [],
|
|
78
91
|
yielded: false,
|
|
92
|
+
truncated: false,
|
|
79
93
|
listeners: new Set(),
|
|
80
94
|
done: Promise.resolve()
|
|
81
95
|
};
|
|
@@ -90,7 +104,7 @@ export class CodeCells {
|
|
|
90
104
|
.then(async () => {
|
|
91
105
|
releaseSession = await context.codeMode?.retainSession?.();
|
|
92
106
|
return run(source, { ...context, signal: controller.signal }, {
|
|
93
|
-
text: (text) => cell.text.
|
|
107
|
+
text: (text, originalBytes) => cell.text.append(text, originalBytes),
|
|
94
108
|
image: (image) => cell.images.push(image),
|
|
95
109
|
yield: () => {
|
|
96
110
|
cell.yielded = true;
|
|
@@ -187,7 +201,12 @@ export class CodeCells {
|
|
|
187
201
|
if (cell.terminal) {
|
|
188
202
|
const snapshot = cell.terminal.data;
|
|
189
203
|
const bounded = truncateModelText(snapshot.output, input.maxOutputTokens);
|
|
190
|
-
const data = {
|
|
204
|
+
const data = {
|
|
205
|
+
...snapshot,
|
|
206
|
+
output: bounded.content,
|
|
207
|
+
truncated: cell.terminal.truncated || bounded.truncated,
|
|
208
|
+
cached: true
|
|
209
|
+
};
|
|
191
210
|
return {
|
|
192
211
|
...cell.terminal,
|
|
193
212
|
data,
|
|
@@ -195,10 +214,12 @@ export class CodeCells {
|
|
|
195
214
|
truncated: cell.terminal.truncated || bounded.truncated
|
|
196
215
|
};
|
|
197
216
|
}
|
|
198
|
-
const
|
|
217
|
+
const captured = cell.text.drain();
|
|
199
218
|
const images = cell.images.splice(0);
|
|
219
|
+
const text = renderCapturedText(captured, images.length);
|
|
200
220
|
cell.yielded = false;
|
|
201
221
|
const bounded = truncateModelText(text, input.maxOutputTokens);
|
|
222
|
+
cell.truncated ||= captured.droppedBytes > 0 || bounded.truncated;
|
|
202
223
|
const error = cell.error === undefined
|
|
203
224
|
? undefined
|
|
204
225
|
: describeMarAgentError(cell.error, 'MAR_AGENT_CODE_FAILED');
|
|
@@ -207,13 +228,16 @@ export class CodeCells {
|
|
|
207
228
|
status: cell.status,
|
|
208
229
|
output: bounded.content,
|
|
209
230
|
...cell.result?.data,
|
|
231
|
+
outputBytes: captured.originalBytes,
|
|
232
|
+
outputDroppedBytes: captured.droppedBytes,
|
|
233
|
+
truncated: cell.truncated,
|
|
210
234
|
...(error ? { error: { code: error.code, message: error.message } } : {}),
|
|
211
235
|
cached: false
|
|
212
236
|
};
|
|
213
237
|
const result = {
|
|
214
238
|
content: JSON.stringify(data),
|
|
215
239
|
data,
|
|
216
|
-
truncated:
|
|
240
|
+
truncated: cell.truncated,
|
|
217
241
|
...(images.length ? { images } : {})
|
|
218
242
|
};
|
|
219
243
|
if (cell.status !== 'running')
|
|
@@ -221,7 +245,7 @@ export class CodeCells {
|
|
|
221
245
|
// Keep synchronous completion compatible; retained waits always include explicit lifecycle metadata.
|
|
222
246
|
if (initial && cell.status !== 'running') {
|
|
223
247
|
if (cell.error !== undefined)
|
|
224
|
-
throw cell.error;
|
|
248
|
+
throw new CodeCellExecutionError(cell.error, result);
|
|
225
249
|
return { ...result, content: bounded.content || 'Code Exec completed without emitted text.' };
|
|
226
250
|
}
|
|
227
251
|
return result;
|