@myagentroam/agent 0.9.135 → 0.9.136
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/cli/main.js +1 -1
- package/dist/host/local-agent-host.d.ts +9 -7
- package/dist/host/local-agent-host.js +77 -5
- package/dist/host/local-process-executor.d.ts +2 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +1 -0
- package/dist/model/catalog-file.d.ts +2 -0
- package/dist/model/configuration.d.ts +5 -0
- package/dist/model/configuration.js +8 -0
- package/dist/model/contracts.d.ts +7 -0
- package/dist/model/openai-responses.d.ts +1 -1
- package/dist/model/openai-responses.js +85 -20
- package/dist/prompts/workflow.js +2 -0
- package/dist/runtime/context-gc-runtime.d.ts +1 -0
- package/dist/runtime/context-gc-runtime.js +3 -0
- package/dist/runtime/context-gc-tokens.d.ts +1 -0
- package/dist/runtime/context-gc-tokens.js +1 -1
- package/dist/runtime/context-gc.d.ts +1 -0
- package/dist/runtime/context-gc.js +85 -8
- package/dist/runtime/context-projection.js +19 -2
- package/dist/sandbox/linux/bubblewrap-sandbox-executor.js +29 -2
- package/dist/sdk/agent.js +208 -25
- package/dist/sdk/mcp-catalog.d.ts +13 -0
- package/dist/sdk/mcp-catalog.js +219 -0
- package/dist/sdk/types.d.ts +50 -1
- package/dist/session/context-gc-checkpoint.d.ts +7 -1
- package/dist/session/context-gc-checkpoint.js +28 -2
- package/dist/session/context-gc-replacements.d.ts +7 -0
- package/dist/session/context-gc-replacements.js +26 -0
- package/dist/session/jsonl-store.d.ts +6 -0
- package/dist/session/jsonl-store.js +92 -54
- package/dist/subagent/scheduler.d.ts +3 -3
- package/dist/subagent/session-controller.d.ts +1 -1
- package/dist/tools/agent-cancel.js +1 -0
- package/dist/tools/agent-message.js +1 -0
- package/dist/tools/agent-start.js +1 -0
- package/dist/tools/agent-wait.js +1 -0
- package/dist/tools/apply-patch.js +2 -1
- package/dist/tools/callers.d.ts +4 -0
- package/dist/tools/callers.js +18 -0
- package/dist/tools/code-mode/engine.d.ts +43 -0
- package/dist/tools/code-mode/engine.js +1 -0
- package/dist/tools/code-mode/quickjs-engine.d.ts +2 -0
- package/dist/tools/code-mode/quickjs-engine.js +454 -0
- package/dist/tools/code-mode/state.d.ts +20 -0
- package/dist/tools/code-mode/state.js +82 -0
- package/dist/tools/code-mode/worker.d.ts +1 -0
- package/dist/tools/code-mode/worker.js +65 -0
- package/dist/tools/code-mode.d.ts +5 -3
- package/dist/tools/code-mode.js +129 -120
- package/dist/tools/exec.js +1 -0
- package/dist/tools/execution-limits.d.ts +8 -0
- package/dist/tools/execution-limits.js +8 -0
- package/dist/tools/local-registry.d.ts +2 -0
- package/dist/tools/local-registry.js +1 -1
- package/dist/tools/question.js +1 -0
- package/dist/tools/read.js +1 -0
- package/dist/tools/registry.js +3 -0
- package/dist/tools/runtime-context.d.ts +2 -0
- package/dist/tools/runtime.js +3 -1
- package/dist/tools/skill.js +1 -0
- package/dist/tools/todo-read.js +1 -0
- package/dist/tools/todo-write.js +1 -0
- package/dist/tools/tool-search.d.ts +43 -0
- package/dist/tools/tool-search.js +278 -0
- package/dist/tools/view-image.js +1 -0
- package/dist/tools/web-fetch.js +1 -0
- package/dist/tools/web-search.js +1 -0
- package/package.json +3 -1
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { MarAgentError } from '../error.js';
|
|
2
|
+
export const MCP_DEFINITION_LIMITS = Object.freeze({
|
|
3
|
+
depth: 64,
|
|
4
|
+
nodes: 32768,
|
|
5
|
+
definitionBytes: 256 * 1024,
|
|
6
|
+
catalogBytes: 16 * 1024 * 1024,
|
|
7
|
+
tools: 10000,
|
|
8
|
+
servers: 256
|
|
9
|
+
});
|
|
10
|
+
function invalid() {
|
|
11
|
+
throw new MarAgentError('MAR_AGENT_MCP_CATALOG_INVALID', 'MCP catalog contains invalid or oversized definitions.');
|
|
12
|
+
}
|
|
13
|
+
const record = (value) => value !== null &&
|
|
14
|
+
typeof value === 'object' &&
|
|
15
|
+
!Array.isArray(value) &&
|
|
16
|
+
[Object.prototype, null].includes(Object.getPrototypeOf(value));
|
|
17
|
+
const text = (value, maximum) => typeof value === 'string' && value.length > 0 && value.length <= maximum;
|
|
18
|
+
const onlyKeys = (value, keys) => Object.keys(value).every((key) => keys.includes(key));
|
|
19
|
+
/** Bound all JSON, including unknown extension keywords, before recursive lowering. */
|
|
20
|
+
function assertJson(value) {
|
|
21
|
+
const stack = [{ value, depth: 0 }];
|
|
22
|
+
const seen = new Set();
|
|
23
|
+
let nodes = 0;
|
|
24
|
+
while (stack.length) {
|
|
25
|
+
const current = stack.pop();
|
|
26
|
+
if (current.exit) {
|
|
27
|
+
seen.delete(current.value);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (++nodes > MCP_DEFINITION_LIMITS.nodes || current.depth > MCP_DEFINITION_LIMITS.depth)
|
|
31
|
+
invalid();
|
|
32
|
+
const item = current.value;
|
|
33
|
+
if (item === null ||
|
|
34
|
+
typeof item === 'string' ||
|
|
35
|
+
typeof item === 'boolean' ||
|
|
36
|
+
(typeof item === 'number' && Number.isFinite(item)))
|
|
37
|
+
continue;
|
|
38
|
+
if (!Array.isArray(item) && !record(item))
|
|
39
|
+
invalid();
|
|
40
|
+
if (seen.has(item))
|
|
41
|
+
invalid();
|
|
42
|
+
seen.add(item);
|
|
43
|
+
stack.push({ value: item, depth: current.depth, exit: true });
|
|
44
|
+
for (const child of Object.values(item))
|
|
45
|
+
stack.push({ value: child, depth: current.depth + 1 });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function assertSchema(schema) {
|
|
49
|
+
if (!record(schema) || schema.type !== 'object')
|
|
50
|
+
invalid();
|
|
51
|
+
assertJson(schema);
|
|
52
|
+
const stack = [schema];
|
|
53
|
+
const types = new Set(['null', 'boolean', 'object', 'array', 'number', 'integer', 'string']);
|
|
54
|
+
while (stack.length) {
|
|
55
|
+
const value = stack.pop();
|
|
56
|
+
if (typeof value === 'boolean')
|
|
57
|
+
continue;
|
|
58
|
+
if (!record(value))
|
|
59
|
+
invalid();
|
|
60
|
+
if (value.type !== undefined) {
|
|
61
|
+
const entries = Array.isArray(value.type) ? value.type : [value.type];
|
|
62
|
+
if (!entries.length ||
|
|
63
|
+
new Set(entries).size !== entries.length ||
|
|
64
|
+
entries.some((type) => typeof type !== 'string' || !types.has(type)))
|
|
65
|
+
invalid();
|
|
66
|
+
}
|
|
67
|
+
if (value.required !== undefined &&
|
|
68
|
+
(!Array.isArray(value.required) ||
|
|
69
|
+
value.required.some((name) => typeof name !== 'string') ||
|
|
70
|
+
new Set(value.required).size !== value.required.length))
|
|
71
|
+
invalid();
|
|
72
|
+
for (const key of [
|
|
73
|
+
'properties',
|
|
74
|
+
'patternProperties',
|
|
75
|
+
'$defs',
|
|
76
|
+
'definitions',
|
|
77
|
+
'dependentSchemas'
|
|
78
|
+
]) {
|
|
79
|
+
const children = value[key];
|
|
80
|
+
if (children === undefined)
|
|
81
|
+
continue;
|
|
82
|
+
if (!record(children))
|
|
83
|
+
invalid();
|
|
84
|
+
stack.push(...Object.values(children));
|
|
85
|
+
}
|
|
86
|
+
for (const key of ['allOf', 'anyOf', 'oneOf', 'prefixItems']) {
|
|
87
|
+
const children = value[key];
|
|
88
|
+
if (children === undefined)
|
|
89
|
+
continue;
|
|
90
|
+
if (!Array.isArray(children) || !children.length)
|
|
91
|
+
invalid();
|
|
92
|
+
stack.push(...children);
|
|
93
|
+
}
|
|
94
|
+
for (const key of [
|
|
95
|
+
'additionalProperties',
|
|
96
|
+
'unevaluatedProperties',
|
|
97
|
+
'additionalItems',
|
|
98
|
+
'unevaluatedItems',
|
|
99
|
+
'contains',
|
|
100
|
+
'propertyNames',
|
|
101
|
+
'not',
|
|
102
|
+
'if',
|
|
103
|
+
'then',
|
|
104
|
+
'else',
|
|
105
|
+
'items'
|
|
106
|
+
]) {
|
|
107
|
+
const child = value[key];
|
|
108
|
+
if (child !== undefined) {
|
|
109
|
+
if (key === 'items' && Array.isArray(child))
|
|
110
|
+
stack.push(...child);
|
|
111
|
+
else
|
|
112
|
+
stack.push(child);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
for (const key of ['$ref', '$id', '$schema', 'title', 'description', 'format', 'pattern'])
|
|
116
|
+
if (value[key] !== undefined && typeof value[key] !== 'string')
|
|
117
|
+
invalid();
|
|
118
|
+
for (const key of ['minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf'])
|
|
119
|
+
if (value[key] !== undefined &&
|
|
120
|
+
(typeof value[key] !== 'number' ||
|
|
121
|
+
!Number.isFinite(value[key]) ||
|
|
122
|
+
(key === 'multipleOf' && Number(value[key]) <= 0)))
|
|
123
|
+
invalid();
|
|
124
|
+
for (const key of [
|
|
125
|
+
'minLength',
|
|
126
|
+
'maxLength',
|
|
127
|
+
'minItems',
|
|
128
|
+
'maxItems',
|
|
129
|
+
'minProperties',
|
|
130
|
+
'maxProperties'
|
|
131
|
+
])
|
|
132
|
+
if (value[key] !== undefined && (!Number.isSafeInteger(value[key]) || Number(value[key]) < 0))
|
|
133
|
+
invalid();
|
|
134
|
+
if (value.enum !== undefined && (!Array.isArray(value.enum) || !value.enum.length))
|
|
135
|
+
invalid();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/** Shared Host and Node publication boundary, not a provider-specific conversion. */
|
|
139
|
+
export function assertMcpToolDefinition(value) {
|
|
140
|
+
if (!record(value) ||
|
|
141
|
+
!text(value.name, 256) ||
|
|
142
|
+
!/^mcp__[A-Za-z0-9_-]+__[A-Za-z0-9_-]+$/u.test(value.name) ||
|
|
143
|
+
typeof value.description !== 'string' ||
|
|
144
|
+
value.description.length > 65536 ||
|
|
145
|
+
(value.parallelSafety !== undefined &&
|
|
146
|
+
!['safe', 'serial'].includes(String(value.parallelSafety))) ||
|
|
147
|
+
(value.agentScope !== undefined &&
|
|
148
|
+
!['ROOT_AND_SUBAGENTS', 'ROOT_ONLY'].includes(String(value.agentScope))) ||
|
|
149
|
+
(value.allowedCallers !== undefined &&
|
|
150
|
+
(!Array.isArray(value.allowedCallers) ||
|
|
151
|
+
!value.allowedCallers.length ||
|
|
152
|
+
value.allowedCallers.some((caller) => caller !== 'direct' && caller !== 'programmatic'))) ||
|
|
153
|
+
(value.inputMode !== undefined && value.inputMode !== 'json'))
|
|
154
|
+
invalid();
|
|
155
|
+
assertJson(value);
|
|
156
|
+
assertSchema(value.inputSchema);
|
|
157
|
+
if (value.outputSchema !== undefined)
|
|
158
|
+
assertSchema(value.outputSchema);
|
|
159
|
+
if (Buffer.byteLength(JSON.stringify(value)) > MCP_DEFINITION_LIMITS.definitionBytes)
|
|
160
|
+
invalid();
|
|
161
|
+
}
|
|
162
|
+
export function assertMcpToolCatalog(value) {
|
|
163
|
+
if (!record(value) ||
|
|
164
|
+
typeof value.revision !== 'string' ||
|
|
165
|
+
value.revision.length > 512 ||
|
|
166
|
+
!onlyKeys(value, ['revision', 'servers', 'tools']) ||
|
|
167
|
+
!Array.isArray(value.servers) ||
|
|
168
|
+
value.servers.length > MCP_DEFINITION_LIMITS.servers ||
|
|
169
|
+
!Array.isArray(value.tools) ||
|
|
170
|
+
value.tools.length > MCP_DEFINITION_LIMITS.tools)
|
|
171
|
+
invalid();
|
|
172
|
+
const servers = new Set();
|
|
173
|
+
for (const server of value.servers) {
|
|
174
|
+
if (!record(server) ||
|
|
175
|
+
!text(server.id, 512) ||
|
|
176
|
+
!text(server.name, 256) ||
|
|
177
|
+
servers.has(server.id) ||
|
|
178
|
+
!onlyKeys(server, ['id', 'name', 'description', 'instructions']) ||
|
|
179
|
+
(server.description !== undefined &&
|
|
180
|
+
(typeof server.description !== 'string' || server.description.length > 65536)) ||
|
|
181
|
+
(server.instructions !== undefined &&
|
|
182
|
+
(typeof server.instructions !== 'string' || server.instructions.length > 65536)))
|
|
183
|
+
invalid();
|
|
184
|
+
servers.add(server.id);
|
|
185
|
+
}
|
|
186
|
+
const names = new Set();
|
|
187
|
+
for (const tool of value.tools) {
|
|
188
|
+
if (!record(tool) ||
|
|
189
|
+
!text(tool.serverId, 512) ||
|
|
190
|
+
!servers.has(tool.serverId) ||
|
|
191
|
+
!onlyKeys(tool, [
|
|
192
|
+
'definition',
|
|
193
|
+
'serverId',
|
|
194
|
+
'rawName',
|
|
195
|
+
'sourceIdentity',
|
|
196
|
+
'definitionRevision',
|
|
197
|
+
'lifetime',
|
|
198
|
+
'exposure',
|
|
199
|
+
'annotations'
|
|
200
|
+
]) ||
|
|
201
|
+
!text(tool.rawName, 256) ||
|
|
202
|
+
!text(tool.sourceIdentity, 512) ||
|
|
203
|
+
!text(tool.definitionRevision, 512) ||
|
|
204
|
+
!['session', 'execution'].includes(String(tool.lifetime)) ||
|
|
205
|
+
!['direct', 'deferred'].includes(String(tool.exposure)))
|
|
206
|
+
invalid();
|
|
207
|
+
assertMcpToolDefinition(tool.definition);
|
|
208
|
+
if (tool.annotations !== undefined) {
|
|
209
|
+
if (!record(tool.annotations))
|
|
210
|
+
invalid();
|
|
211
|
+
assertJson(tool.annotations);
|
|
212
|
+
}
|
|
213
|
+
if (names.has(tool.definition.name))
|
|
214
|
+
invalid();
|
|
215
|
+
names.add(tool.definition.name);
|
|
216
|
+
}
|
|
217
|
+
if (Buffer.byteLength(JSON.stringify(value)) > MCP_DEFINITION_LIMITS.catalogBytes)
|
|
218
|
+
invalid();
|
|
219
|
+
}
|
package/dist/sdk/types.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { HostDescription } from '../host/contracts.js';
|
|
2
2
|
import type { MarAgentModelConfiguration, MarAgentReasoningEffort } from '../model/configuration.js';
|
|
3
|
+
import type { ClientToolDefinition } from '../model/contracts.js';
|
|
3
4
|
import type { MarAgentSessionPage, MarAgentSessionSummary } from '../session/catalog.js';
|
|
4
5
|
import type { SessionListInput, SessionSource } from '../session/types.js';
|
|
6
|
+
import type { CodeModeStateEntry, CodeModeStateMutation } from '../tools/code-mode/state.js';
|
|
5
7
|
/** @deprecated MAR Agent always executes tools directly through the Host. */
|
|
6
8
|
export type AccessMode = 'full-access';
|
|
7
9
|
export type ExecutionMode = 'run' | 'plan' | 'compact';
|
|
@@ -22,6 +24,8 @@ export interface ToolExecutionContext {
|
|
|
22
24
|
codeMode?: {
|
|
23
25
|
supportsImages?: boolean;
|
|
24
26
|
tools: readonly import('../model/contracts.js').ClientToolDefinition[];
|
|
27
|
+
state?: readonly CodeModeStateEntry[];
|
|
28
|
+
commitState?(mutations: readonly CodeModeStateMutation[]): Promise<readonly CodeModeStateEntry[]>;
|
|
25
29
|
invoke(call: ToolCall, signal: AbortSignal): Promise<ToolExecutionResult>;
|
|
26
30
|
};
|
|
27
31
|
}
|
|
@@ -29,6 +33,34 @@ export interface McpToolCatalogContext extends Pick<ToolExecutionContext, 'sessi
|
|
|
29
33
|
executionScopeId?: string;
|
|
30
34
|
claimExecutionScope?: boolean;
|
|
31
35
|
}
|
|
36
|
+
export interface McpToolCatalog {
|
|
37
|
+
readonly revision: string;
|
|
38
|
+
readonly servers: readonly {
|
|
39
|
+
readonly id: string;
|
|
40
|
+
readonly name: string;
|
|
41
|
+
readonly description?: string;
|
|
42
|
+
readonly instructions?: string;
|
|
43
|
+
}[];
|
|
44
|
+
readonly tools: readonly McpCatalogTool[];
|
|
45
|
+
}
|
|
46
|
+
export interface McpCatalogTool {
|
|
47
|
+
readonly definition: import('../model/contracts.js').ClientToolDefinition;
|
|
48
|
+
readonly serverId: string;
|
|
49
|
+
readonly rawName: string;
|
|
50
|
+
readonly sourceIdentity: string;
|
|
51
|
+
readonly definitionRevision: string;
|
|
52
|
+
readonly lifetime: 'session' | 'execution';
|
|
53
|
+
readonly exposure: 'direct' | 'deferred';
|
|
54
|
+
/** Untrusted MCP hints; never an authorization or parallel-safety decision. */
|
|
55
|
+
readonly annotations?: Readonly<Record<string, unknown>>;
|
|
56
|
+
}
|
|
57
|
+
export interface McpToolCallContext extends Pick<ToolExecutionContext, 'sessionId' | 'executionId'> {
|
|
58
|
+
readonly mode?: ExecutionMode;
|
|
59
|
+
readonly expectedTool?: {
|
|
60
|
+
readonly sourceIdentity: string;
|
|
61
|
+
readonly definitionRevision: string;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
32
64
|
export interface HostSkill {
|
|
33
65
|
readonly name: string;
|
|
34
66
|
readonly description: string;
|
|
@@ -51,6 +83,8 @@ export type MarAgentToolArtifact = MarAgentToolImageArtifact;
|
|
|
51
83
|
export interface ToolExecutionResult {
|
|
52
84
|
content: string;
|
|
53
85
|
data?: unknown;
|
|
86
|
+
/** Runtime-only definitions used to encode a Tool Search output. */
|
|
87
|
+
toolSearchTools?: readonly ClientToolDefinition[];
|
|
54
88
|
truncated?: boolean;
|
|
55
89
|
images?: readonly {
|
|
56
90
|
mimeType: string;
|
|
@@ -78,7 +112,8 @@ export interface MarAgentHost {
|
|
|
78
112
|
questions: unknown[];
|
|
79
113
|
}, signal: AbortSignal): Promise<Record<string, unknown>>;
|
|
80
114
|
listMcpTools?(context?: McpToolCatalogContext): Promise<readonly import('../model/contracts.js').ClientToolDefinition[]>;
|
|
81
|
-
|
|
115
|
+
listMcpToolCatalog?(context: McpToolCatalogContext): Promise<McpToolCatalog>;
|
|
116
|
+
callMcpTool?(call: ToolCall, signal: AbortSignal, context?: McpToolCallContext): Promise<ToolExecutionResult>;
|
|
82
117
|
listSessionResources?(sessionId: string): Promise<HostSessionResourceSnapshot>;
|
|
83
118
|
disposeSessionResources?(sessionId: string): Promise<void>;
|
|
84
119
|
dispose(): Promise<void>;
|
|
@@ -106,6 +141,20 @@ export type MarAgentEvent = MarAgentEventBase & ({
|
|
|
106
141
|
text: string;
|
|
107
142
|
modelId: string;
|
|
108
143
|
modelName: string;
|
|
144
|
+
} | {
|
|
145
|
+
type: 'model.performance';
|
|
146
|
+
ttftMs: number;
|
|
147
|
+
outputTokens?: number | undefined;
|
|
148
|
+
outputDurationMs?: number | undefined;
|
|
149
|
+
tokensPerSecond?: number | undefined;
|
|
150
|
+
} | {
|
|
151
|
+
/** Legacy history event; new executions publish model.performance instead. */
|
|
152
|
+
type: 'message.performance';
|
|
153
|
+
itemId: string;
|
|
154
|
+
ttftMs: number;
|
|
155
|
+
outputTokens?: number | undefined;
|
|
156
|
+
outputDurationMs?: number | undefined;
|
|
157
|
+
tokensPerSecond?: number | undefined;
|
|
109
158
|
} | {
|
|
110
159
|
type: 'plan.completed';
|
|
111
160
|
itemId: 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';
|
|
53
|
+
readonly toolName: 'apply_patch' | 'exec' | 'code_mode';
|
|
54
54
|
readonly callId: string;
|
|
55
55
|
readonly target: ContextGcTarget;
|
|
56
56
|
readonly replacementArguments: unknown;
|
|
@@ -62,6 +62,12 @@ export type ContextGcReplacement = {
|
|
|
62
62
|
readonly target: ContextGcTarget;
|
|
63
63
|
readonly replacementContent: string;
|
|
64
64
|
readonly evidenceSequence: number;
|
|
65
|
+
} | {
|
|
66
|
+
readonly kind: 'CODE_MODE_OUTPUT';
|
|
67
|
+
readonly callId: string;
|
|
68
|
+
readonly target: ContextGcTarget;
|
|
69
|
+
readonly replacementContent: string;
|
|
70
|
+
readonly evidenceSequence: number;
|
|
65
71
|
} | {
|
|
66
72
|
readonly kind: 'SUBAGENT_OUTPUT';
|
|
67
73
|
readonly toolName: 'agent_wait' | 'agent_message' | 'agent_cancel';
|
|
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { posix, win32 } from 'node:path';
|
|
3
3
|
import { isDeepStrictEqual } from 'node:util';
|
|
4
4
|
import { parseReadContextOutput } from '../tools/read-context.js';
|
|
5
|
-
import { contextGcApplyPatchInputReplacement,
|
|
5
|
+
import { contextGcApplyPatchInputReplacement, contextGcCodeModeInputReplacement, contextGcCodeModeOutputReplacement, contextGcExecInputReplacement, contextGcExecOutputReplacement, contextGcPatchResultFromCompletedPayload, contextGcStaleReadReplacement, contextGcWebOutputReplacement, normalizeContextGcCodeModeInput, normalizeContextGcExecResult } from './context-gc-replacements.js';
|
|
6
6
|
import { contextGcSubagentNeedsRetainedPriorFinal, contextGcSubagentOutputReplacement, contextGcSubagentSnapshotIsPreciselyCovered, contextGcSubagentSnapshotIsSuperseded, normalizeContextGcSubagentSnapshot } from './context-gc-subagent.js';
|
|
7
7
|
export const CONTEXT_GC_CHECKPOINT_VERSION = 1;
|
|
8
8
|
export const CONTEXT_GC_MAX_TARGETS = 4_096;
|
|
@@ -266,6 +266,25 @@ function replacementMatchesRecords(replacement, records, recordsBySequence, work
|
|
|
266
266
|
result !== undefined &&
|
|
267
267
|
isDeepStrictEqual(replacement.replacementArguments, contextGcApplyPatchInputReplacement(source.payload.arguments, result, replacement.evidenceSequence, applyPatchReplacementFormatVersion(replacement.replacementArguments))));
|
|
268
268
|
}
|
|
269
|
+
if (replacement.kind === 'TOOL_CALL_INPUT' && replacement.toolName === 'code_mode') {
|
|
270
|
+
const evidence = successfulToolEvidence(records, recordsBySequence, replacement.evidenceSequence, 'code_mode');
|
|
271
|
+
const input = normalizeContextGcCodeModeInput(source.payload.arguments);
|
|
272
|
+
return (evidence?.callId === replacement.callId &&
|
|
273
|
+
evidence.call.sequence === replacement.target.sequence &&
|
|
274
|
+
input !== undefined &&
|
|
275
|
+
isDeepStrictEqual(replacement.replacementArguments, contextGcCodeModeInputReplacement(input)));
|
|
276
|
+
}
|
|
277
|
+
if (replacement.kind === 'CODE_MODE_OUTPUT') {
|
|
278
|
+
const evidence = successfulToolEvidence(records, recordsBySequence, replacement.evidenceSequence, 'code_mode');
|
|
279
|
+
return (evidence?.callId === replacement.callId &&
|
|
280
|
+
evidence.result.sequence === replacement.target.sequence &&
|
|
281
|
+
isRecord(evidence.result.payload) &&
|
|
282
|
+
typeof evidence.result.payload.content === 'string' &&
|
|
283
|
+
isRecord(evidence.completed.payload) &&
|
|
284
|
+
typeof evidence.completed.payload.truncated === 'boolean' &&
|
|
285
|
+
replacement.replacementContent ===
|
|
286
|
+
contextGcCodeModeOutputReplacement(evidence.result.payload.content, evidence.completed.payload.truncated));
|
|
287
|
+
}
|
|
269
288
|
if (replacement.kind === 'SUBAGENT_OUTPUT') {
|
|
270
289
|
const retainedPriorFinal = replacement.retainedPriorFinal;
|
|
271
290
|
const sourceEvidence = successfulToolEvidence(records, recordsBySequence, source.sequence, replacement.toolName);
|
|
@@ -605,7 +624,9 @@ function isContextGcReplacement(value) {
|
|
|
605
624
|
'evidenceSequence'
|
|
606
625
|
]) &&
|
|
607
626
|
value.target.role === 'tool_call' &&
|
|
608
|
-
(value.toolName === 'apply_patch' ||
|
|
627
|
+
(value.toolName === 'apply_patch' ||
|
|
628
|
+
value.toolName === 'exec' ||
|
|
629
|
+
value.toolName === 'code_mode') &&
|
|
609
630
|
value.target.toolName === value.toolName &&
|
|
610
631
|
Array.isArray(value.providerAliasTargets) &&
|
|
611
632
|
value.providerAliasTargets.every((target) => isContextGcTarget(target) &&
|
|
@@ -661,6 +682,11 @@ function isContextGcReplacement(value) {
|
|
|
661
682
|
(value.target.toolName === undefined || value.target.toolName === value.toolName) &&
|
|
662
683
|
isNonemptyString(value.replacementContent) &&
|
|
663
684
|
isPositiveInteger(value.evidenceSequence));
|
|
685
|
+
if (value.kind === 'CODE_MODE_OUTPUT')
|
|
686
|
+
return (hasOnlyKeys(value, ['kind', 'callId', 'target', 'replacementContent', 'evidenceSequence']) &&
|
|
687
|
+
value.target.role === 'tool' &&
|
|
688
|
+
isNonemptyString(value.replacementContent) &&
|
|
689
|
+
isPositiveInteger(value.evidenceSequence));
|
|
664
690
|
return (value.kind === 'EXEC_OUTPUT' &&
|
|
665
691
|
hasOnlyKeys(value, ['kind', 'callId', 'target', 'replacementContent', 'evidenceSequence']) &&
|
|
666
692
|
value.target.role === 'tool' &&
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
export type ContextGcWebToolName = 'web_search' | 'web_fetch';
|
|
2
|
+
export interface ContextGcCodeModeInput {
|
|
3
|
+
readonly code: string;
|
|
4
|
+
readonly inputMode: 'freeform' | 'json';
|
|
5
|
+
}
|
|
6
|
+
export declare function normalizeContextGcCodeModeInput(arguments_: unknown): ContextGcCodeModeInput | undefined;
|
|
7
|
+
export declare function contextGcCodeModeInputReplacement(input: ContextGcCodeModeInput): unknown;
|
|
8
|
+
export declare function contextGcCodeModeOutputReplacement(originalContent: string, truncated: boolean): string;
|
|
2
9
|
export interface ContextGcPatchFile {
|
|
3
10
|
readonly path: string;
|
|
4
11
|
readonly targetPath?: string;
|
|
@@ -3,6 +3,32 @@ const RAW_MODEL_CONTEXT_PREVIEW_CHARACTERS = 256;
|
|
|
3
3
|
const EXEC_INPUT_COMPLETE_CHARACTERS = 512;
|
|
4
4
|
const EXEC_INPUT_PREVIEW_CHARACTERS = 256;
|
|
5
5
|
const EXEC_OUTPUT_PREVIEW_CHARACTERS = 512;
|
|
6
|
+
const CODE_MODE_OUTPUT_PREVIEW_CHARACTERS = 512;
|
|
7
|
+
export function normalizeContextGcCodeModeInput(arguments_) {
|
|
8
|
+
if (typeof arguments_ === 'string' && arguments_.length > 0)
|
|
9
|
+
return { code: arguments_, inputMode: 'freeform' };
|
|
10
|
+
if (isRecord(arguments_) &&
|
|
11
|
+
Object.keys(arguments_).length === 1 &&
|
|
12
|
+
typeof arguments_.code === 'string' &&
|
|
13
|
+
arguments_.code.length > 0)
|
|
14
|
+
return { code: arguments_.code, inputMode: 'json' };
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
export function contextGcCodeModeInputReplacement(input) {
|
|
18
|
+
const replacement = `<context_gc kind="completed_code_mode_input" originalBytes="${Buffer.byteLength(input.code)}" lines="${lineCount(input.code)}" inputMode="${input.inputMode}">Completed Code Mode source was reclaimed after six successful rollouts. The paired output, images, completion evidence, and nested tool side effects remain in history.</context_gc>`;
|
|
19
|
+
return input.inputMode === 'freeform' ? replacement : { code: replacement };
|
|
20
|
+
}
|
|
21
|
+
export function contextGcCodeModeOutputReplacement(originalContent, truncated) {
|
|
22
|
+
const preview = unicodeHeadTail(originalContent, CODE_MODE_OUTPUT_PREVIEW_CHARACTERS);
|
|
23
|
+
return JSON.stringify({
|
|
24
|
+
originalBytes: Buffer.byteLength(originalContent),
|
|
25
|
+
lines: lineCount(originalContent),
|
|
26
|
+
truncated,
|
|
27
|
+
preview,
|
|
28
|
+
previewComplete: !truncated && preview === originalContent,
|
|
29
|
+
contextGc: 'Detailed Code Mode output was reclaimed after six successful rollouts. An incomplete preview is not the complete cell output. The source was not replayed and nested tool side effects were not changed.'
|
|
30
|
+
});
|
|
31
|
+
}
|
|
6
32
|
export function contextGcPatchResultFromCompletedPayload(payload) {
|
|
7
33
|
if (!isRecord(payload) || payload.type !== 'tool.completed')
|
|
8
34
|
return undefined;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ModelUsage } from '../model/contracts.js';
|
|
2
2
|
import type { EnvironmentContextState } from '../prompts/core.js';
|
|
3
|
+
import { type CodeModeStateEntry } from '../tools/code-mode/state.js';
|
|
3
4
|
import { type TodoSnapshot } from '../tools/todo.js';
|
|
4
5
|
import { type ContextGcCheckpointDraft } from './context-gc-checkpoint.js';
|
|
5
6
|
import type { SessionListInput, SessionSource } from './types.js';
|
|
@@ -146,6 +147,11 @@ export declare class JsonlSessionStore {
|
|
|
146
147
|
timestamp?: string;
|
|
147
148
|
}): Promise<SessionRecord>;
|
|
148
149
|
read(sessionId: string): Promise<SessionRecord[]>;
|
|
150
|
+
readCodeModeState(sessionId: string): Promise<CodeModeStateEntry[]>;
|
|
151
|
+
commitCodeModeState(sessionId: string, mutations: unknown, input: {
|
|
152
|
+
readonly executionId: string;
|
|
153
|
+
readonly turnId: string;
|
|
154
|
+
}): Promise<CodeModeStateEntry[]>;
|
|
149
155
|
readExecutionState(sessionId: string): Promise<SessionExecutionState>;
|
|
150
156
|
readContext(sessionId: string): Promise<SessionRecord[]>;
|
|
151
157
|
contextGcProjectionHash(sessionId: string, boundarySequence: number): Promise<string>;
|
|
@@ -5,6 +5,7 @@ import { basename, join } from 'node:path';
|
|
|
5
5
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
6
6
|
import { isDeepStrictEqual } from 'node:util';
|
|
7
7
|
import { MarAgentError } from '../error.js';
|
|
8
|
+
import { applyCodeModeStateMutations, parseCodeModeStateMutations, restoreCodeModeState } from '../tools/code-mode/state.js';
|
|
8
9
|
import { todoItemsFromSnapshot } from '../tools/todo.js';
|
|
9
10
|
import { contextGcBaseFromRecords, contextGcBaseSequence, contextGcMinimumUsageGrowth, contextGcSourceProjectionHash, latestValidContextGcCheckpoint, normalizeContextGcReplacements, parseContextGcCheckpoint, sameContextGcBase, validateContextGcCheckpoint } from './context-gc-checkpoint.js';
|
|
10
11
|
import { beginSessionStorageTransaction, isUnjournaledTransactionFile, recoverSessionStorageTransaction } from './transaction-recovery.js';
|
|
@@ -521,20 +522,31 @@ export class JsonlSessionStore {
|
|
|
521
522
|
this.#shared.pendingSessions.set(target.id, target);
|
|
522
523
|
let discard = false;
|
|
523
524
|
try {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
525
|
+
const copied = [
|
|
526
|
+
{
|
|
527
|
+
schemaVersion: 1,
|
|
528
|
+
recordId: randomUUID(),
|
|
529
|
+
sequence: 1,
|
|
530
|
+
timestamp: createdAt,
|
|
531
|
+
sessionId: target.id,
|
|
532
|
+
type: 'session.created',
|
|
533
|
+
payload: {
|
|
534
|
+
title: target.title,
|
|
535
|
+
workspace: target.workspace,
|
|
536
|
+
automaticTitle: false,
|
|
537
|
+
source: target.source
|
|
538
|
+
}
|
|
531
539
|
}
|
|
532
|
-
|
|
540
|
+
];
|
|
533
541
|
const retained = input.boundaryTurnId === null ? [] : records.slice(1, boundaryIndex + 1);
|
|
534
542
|
for (const record of retained) {
|
|
535
543
|
if (record.type === 'context.gc.completed' || record.type === 'responses.continuation')
|
|
536
544
|
continue;
|
|
537
|
-
|
|
545
|
+
copied.push({
|
|
546
|
+
schemaVersion: 1,
|
|
547
|
+
recordId: randomUUID(),
|
|
548
|
+
sequence: copied.length + 1,
|
|
549
|
+
sessionId: target.id,
|
|
538
550
|
type: record.type,
|
|
539
551
|
payload: forkedPayload(record.type, record.payload, sourceSessionId, target.id, input.title),
|
|
540
552
|
...(record.executionId === undefined ? {} : { executionId: record.executionId }),
|
|
@@ -542,6 +554,15 @@ export class JsonlSessionStore {
|
|
|
542
554
|
timestamp: record.timestamp
|
|
543
555
|
});
|
|
544
556
|
}
|
|
557
|
+
// A fork is unpublished until every segment and referenced image is durable.
|
|
558
|
+
const staged = await stageSessionSegments(this.sessionDirectory(target.id), copied, 'fork');
|
|
559
|
+
for (const file of staged.files)
|
|
560
|
+
await rename(file.temporaryPath, file.path);
|
|
561
|
+
target.segments = staged.segments;
|
|
562
|
+
target.nextSequence = copied.length + 1;
|
|
563
|
+
this.#shared.segments.set(target.id, staged.segments.length);
|
|
564
|
+
for (const record of copied)
|
|
565
|
+
this.#applyRecordToResumeIndex(target, record);
|
|
545
566
|
await copyGeneratedImages(this.sessionDirectory(sourceSessionId), this.sessionDirectory(target.id), generatedImageFileNames(retained));
|
|
546
567
|
await copyInputImages(this.sessionDirectory(sourceSessionId), this.sessionDirectory(target.id), inputImageReferencesFromRecords(retained));
|
|
547
568
|
target.lastActivityAt = target.createdAt;
|
|
@@ -621,7 +642,7 @@ export class JsonlSessionStore {
|
|
|
621
642
|
const retained = records
|
|
622
643
|
.slice(0, boundaryIndex + 1)
|
|
623
644
|
.filter((record) => record.type !== 'responses.continuation');
|
|
624
|
-
const staged = await
|
|
645
|
+
const staged = await stageSessionSegments(this.sessionDirectory(sessionId), retained);
|
|
625
646
|
const transactionId = randomUUID();
|
|
626
647
|
const targetNextSequence = (retained.at(-1)?.sequence ?? 0) + 1;
|
|
627
648
|
const contextualWorldStatePath = join(this.sessionDirectory(sessionId), CONTEXTUAL_WORLD_STATE_FILE);
|
|
@@ -765,7 +786,7 @@ export class JsonlSessionStore {
|
|
|
765
786
|
}
|
|
766
787
|
}
|
|
767
788
|
];
|
|
768
|
-
const staged = await
|
|
789
|
+
const staged = await stageSessionSegments(this.sessionDirectory(sessionId), retained);
|
|
769
790
|
const transactionId = randomUUID();
|
|
770
791
|
const contextualWorldStatePath = join(this.sessionDirectory(sessionId), CONTEXTUAL_WORLD_STATE_FILE);
|
|
771
792
|
const contextualWorldStateBackup = `${contextualWorldStatePath}.2.${transactionId}.clear-backup`;
|
|
@@ -966,6 +987,22 @@ export class JsonlSessionStore {
|
|
|
966
987
|
throw new MarAgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.', { cause });
|
|
967
988
|
}
|
|
968
989
|
}
|
|
990
|
+
async readCodeModeState(sessionId) {
|
|
991
|
+
return restoreCodeModeState(await this.read(sessionId));
|
|
992
|
+
}
|
|
993
|
+
async commitCodeModeState(sessionId, mutations, input) {
|
|
994
|
+
const parsed = parseCodeModeStateMutations(mutations);
|
|
995
|
+
if (parsed.length === 0)
|
|
996
|
+
return this.readCodeModeState(sessionId);
|
|
997
|
+
const state = applyCodeModeStateMutations(await this.readCodeModeState(sessionId), parsed);
|
|
998
|
+
await this.append(sessionId, {
|
|
999
|
+
type: 'code.state.updated',
|
|
1000
|
+
payload: { mutations: parsed },
|
|
1001
|
+
executionId: input.executionId,
|
|
1002
|
+
turnId: input.turnId
|
|
1003
|
+
});
|
|
1004
|
+
return state;
|
|
1005
|
+
}
|
|
969
1006
|
async readExecutionState(sessionId) {
|
|
970
1007
|
this.#assertOpen();
|
|
971
1008
|
const meta = this.#shared.index.sessions.find((item) => item.id === sessionId);
|
|
@@ -1637,55 +1674,56 @@ function generatedImageFileNames(records) {
|
|
|
1637
1674
|
}
|
|
1638
1675
|
return [...names];
|
|
1639
1676
|
}
|
|
1640
|
-
async function
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
const line = `${JSON.stringify(record)}\n`;
|
|
1644
|
-
const bytes = Buffer.byteLength(line);
|
|
1645
|
-
let group = groups.at(-1);
|
|
1646
|
-
if (group !== undefined && group.bytes > 0 && group.bytes + bytes > MAX_SEGMENT_BYTES)
|
|
1647
|
-
group = undefined;
|
|
1648
|
-
if (group === undefined) {
|
|
1649
|
-
group = {
|
|
1650
|
-
lines: [],
|
|
1651
|
-
bytes: 0,
|
|
1652
|
-
firstSequence: record.sequence,
|
|
1653
|
-
lastSequence: record.sequence
|
|
1654
|
-
};
|
|
1655
|
-
groups.push(group);
|
|
1656
|
-
}
|
|
1657
|
-
group.lines.push(line);
|
|
1658
|
-
group.bytes += bytes;
|
|
1659
|
-
group.lastSequence = record.sequence;
|
|
1660
|
-
}
|
|
1661
|
-
if (groups.length === 0)
|
|
1677
|
+
async function stageSessionSegments(sessionDirectory, records, operation = 'rollback') {
|
|
1678
|
+
let group;
|
|
1679
|
+
if (records.length === 0)
|
|
1662
1680
|
throw new MarAgentError('MAR_AGENT_SESSION_CORRUPT', 'Session history is corrupt.');
|
|
1663
1681
|
const transactionId = randomUUID();
|
|
1664
1682
|
const files = [];
|
|
1683
|
+
const segments = [];
|
|
1684
|
+
const flush = async () => {
|
|
1685
|
+
if (group === undefined)
|
|
1686
|
+
return;
|
|
1687
|
+
const number = segments.length + 1;
|
|
1688
|
+
const path = join(sessionDirectory, `events-${String(number).padStart(6, '0')}.jsonl`);
|
|
1689
|
+
const temporaryPath = `${path}.${transactionId}.${operation}-tmp`;
|
|
1690
|
+
const handle = await open(temporaryPath, 'wx', 0o600);
|
|
1691
|
+
files.push({ path, temporaryPath });
|
|
1692
|
+
try {
|
|
1693
|
+
await handle.writeFile(group.lines.join(''));
|
|
1694
|
+
await handle.sync();
|
|
1695
|
+
}
|
|
1696
|
+
finally {
|
|
1697
|
+
await handle.close();
|
|
1698
|
+
}
|
|
1699
|
+
segments.push({
|
|
1700
|
+
number,
|
|
1701
|
+
firstSequence: group.firstSequence,
|
|
1702
|
+
lastSequence: group.lastSequence,
|
|
1703
|
+
bytes: group.bytes
|
|
1704
|
+
});
|
|
1705
|
+
group = undefined;
|
|
1706
|
+
};
|
|
1665
1707
|
try {
|
|
1666
|
-
for (const
|
|
1667
|
-
const
|
|
1668
|
-
const
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1708
|
+
for (const record of records) {
|
|
1709
|
+
const line = `${JSON.stringify(record)}\n`;
|
|
1710
|
+
const bytes = Buffer.byteLength(line);
|
|
1711
|
+
if (group !== undefined && group.bytes > 0 && group.bytes + bytes > MAX_SEGMENT_BYTES)
|
|
1712
|
+
await flush();
|
|
1713
|
+
if (group === undefined) {
|
|
1714
|
+
group = {
|
|
1715
|
+
lines: [],
|
|
1716
|
+
bytes: 0,
|
|
1717
|
+
firstSequence: record.sequence,
|
|
1718
|
+
lastSequence: record.sequence
|
|
1719
|
+
};
|
|
1677
1720
|
}
|
|
1678
|
-
|
|
1721
|
+
group.lines.push(line);
|
|
1722
|
+
group.bytes += bytes;
|
|
1723
|
+
group.lastSequence = record.sequence;
|
|
1679
1724
|
}
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
segments: groups.map((group, index) => ({
|
|
1683
|
-
number: index + 1,
|
|
1684
|
-
firstSequence: group.firstSequence,
|
|
1685
|
-
lastSequence: group.lastSequence,
|
|
1686
|
-
bytes: group.bytes
|
|
1687
|
-
}))
|
|
1688
|
-
};
|
|
1725
|
+
await flush();
|
|
1726
|
+
return { files, segments };
|
|
1689
1727
|
}
|
|
1690
1728
|
catch (error) {
|
|
1691
1729
|
await Promise.all(files.map((item) => rm(item.temporaryPath, { force: true }))).catch(() => undefined);
|