@myagentroam/agent 0.11.55 → 0.11.57
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/runtime/context/gc-candidates-code.js +5 -6
- package/dist/runtime/context/gc-candidates-exec.js +2 -2
- package/dist/runtime/context/gc-candidates-read-patch.js +3 -3
- package/dist/runtime/context/gc-candidates-web.js +6 -8
- package/dist/runtime/context/gc-economics.d.ts +13 -19
- package/dist/runtime/context/gc-economics.js +18 -66
- package/dist/runtime/context/gc.d.ts +1 -1
- package/dist/runtime/context/gc.js +5 -91
- package/dist/session/context-gc-checkpoint.js +12 -12
- package/dist/session/context-gc-contract.d.ts +5 -4
- package/dist/session/context-gc-protection.d.ts +5 -0
- package/dist/session/context-gc-protection.js +4 -0
- package/dist/session/context-gc-replacements.js +4 -4
- package/dist/session/store/jsonl/fork-session.js +38 -3
- package/dist/session/store/jsonl/history-replacement.d.ts +2 -1
- package/dist/session/store/jsonl/history-replacement.js +16 -5
- package/dist/session/store/jsonl/history.d.ts +5 -0
- package/dist/session/store/jsonl/history.js +84 -24
- package/dist/session/store/jsonl/jsonl-store.js +25 -8
- package/dist/session/store/jsonl/transaction-files.d.ts +2 -0
- package/dist/session/store/jsonl/transaction-files.js +22 -0
- package/dist/session/store/jsonl/transaction-recovery.js +11 -14
- package/dist/session/tool-result-resources.d.ts +10 -1
- package/dist/session/tool-result-resources.js +109 -38
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import { lstat, mkdir, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises';
|
|
3
|
-
import { join } from 'node:path';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
4
|
import { AgentError } from '../error.js';
|
|
5
5
|
import { parseToolResultExtensions } from '../tools/result-extensions.js';
|
|
6
6
|
const REF = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/u;
|
|
@@ -73,13 +73,8 @@ export async function bindToolResultResources(directory, record, readRecord, deg
|
|
|
73
73
|
async function bindToolResultResource(directory, record, resource, readRecord) {
|
|
74
74
|
const path = join(directory, 'tool_results', `${resource.resourceRef}.json`);
|
|
75
75
|
try {
|
|
76
|
-
await
|
|
77
|
-
|
|
78
|
-
catch (error) {
|
|
79
|
-
if (error.code !== 'EEXIST')
|
|
80
|
-
throw error;
|
|
81
|
-
try {
|
|
82
|
-
const locator = JSON.parse(await readFile(path, 'utf8'));
|
|
76
|
+
const locator = await readToolResourceLocator(path, resource.resourceRef);
|
|
77
|
+
if (locator !== undefined) {
|
|
83
78
|
const existing = locator.sequence === record.sequence && locator.recordId === record.recordId
|
|
84
79
|
? record
|
|
85
80
|
: Number.isSafeInteger(locator.sequence)
|
|
@@ -89,46 +84,103 @@ async function bindToolResultResource(directory, record, resource, readRecord) {
|
|
|
89
84
|
existing.recordId !== locator.recordId ||
|
|
90
85
|
!recordToolResources(existing).some((entry) => entry.resourceRef === resource.resourceRef && entry.sha256 === resource.sha256))
|
|
91
86
|
throw resourceError('CORRUPT', 'Tool resource locator conflicts with committed history.');
|
|
87
|
+
return;
|
|
92
88
|
}
|
|
93
|
-
|
|
89
|
+
}
|
|
90
|
+
catch (cause) {
|
|
91
|
+
if (cause.code !== 'ENOENT')
|
|
94
92
|
throw normalizedResourceError(cause);
|
|
95
|
-
}
|
|
96
93
|
}
|
|
94
|
+
await writeFile(path, JSON.stringify({ sequence: record.sequence, recordId: record.recordId }), {
|
|
95
|
+
flag: 'wx',
|
|
96
|
+
mode: 0o600,
|
|
97
|
+
flush: true
|
|
98
|
+
});
|
|
97
99
|
}
|
|
98
100
|
export function toolResultResourceFiles(records) {
|
|
99
|
-
|
|
101
|
+
const files = new Set(records.flatMap((record) => recordToolResources(record).flatMap((resource) => [
|
|
100
102
|
`${resource.resourceRef}.diff`,
|
|
101
103
|
`${resource.resourceRef}.json`
|
|
102
104
|
])));
|
|
105
|
+
if (files.size > 0)
|
|
106
|
+
files.add('index.json');
|
|
107
|
+
return files;
|
|
103
108
|
}
|
|
104
|
-
export async function
|
|
105
|
-
const
|
|
109
|
+
export async function prepareToolResultResources(source, records) {
|
|
110
|
+
const resources = new Map();
|
|
106
111
|
for (const record of records) {
|
|
107
112
|
for (const resource of recordToolResources(record)) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
113
|
+
const previous = resources.get(resource.resourceRef);
|
|
114
|
+
if (previous && previous.resource.sha256 !== resource.sha256)
|
|
115
|
+
throw resourceError('CORRUPT', 'Tool resource locator conflicts with committed history.');
|
|
116
|
+
if (!previous)
|
|
117
|
+
resources.set(resource.resourceRef, { record, resource });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const bodies = new Map();
|
|
121
|
+
if (resources.size === 0)
|
|
122
|
+
return bodies;
|
|
123
|
+
const bySequence = new Map(records.map((record) => [record.sequence, record]));
|
|
124
|
+
const pending = resources.values();
|
|
125
|
+
let failure;
|
|
126
|
+
const read = async () => {
|
|
127
|
+
while (!failure) {
|
|
128
|
+
const next = pending.next();
|
|
129
|
+
if (next.done)
|
|
130
|
+
return;
|
|
131
|
+
const { record, resource } = next.value;
|
|
111
132
|
try {
|
|
112
|
-
const bytes = await readResourceBytes(source, resource);
|
|
113
|
-
|
|
114
|
-
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
115
|
-
await writeFile(join(directory, `${resource.resourceRef}.diff`), bytes, {
|
|
116
|
-
flag: 'wx',
|
|
117
|
-
mode: 0o600,
|
|
118
|
-
flush: true
|
|
119
|
-
});
|
|
133
|
+
const bytes = await readResourceBytes(source, resource, async (sequence) => bySequence.get(sequence));
|
|
134
|
+
bodies.set(resource.resourceRef, { owner: record.recordId, bytes });
|
|
120
135
|
}
|
|
121
136
|
catch (error) {
|
|
122
|
-
if (error instanceof AgentError && error.code === 'MAR_AGENT_TOOL_RESOURCE_NOT_FOUND')
|
|
123
|
-
|
|
124
|
-
throw error;
|
|
137
|
+
if (!(error instanceof AgentError && error.code === 'MAR_AGENT_TOOL_RESOURCE_NOT_FOUND'))
|
|
138
|
+
failure ??= { error };
|
|
125
139
|
}
|
|
126
140
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
141
|
+
};
|
|
142
|
+
await Promise.all(Array.from({ length: Math.min(8, resources.size) }, read));
|
|
143
|
+
if (failure)
|
|
144
|
+
throw failure.error;
|
|
145
|
+
return bodies;
|
|
146
|
+
}
|
|
147
|
+
export async function writeToolResultResourceIndex(target, locators) {
|
|
148
|
+
if (locators.size === 0)
|
|
149
|
+
return;
|
|
150
|
+
const directory = join(target, 'tool_results');
|
|
151
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
152
|
+
await writeFile(join(directory, 'index.json'), JSON.stringify({ version: 1, locators: Object.fromEntries(locators) }), {
|
|
153
|
+
flag: 'wx',
|
|
154
|
+
mode: 0o600,
|
|
155
|
+
flush: true
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
async function readToolResourceLocator(path, resourceRef) {
|
|
159
|
+
let text;
|
|
160
|
+
try {
|
|
161
|
+
text = await readFile(path, 'utf8');
|
|
131
162
|
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
if (error.code !== 'ENOENT')
|
|
165
|
+
throw error;
|
|
166
|
+
text = await readFile(join(dirname(path), 'index.json'), 'utf8');
|
|
167
|
+
}
|
|
168
|
+
const value = JSON.parse(text);
|
|
169
|
+
const locator = value?.version === 1 ? value.locators?.[resourceRef] : value;
|
|
170
|
+
if (value?.version === 1 && locator === undefined)
|
|
171
|
+
return undefined;
|
|
172
|
+
if (!Number.isSafeInteger(locator?.sequence) ||
|
|
173
|
+
locator.sequence < 1 ||
|
|
174
|
+
typeof locator.recordId !== 'string')
|
|
175
|
+
throw resourceError('CORRUPT', 'Invalid tool resource locator.');
|
|
176
|
+
if (locator.bodySequences !== undefined &&
|
|
177
|
+
(!Array.isArray(locator.bodySequences) ||
|
|
178
|
+
locator.bodySequences.length === 0 ||
|
|
179
|
+
!locator.bodySequences.every((sequence) => Number.isSafeInteger(sequence) &&
|
|
180
|
+
sequence > 0 &&
|
|
181
|
+
sequence < locator.sequence)))
|
|
182
|
+
throw resourceError('CORRUPT', 'Invalid tool resource body locator.');
|
|
183
|
+
return locator;
|
|
132
184
|
}
|
|
133
185
|
export async function completeToolResultResources(directory, record) {
|
|
134
186
|
await Promise.all(recordToolResources(record).map((resource) => rm(join(directory, 'tool_results', `.${resource.resourceRef}.pending`), {
|
|
@@ -177,22 +229,20 @@ export async function readToolResultResource(directory, sessionId, input, readRe
|
|
|
177
229
|
let locator;
|
|
178
230
|
try {
|
|
179
231
|
const file = join(directory, 'tool_results', `${input.resourceRef}.json`);
|
|
180
|
-
locator =
|
|
232
|
+
locator = await readToolResourceLocator(file, input.resourceRef);
|
|
181
233
|
}
|
|
182
234
|
catch (error) {
|
|
183
235
|
throw normalizedResourceError(error);
|
|
184
236
|
}
|
|
185
|
-
if (
|
|
186
|
-
|
|
187
|
-
typeof locator.recordId !== 'string')
|
|
188
|
-
throw resourceError('CORRUPT', 'Invalid tool resource locator.');
|
|
237
|
+
if (locator === undefined)
|
|
238
|
+
throw resourceError('NOT_FOUND', 'Tool resource is unavailable.');
|
|
189
239
|
const record = await readRecord(locator.sequence);
|
|
190
240
|
const resource = record?.recordId === locator.recordId
|
|
191
241
|
? recordToolResources(record).find((entry) => entry.resourceRef === input.resourceRef)
|
|
192
242
|
: undefined;
|
|
193
243
|
if (!resource)
|
|
194
244
|
throw resourceError('NOT_FOUND', 'Tool resource is not part of this session history.');
|
|
195
|
-
const bytes = await readResourceBytes(directory, resource);
|
|
245
|
+
const bytes = await readResourceBytes(directory, resource, readRecord, locator);
|
|
196
246
|
let offset = 0;
|
|
197
247
|
if (input.cursor !== undefined) {
|
|
198
248
|
try {
|
|
@@ -237,10 +287,31 @@ export async function readToolResultResource(directory, sessionId, input, readRe
|
|
|
237
287
|
: {})
|
|
238
288
|
};
|
|
239
289
|
}
|
|
240
|
-
async function readResourceBytes(directory, resource) {
|
|
290
|
+
async function readResourceBytes(directory, resource, readRecord, knownLocator) {
|
|
241
291
|
try {
|
|
242
292
|
return await readFile(join(directory, 'tool_results', `${resource.resourceRef}.diff`));
|
|
243
293
|
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
if (error.code !== 'ENOENT')
|
|
296
|
+
throw normalizedResourceError(error);
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
const locator = knownLocator ??
|
|
300
|
+
(await readToolResourceLocator(join(directory, 'tool_results', `${resource.resourceRef}.json`), resource.resourceRef));
|
|
301
|
+
if (!locator?.bodySequences || !readRecord)
|
|
302
|
+
throw resourceError('NOT_FOUND', 'Tool resource is unavailable.');
|
|
303
|
+
const chunks = [];
|
|
304
|
+
for (const sequence of locator.bodySequences) {
|
|
305
|
+
const record = await readRecord(sequence);
|
|
306
|
+
const payload = record?.payload;
|
|
307
|
+
if (record?.type !== 'tool.resource' ||
|
|
308
|
+
payload?.resourceRef !== resource.resourceRef ||
|
|
309
|
+
typeof payload.dataBase64 !== 'string')
|
|
310
|
+
throw resourceError('CORRUPT', 'Invalid tool resource body.');
|
|
311
|
+
chunks.push(Buffer.from(payload.dataBase64, 'base64'));
|
|
312
|
+
}
|
|
313
|
+
return Buffer.concat(chunks);
|
|
314
|
+
}
|
|
244
315
|
catch (error) {
|
|
245
316
|
throw normalizedResourceError(error);
|
|
246
317
|
}
|