@cydm/pie 1.0.18 → 1.0.20
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/builtin/extensions/ask-user/index.js +3 -3
- package/dist/builtin/extensions/plan-mode/index.js +3 -3
- package/dist/builtin/extensions/subagent/index.js +4 -4
- package/dist/builtin/extensions/todo/index.js +3 -3
- package/dist/chunks/{chunk-VE2HDCNB.js → chunk-L5BJNCNG.js} +1 -1
- package/dist/chunks/{chunk-G4GV2CRT.js → chunk-NHR6EBM2.js} +775 -181
- package/dist/chunks/{chunk-5DA2D3K2.js → chunk-VESPMEDG.js} +317 -0
- package/dist/chunks/{chunk-MHFUWY7I.js → chunk-ZYOTRKU7.js} +3 -2
- package/dist/chunks/{src-6WPNVGT2.js → src-2FHZO63Y.js} +2 -2
- package/dist/chunks/{test-stream-ZSKNLUEJ.js → test-stream-UW74PA6T.js} +1 -1
- package/dist/cli.js +749 -232
- package/package.json +4 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
Agent
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-L5BJNCNG.js";
|
|
5
5
|
import {
|
|
6
6
|
FileSystemGateway,
|
|
7
7
|
Type,
|
|
@@ -9,8 +9,9 @@ import {
|
|
|
9
9
|
detectPlatform,
|
|
10
10
|
getFileSystem,
|
|
11
11
|
getPlatformConfig,
|
|
12
|
+
hashText,
|
|
12
13
|
streamSimple
|
|
13
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-VESPMEDG.js";
|
|
14
15
|
import {
|
|
15
16
|
__require
|
|
16
17
|
} from "./chunk-TG2EQLX2.js";
|
|
@@ -1995,7 +1996,7 @@ function isTransientFileError(error) {
|
|
|
1995
1996
|
return /^(EPERM|EACCES|EBUSY|ENOTEMPTY)$/.test(code) || /\b(EPERM|EACCES|EBUSY|locked|busy)\b/i.test(message);
|
|
1996
1997
|
}
|
|
1997
1998
|
function sleep(ms) {
|
|
1998
|
-
return new Promise((
|
|
1999
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
1999
2000
|
}
|
|
2000
2001
|
async function retryFileOperation(operation, retryDelaysMs) {
|
|
2001
2002
|
let lastError;
|
|
@@ -2070,6 +2071,15 @@ function candidatePriority(role) {
|
|
|
2070
2071
|
return 1;
|
|
2071
2072
|
}
|
|
2072
2073
|
function chooseBestCandidate(candidates) {
|
|
2074
|
+
const main = candidates.find((candidate) => candidate.role === "main");
|
|
2075
|
+
if (main) {
|
|
2076
|
+
const backup = candidates.find((candidate) => candidate.role === "backup");
|
|
2077
|
+
const previous = candidates.find((candidate) => candidate.role === "previous");
|
|
2078
|
+
if (backup && previous && backup.content !== main.content && previous.content === main.content && backup.updatedAt >= main.updatedAt) {
|
|
2079
|
+
return backup;
|
|
2080
|
+
}
|
|
2081
|
+
return main;
|
|
2082
|
+
}
|
|
2073
2083
|
return [...candidates].sort((a, b) => {
|
|
2074
2084
|
const updatedAtDiff = b.updatedAt - a.updatedAt;
|
|
2075
2085
|
if (updatedAtDiff !== 0) return updatedAtDiff;
|
|
@@ -2158,6 +2168,8 @@ function deleteDurableFile(gateway, filePath) {
|
|
|
2158
2168
|
|
|
2159
2169
|
// ../../packages/agent-framework/src/session/store.ts
|
|
2160
2170
|
var SESSION_VERSION = 2;
|
|
2171
|
+
var METADATA_INDEX_VERSION = 3;
|
|
2172
|
+
var METADATA_FAST_READ_BYTES = 64 * 1024;
|
|
2161
2173
|
function isSessionEntry(value) {
|
|
2162
2174
|
return !!value && typeof value === "object" && "id" in value && "type" in value;
|
|
2163
2175
|
}
|
|
@@ -2180,6 +2192,60 @@ function normalizeEntriesInput(entriesOrMessages) {
|
|
|
2180
2192
|
}
|
|
2181
2193
|
return entries;
|
|
2182
2194
|
}
|
|
2195
|
+
function extractJsonObjectProperty(content, propertyName) {
|
|
2196
|
+
const propertyIndex = content.indexOf(`"${propertyName}"`);
|
|
2197
|
+
if (propertyIndex < 0) return null;
|
|
2198
|
+
const colonIndex = content.indexOf(":", propertyIndex + propertyName.length + 2);
|
|
2199
|
+
if (colonIndex < 0) return null;
|
|
2200
|
+
let index = colonIndex + 1;
|
|
2201
|
+
while (index < content.length && /\s/.test(content[index])) index += 1;
|
|
2202
|
+
if (content[index] !== "{") return null;
|
|
2203
|
+
let depth = 0;
|
|
2204
|
+
let inString = false;
|
|
2205
|
+
let escaping = false;
|
|
2206
|
+
for (let cursor = index; cursor < content.length; cursor += 1) {
|
|
2207
|
+
const char = content[cursor];
|
|
2208
|
+
if (inString) {
|
|
2209
|
+
if (escaping) {
|
|
2210
|
+
escaping = false;
|
|
2211
|
+
} else if (char === "\\") {
|
|
2212
|
+
escaping = true;
|
|
2213
|
+
} else if (char === '"') {
|
|
2214
|
+
inString = false;
|
|
2215
|
+
}
|
|
2216
|
+
continue;
|
|
2217
|
+
}
|
|
2218
|
+
if (char === '"') {
|
|
2219
|
+
inString = true;
|
|
2220
|
+
continue;
|
|
2221
|
+
}
|
|
2222
|
+
if (char === "{") {
|
|
2223
|
+
depth += 1;
|
|
2224
|
+
} else if (char === "}") {
|
|
2225
|
+
depth -= 1;
|
|
2226
|
+
if (depth === 0) {
|
|
2227
|
+
return content.slice(index, cursor + 1);
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
return null;
|
|
2232
|
+
}
|
|
2233
|
+
function parseMetadataOnly(content) {
|
|
2234
|
+
const metadataText = extractJsonObjectProperty(content, "metadata");
|
|
2235
|
+
if (!metadataText) return null;
|
|
2236
|
+
const parsed = JSON.parse(metadataText);
|
|
2237
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
2238
|
+
const metadata = parsed;
|
|
2239
|
+
if (typeof metadata.id !== "string" || typeof metadata.name !== "string" || typeof metadata.createdAt !== "number" || typeof metadata.updatedAt !== "number" || typeof metadata.entryCount !== "number" || typeof metadata.messageCount !== "number") {
|
|
2240
|
+
return null;
|
|
2241
|
+
}
|
|
2242
|
+
return metadata;
|
|
2243
|
+
}
|
|
2244
|
+
function durableRolePriority(role) {
|
|
2245
|
+
if (role === "main") return 3;
|
|
2246
|
+
if (role === "backup") return 2;
|
|
2247
|
+
return 1;
|
|
2248
|
+
}
|
|
2183
2249
|
var LEGACY_VERSION = 1;
|
|
2184
2250
|
function getDefaultSessionsDir() {
|
|
2185
2251
|
return getPlatformConfig().sessionsPath;
|
|
@@ -2245,6 +2311,9 @@ var FileSessionStore = class {
|
|
|
2245
2311
|
const safeId = sessionId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
2246
2312
|
return this.gateway.join(this.sessionsDir, `${safeId}.json`);
|
|
2247
2313
|
}
|
|
2314
|
+
getMetadataIndexPath() {
|
|
2315
|
+
return this.gateway.join(this.sessionsDir, ".metadata-index.json");
|
|
2316
|
+
}
|
|
2248
2317
|
normalizeLoadedData(data) {
|
|
2249
2318
|
if (data.version === LEGACY_VERSION || !data.version) {
|
|
2250
2319
|
return { data: migrateV1ToV2(data), shouldPersist: true };
|
|
@@ -2279,6 +2348,8 @@ var FileSessionStore = class {
|
|
|
2279
2348
|
const normalizedEntries = normalizeEntriesInput(entries);
|
|
2280
2349
|
const now = Date.now();
|
|
2281
2350
|
const existing = await this.load(sessionId);
|
|
2351
|
+
const requestedUpdatedAt = metadata?.updatedAt ?? now;
|
|
2352
|
+
const updatedAt = Math.max(requestedUpdatedAt, existing?.metadata.updatedAt ?? 0);
|
|
2282
2353
|
const messageCount = normalizedEntries.filter((e) => e.type === "message" && e.message).length;
|
|
2283
2354
|
const rootEntry = normalizedEntries.find((e) => e.parentId === null);
|
|
2284
2355
|
const activeEntryId = metadata?.activeEntryId ?? existing?.metadata.activeEntryId ?? normalizedEntries[normalizedEntries.length - 1]?.id ?? null;
|
|
@@ -2288,13 +2359,13 @@ var FileSessionStore = class {
|
|
|
2288
2359
|
id: sessionId,
|
|
2289
2360
|
name: metadata?.name ?? existing?.metadata.name ?? `Session ${sessionId.slice(0, 8)}`,
|
|
2290
2361
|
createdAt: existing?.metadata.createdAt ?? now,
|
|
2291
|
-
updatedAt: now,
|
|
2292
2362
|
entryCount: normalizedEntries.length,
|
|
2293
2363
|
messageCount,
|
|
2294
2364
|
activeEntryId: activeEntryId ?? rootEntry?.id ?? null,
|
|
2295
2365
|
rootEntryId: rootEntry?.id ?? null,
|
|
2296
2366
|
tags: metadata?.tags ?? existing?.metadata.tags,
|
|
2297
|
-
...metadata
|
|
2367
|
+
...metadata,
|
|
2368
|
+
updatedAt
|
|
2298
2369
|
},
|
|
2299
2370
|
entries: normalizedEntries,
|
|
2300
2371
|
messages: flattenTreeToMessages(normalizedEntries, activeEntryId ?? null)
|
|
@@ -2306,6 +2377,7 @@ var FileSessionStore = class {
|
|
|
2306
2377
|
filePath,
|
|
2307
2378
|
parse: (content, path3) => this.parseSessionContent(content, path3)
|
|
2308
2379
|
}, json);
|
|
2380
|
+
this.upsertMetadataIndexBestEffort(sessionData.metadata);
|
|
2309
2381
|
}
|
|
2310
2382
|
/**
|
|
2311
2383
|
* Legacy save method for backward compatibility
|
|
@@ -2361,7 +2433,9 @@ var FileSessionStore = class {
|
|
|
2361
2433
|
async delete(sessionId) {
|
|
2362
2434
|
const filePath = this.getSessionPath(sessionId);
|
|
2363
2435
|
try {
|
|
2364
|
-
|
|
2436
|
+
const deleted = deleteDurableFile(this.gateway, filePath);
|
|
2437
|
+
if (deleted) this.removeMetadataIndexEntryBestEffort(sessionId);
|
|
2438
|
+
return deleted;
|
|
2365
2439
|
} catch (e) {
|
|
2366
2440
|
console.error(`[SessionStore] Failed to delete session: ${sessionId}`, e);
|
|
2367
2441
|
return false;
|
|
@@ -2378,13 +2452,16 @@ var FileSessionStore = class {
|
|
|
2378
2452
|
* List all session IDs
|
|
2379
2453
|
*/
|
|
2380
2454
|
async list() {
|
|
2455
|
+
return this.listSessionIdsSync();
|
|
2456
|
+
}
|
|
2457
|
+
listSessionIdsSync() {
|
|
2381
2458
|
if (!this.gateway.exists(this.sessionsDir)) {
|
|
2382
2459
|
return [];
|
|
2383
2460
|
}
|
|
2384
2461
|
const files = this.gateway.readdir(this.sessionsDir);
|
|
2385
2462
|
const sessionIds = /* @__PURE__ */ new Set();
|
|
2386
2463
|
for (const file of files) {
|
|
2387
|
-
if (
|
|
2464
|
+
if (isSessionSidecarFile(file)) {
|
|
2388
2465
|
continue;
|
|
2389
2466
|
}
|
|
2390
2467
|
if (file.endsWith(".json")) {
|
|
@@ -2401,24 +2478,235 @@ var FileSessionStore = class {
|
|
|
2401
2478
|
* List all session metadata
|
|
2402
2479
|
*/
|
|
2403
2480
|
async listMetadata() {
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2481
|
+
return await this.listMetadataIndexed();
|
|
2482
|
+
}
|
|
2483
|
+
async listMetadataPage(input) {
|
|
2484
|
+
const limit = Math.max(0, input.limit);
|
|
2485
|
+
if (limit === 0) return { metadata: [], hasMore: false };
|
|
2486
|
+
const result = [];
|
|
2487
|
+
for (const metadata of await this.listMetadataIndexed()) {
|
|
2488
|
+
if (input.after && compareMetadataListPosition(metadata, input.after) <= 0) continue;
|
|
2489
|
+
if (input.filter && !input.filter(metadata)) continue;
|
|
2490
|
+
result.push(metadata);
|
|
2491
|
+
if (result.length > limit) break;
|
|
2411
2492
|
}
|
|
2412
|
-
return
|
|
2493
|
+
return {
|
|
2494
|
+
metadata: result.slice(0, limit),
|
|
2495
|
+
hasMore: result.length > limit
|
|
2496
|
+
};
|
|
2413
2497
|
}
|
|
2414
2498
|
/**
|
|
2415
2499
|
* Get session metadata without loading full entries
|
|
2416
2500
|
*/
|
|
2417
2501
|
async getMetadata(sessionId) {
|
|
2502
|
+
const fastMetadata = this.getMetadataFast(sessionId);
|
|
2503
|
+
if (fastMetadata) return fastMetadata;
|
|
2418
2504
|
const data = await this.load(sessionId);
|
|
2419
2505
|
return data?.metadata ?? null;
|
|
2420
2506
|
}
|
|
2507
|
+
getMetadataFast(sessionId) {
|
|
2508
|
+
const paths = durableFilePaths(this.getSessionPath(sessionId));
|
|
2509
|
+
const candidates = [];
|
|
2510
|
+
for (const [role, path3] of [
|
|
2511
|
+
["main", paths.main],
|
|
2512
|
+
["backup", paths.backup],
|
|
2513
|
+
["previous", paths.previous]
|
|
2514
|
+
]) {
|
|
2515
|
+
try {
|
|
2516
|
+
if (!this.gateway.exists(path3)) continue;
|
|
2517
|
+
const metadata = this.readMetadataOnlyFast(path3);
|
|
2518
|
+
if (metadata) candidates.push({ metadata, role });
|
|
2519
|
+
} catch {
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
if (candidates.length === 0) return null;
|
|
2523
|
+
return chooseBestMetadataCandidate(candidates).metadata;
|
|
2524
|
+
}
|
|
2525
|
+
readMetadataOnlyFast(path3) {
|
|
2526
|
+
const fileSize = this.gateway.getFileSize(path3);
|
|
2527
|
+
if (fileSize <= METADATA_FAST_READ_BYTES) {
|
|
2528
|
+
return parseMetadataOnly(this.gateway.readFile(path3, "utf-8"));
|
|
2529
|
+
}
|
|
2530
|
+
const head = this.gateway.readFileHead(path3, METADATA_FAST_READ_BYTES, "utf-8");
|
|
2531
|
+
const metadata = parseMetadataOnly(head);
|
|
2532
|
+
if (metadata) return metadata;
|
|
2533
|
+
return parseMetadataOnly(this.gateway.readFile(path3, "utf-8"));
|
|
2534
|
+
}
|
|
2535
|
+
async listMetadataIndexed() {
|
|
2536
|
+
const sessionIds = await this.list();
|
|
2537
|
+
const sessionIdSet = new Set(sessionIds);
|
|
2538
|
+
const index = this.readMetadataIndexBestEffort();
|
|
2539
|
+
if (index && this.metadataIndexMatchesSessionFiles(index, sessionIdSet)) {
|
|
2540
|
+
return sortMetadata(index.sessions.map((entry) => entry.metadata).filter((metadata2) => sessionIdSet.has(metadata2.id)));
|
|
2541
|
+
}
|
|
2542
|
+
const metadata = [];
|
|
2543
|
+
for (const id of sessionIds) {
|
|
2544
|
+
const meta = await this.getMetadata(id);
|
|
2545
|
+
if (meta) metadata.push(meta);
|
|
2546
|
+
}
|
|
2547
|
+
const sorted = sortMetadata(metadata);
|
|
2548
|
+
this.writeMetadataIndexBestEffort(sorted);
|
|
2549
|
+
return sorted;
|
|
2550
|
+
}
|
|
2551
|
+
readMetadataIndexBestEffort() {
|
|
2552
|
+
try {
|
|
2553
|
+
if (!this.gateway.exists(this.getMetadataIndexPath())) return null;
|
|
2554
|
+
const parsed = JSON.parse(this.gateway.readFile(this.getMetadataIndexPath(), "utf-8"));
|
|
2555
|
+
if (!isRecord(parsed) || parsed.v !== METADATA_INDEX_VERSION || !Array.isArray(parsed.sessions)) return null;
|
|
2556
|
+
const sessions = parsed.sessions.filter(isMetadataIndexEntry);
|
|
2557
|
+
if (sessions.length !== parsed.sessions.length) return null;
|
|
2558
|
+
return {
|
|
2559
|
+
v: METADATA_INDEX_VERSION,
|
|
2560
|
+
sessions,
|
|
2561
|
+
updatedAt: typeof parsed.updatedAt === "number" ? parsed.updatedAt : 0
|
|
2562
|
+
};
|
|
2563
|
+
} catch {
|
|
2564
|
+
return null;
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
writeMetadataIndexBestEffort(sessions) {
|
|
2568
|
+
try {
|
|
2569
|
+
this.gateway.writeFile(this.getMetadataIndexPath(), JSON.stringify({
|
|
2570
|
+
v: METADATA_INDEX_VERSION,
|
|
2571
|
+
updatedAt: Date.now(),
|
|
2572
|
+
sessions: sortMetadata(sessions).map((metadata) => ({
|
|
2573
|
+
metadata,
|
|
2574
|
+
files: this.sessionFileSignature(metadata.id)
|
|
2575
|
+
}))
|
|
2576
|
+
}, null, 2));
|
|
2577
|
+
} catch {
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
upsertMetadataIndexBestEffort(metadata) {
|
|
2581
|
+
const current = this.readMetadataIndexBestEffort();
|
|
2582
|
+
if (current && !this.metadataIndexCanAcceptUpsert(current, metadata.id)) {
|
|
2583
|
+
this.removeMetadataIndexFileBestEffort();
|
|
2584
|
+
return;
|
|
2585
|
+
}
|
|
2586
|
+
const next = (current?.sessions.map((entry) => entry.metadata) ?? []).filter((item) => item.id !== metadata.id);
|
|
2587
|
+
next.push(metadata);
|
|
2588
|
+
this.writeMetadataIndexBestEffort(next);
|
|
2589
|
+
}
|
|
2590
|
+
removeMetadataIndexEntryBestEffort(sessionId) {
|
|
2591
|
+
const current = this.readMetadataIndexBestEffort();
|
|
2592
|
+
if (!current) return;
|
|
2593
|
+
if (!this.metadataIndexCanRemoveSession(current, sessionId)) {
|
|
2594
|
+
this.removeMetadataIndexFileBestEffort();
|
|
2595
|
+
return;
|
|
2596
|
+
}
|
|
2597
|
+
this.writeMetadataIndexBestEffort(current.sessions.map((entry) => entry.metadata).filter((item) => item.id !== sessionId));
|
|
2598
|
+
}
|
|
2599
|
+
removeMetadataIndexFileBestEffort() {
|
|
2600
|
+
try {
|
|
2601
|
+
if (this.gateway.exists(this.getMetadataIndexPath())) this.gateway.deleteFile(this.getMetadataIndexPath());
|
|
2602
|
+
} catch {
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
metadataIndexCanAcceptUpsert(index, upsertedSessionId) {
|
|
2606
|
+
const sessionIds = new Set(this.listSessionIdsSync());
|
|
2607
|
+
const indexedIds = new Set(index.sessions.map((entry) => entry.metadata.id));
|
|
2608
|
+
if (indexedIds.size !== index.sessions.length) return false;
|
|
2609
|
+
for (const id of sessionIds) {
|
|
2610
|
+
if (id !== upsertedSessionId && !indexedIds.has(id)) return false;
|
|
2611
|
+
}
|
|
2612
|
+
for (const entry of index.sessions) {
|
|
2613
|
+
const id = entry.metadata.id;
|
|
2614
|
+
if (id !== upsertedSessionId && !sessionIds.has(id)) return false;
|
|
2615
|
+
if (id !== upsertedSessionId && !sessionFileSignaturesEqual(entry.files, this.sessionFileSignature(id))) return false;
|
|
2616
|
+
}
|
|
2617
|
+
return true;
|
|
2618
|
+
}
|
|
2619
|
+
metadataIndexCanRemoveSession(index, removedSessionId) {
|
|
2620
|
+
const sessionIds = new Set(this.listSessionIdsSync());
|
|
2621
|
+
return index.sessions.every(
|
|
2622
|
+
(entry) => entry.metadata.id === removedSessionId || sessionIds.has(entry.metadata.id) && sessionFileSignaturesEqual(entry.files, this.sessionFileSignature(entry.metadata.id))
|
|
2623
|
+
);
|
|
2624
|
+
}
|
|
2625
|
+
metadataIndexMatchesSessionFiles(index, sessionIds) {
|
|
2626
|
+
if (index.sessions.length !== sessionIds.size) return false;
|
|
2627
|
+
const indexedIds = new Set(index.sessions.map((entry) => entry.metadata.id));
|
|
2628
|
+
if (indexedIds.size !== index.sessions.length) return false;
|
|
2629
|
+
for (const id of sessionIds) {
|
|
2630
|
+
if (!indexedIds.has(id)) return false;
|
|
2631
|
+
}
|
|
2632
|
+
return index.sessions.every((entry) => sessionFileSignaturesEqual(entry.files, this.sessionFileSignature(entry.metadata.id)));
|
|
2633
|
+
}
|
|
2634
|
+
sessionFileSignature(sessionId) {
|
|
2635
|
+
const paths = durableFilePaths(this.getSessionPath(sessionId));
|
|
2636
|
+
return {
|
|
2637
|
+
...this.gateway.exists(paths.main) ? { main: this.fileSignature(paths.main) } : {},
|
|
2638
|
+
...this.gateway.exists(paths.backup) ? { backup: this.fileSignature(paths.backup) } : {},
|
|
2639
|
+
...this.gateway.exists(paths.previous) ? { previous: this.fileSignature(paths.previous) } : {}
|
|
2640
|
+
};
|
|
2641
|
+
}
|
|
2642
|
+
fileSignature(path3) {
|
|
2643
|
+
return {
|
|
2644
|
+
size: this.gateway.getFileSize(path3),
|
|
2645
|
+
modifiedAt: this.gateway.getModifiedTime(path3)
|
|
2646
|
+
};
|
|
2647
|
+
}
|
|
2421
2648
|
};
|
|
2649
|
+
function isSessionSidecarFile(file) {
|
|
2650
|
+
return file === ".metadata-index.json" || file.endsWith(".trace.json") || file.endsWith(".trace.json.bak") || file.endsWith(".trace.json.prev") || file.endsWith("-files.json") || file.endsWith("-files.json.bak") || file.endsWith("-files.json.prev");
|
|
2651
|
+
}
|
|
2652
|
+
function sortMetadata(metadata) {
|
|
2653
|
+
return [...metadata].sort((a, b) => b.updatedAt - a.updatedAt || a.id.localeCompare(b.id));
|
|
2654
|
+
}
|
|
2655
|
+
function chooseBestMetadataCandidate(candidates) {
|
|
2656
|
+
const main = candidates.find((candidate) => candidate.role === "main");
|
|
2657
|
+
if (main) {
|
|
2658
|
+
const backup = candidates.find((candidate) => candidate.role === "backup");
|
|
2659
|
+
const previous = candidates.find((candidate) => candidate.role === "previous");
|
|
2660
|
+
if (backup && previous && !metadataObjectsEqual(backup.metadata, main.metadata) && metadataObjectsEqual(previous.metadata, main.metadata) && backup.metadata.updatedAt >= main.metadata.updatedAt) {
|
|
2661
|
+
return backup;
|
|
2662
|
+
}
|
|
2663
|
+
return main;
|
|
2664
|
+
}
|
|
2665
|
+
return [...candidates].sort((left, right) => {
|
|
2666
|
+
const updatedAtDiff = right.metadata.updatedAt - left.metadata.updatedAt;
|
|
2667
|
+
if (updatedAtDiff !== 0) return updatedAtDiff;
|
|
2668
|
+
return durableRolePriority(right.role) - durableRolePriority(left.role);
|
|
2669
|
+
})[0];
|
|
2670
|
+
}
|
|
2671
|
+
function metadataObjectsEqual(left, right) {
|
|
2672
|
+
return stableJson(left) === stableJson(right);
|
|
2673
|
+
}
|
|
2674
|
+
function stableJson(value) {
|
|
2675
|
+
if (Array.isArray(value)) {
|
|
2676
|
+
return `[${value.map((item) => stableJson(item)).join(",")}]`;
|
|
2677
|
+
}
|
|
2678
|
+
if (isRecord(value)) {
|
|
2679
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`).join(",")}}`;
|
|
2680
|
+
}
|
|
2681
|
+
return JSON.stringify(value);
|
|
2682
|
+
}
|
|
2683
|
+
function compareMetadataListPosition(metadata, boundary) {
|
|
2684
|
+
const updatedAtDiff = boundary.updatedAt - metadata.updatedAt;
|
|
2685
|
+
if (updatedAtDiff !== 0) return updatedAtDiff;
|
|
2686
|
+
return metadata.id.localeCompare(boundary.id);
|
|
2687
|
+
}
|
|
2688
|
+
function isSessionMetadata(value) {
|
|
2689
|
+
if (!isRecord(value)) return false;
|
|
2690
|
+
return typeof value.id === "string" && typeof value.name === "string" && typeof value.createdAt === "number" && typeof value.updatedAt === "number" && typeof value.entryCount === "number" && typeof value.messageCount === "number";
|
|
2691
|
+
}
|
|
2692
|
+
function isMetadataIndexEntry(value) {
|
|
2693
|
+
if (!isRecord(value) || !isSessionMetadata(value.metadata) || !isRecord(value.files)) return false;
|
|
2694
|
+
return optionalFileSignature(value.files.main) && optionalFileSignature(value.files.backup) && optionalFileSignature(value.files.previous);
|
|
2695
|
+
}
|
|
2696
|
+
function optionalFileSignature(value) {
|
|
2697
|
+
if (value === void 0) return true;
|
|
2698
|
+
return isRecord(value) && typeof value.size === "number" && Number.isFinite(value.size) && typeof value.modifiedAt === "number" && Number.isFinite(value.modifiedAt);
|
|
2699
|
+
}
|
|
2700
|
+
function sessionFileSignaturesEqual(left, right) {
|
|
2701
|
+
return fileSignaturesEqual(left.main, right.main) && fileSignaturesEqual(left.backup, right.backup) && fileSignaturesEqual(left.previous, right.previous);
|
|
2702
|
+
}
|
|
2703
|
+
function fileSignaturesEqual(left, right) {
|
|
2704
|
+
if (!left || !right) return left === right;
|
|
2705
|
+
return left.size === right.size && left.modifiedAt === right.modifiedAt;
|
|
2706
|
+
}
|
|
2707
|
+
function isRecord(value) {
|
|
2708
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
2709
|
+
}
|
|
2422
2710
|
|
|
2423
2711
|
// ../../packages/agent-framework/src/session/manager.ts
|
|
2424
2712
|
function generateSessionId() {
|
|
@@ -2507,10 +2795,12 @@ var SessionManager = class {
|
|
|
2507
2795
|
/**
|
|
2508
2796
|
* Load an existing session
|
|
2509
2797
|
*/
|
|
2510
|
-
async loadSession(sessionId) {
|
|
2798
|
+
async loadSession(sessionId, options) {
|
|
2511
2799
|
const data = await this.store.load(sessionId);
|
|
2512
2800
|
if (!data) {
|
|
2513
|
-
|
|
2801
|
+
if (options?.warnIfMissing !== false) {
|
|
2802
|
+
console.warn(`[SessionManager] Session not found: ${sessionId}`);
|
|
2803
|
+
}
|
|
2514
2804
|
return false;
|
|
2515
2805
|
}
|
|
2516
2806
|
if (!data.entries) {
|
|
@@ -3038,15 +3328,15 @@ var AgentSessionAutoCompactScheduler = class {
|
|
|
3038
3328
|
}
|
|
3039
3329
|
ensurePendingWait() {
|
|
3040
3330
|
if (this.pendingWait) return;
|
|
3041
|
-
this.pendingWait = new Promise((
|
|
3042
|
-
this.resolvePendingWait =
|
|
3331
|
+
this.pendingWait = new Promise((resolve3) => {
|
|
3332
|
+
this.resolvePendingWait = resolve3;
|
|
3043
3333
|
});
|
|
3044
3334
|
}
|
|
3045
3335
|
resolvePending() {
|
|
3046
|
-
const
|
|
3336
|
+
const resolve3 = this.resolvePendingWait;
|
|
3047
3337
|
this.pendingWait = void 0;
|
|
3048
3338
|
this.resolvePendingWait = void 0;
|
|
3049
|
-
|
|
3339
|
+
resolve3?.();
|
|
3050
3340
|
}
|
|
3051
3341
|
};
|
|
3052
3342
|
|
|
@@ -3124,29 +3414,29 @@ var AgentSessionAutoContinueController = class {
|
|
|
3124
3414
|
this.options.emit({ type: "auto_continue_scheduled", reason });
|
|
3125
3415
|
this.options.agent.followUp(message);
|
|
3126
3416
|
const setTimer = this.options.retry?.setTimeout ?? defaultSetTimeout2;
|
|
3127
|
-
await new Promise((
|
|
3417
|
+
await new Promise((resolve3) => {
|
|
3128
3418
|
this.autoContinueTimer = setTimer(() => {
|
|
3129
3419
|
this.autoContinueTimer = void 0;
|
|
3130
3420
|
if (this.options.getDisposed()) {
|
|
3131
|
-
|
|
3421
|
+
resolve3();
|
|
3132
3422
|
return;
|
|
3133
3423
|
}
|
|
3134
3424
|
void this.options.continueTurn().catch(() => {
|
|
3135
|
-
}).finally(
|
|
3425
|
+
}).finally(resolve3);
|
|
3136
3426
|
}, 0);
|
|
3137
3427
|
});
|
|
3138
3428
|
}
|
|
3139
3429
|
ensurePending() {
|
|
3140
3430
|
if (this.pendingAutoContinue) return;
|
|
3141
|
-
this.pendingAutoContinue = new Promise((
|
|
3142
|
-
this.resolvePendingAutoContinue =
|
|
3431
|
+
this.pendingAutoContinue = new Promise((resolve3) => {
|
|
3432
|
+
this.resolvePendingAutoContinue = resolve3;
|
|
3143
3433
|
});
|
|
3144
3434
|
}
|
|
3145
3435
|
resolvePending() {
|
|
3146
|
-
const
|
|
3436
|
+
const resolve3 = this.resolvePendingAutoContinue;
|
|
3147
3437
|
this.pendingAutoContinue = void 0;
|
|
3148
3438
|
this.resolvePendingAutoContinue = void 0;
|
|
3149
|
-
|
|
3439
|
+
resolve3?.();
|
|
3150
3440
|
}
|
|
3151
3441
|
};
|
|
3152
3442
|
|
|
@@ -3555,15 +3845,15 @@ var AgentSessionQueueDispatcher = class {
|
|
|
3555
3845
|
}
|
|
3556
3846
|
ensurePending() {
|
|
3557
3847
|
if (this.pendingDispatch) return;
|
|
3558
|
-
this.pendingDispatch = new Promise((
|
|
3559
|
-
this.resolvePendingDispatch =
|
|
3848
|
+
this.pendingDispatch = new Promise((resolve3) => {
|
|
3849
|
+
this.resolvePendingDispatch = resolve3;
|
|
3560
3850
|
});
|
|
3561
3851
|
}
|
|
3562
3852
|
resolvePending() {
|
|
3563
|
-
const
|
|
3853
|
+
const resolve3 = this.resolvePendingDispatch;
|
|
3564
3854
|
this.pendingDispatch = void 0;
|
|
3565
3855
|
this.resolvePendingDispatch = void 0;
|
|
3566
|
-
|
|
3856
|
+
resolve3?.();
|
|
3567
3857
|
}
|
|
3568
3858
|
};
|
|
3569
3859
|
|
|
@@ -3717,15 +4007,15 @@ var AgentSessionRetryController = class {
|
|
|
3717
4007
|
if (this.pendingRetry) {
|
|
3718
4008
|
return;
|
|
3719
4009
|
}
|
|
3720
|
-
this.pendingRetry = new Promise((
|
|
3721
|
-
this.resolvePendingRetry =
|
|
4010
|
+
this.pendingRetry = new Promise((resolve3) => {
|
|
4011
|
+
this.resolvePendingRetry = resolve3;
|
|
3722
4012
|
});
|
|
3723
4013
|
}
|
|
3724
4014
|
resolvePending() {
|
|
3725
|
-
const
|
|
4015
|
+
const resolve3 = this.resolvePendingRetry;
|
|
3726
4016
|
this.pendingRetry = void 0;
|
|
3727
4017
|
this.resolvePendingRetry = void 0;
|
|
3728
|
-
|
|
4018
|
+
resolve3?.();
|
|
3729
4019
|
}
|
|
3730
4020
|
};
|
|
3731
4021
|
|
|
@@ -5443,9 +5733,98 @@ function truncateLine(line, maxChars = GREP_MAX_LINE_LENGTH) {
|
|
|
5443
5733
|
return { text: `${line.slice(0, maxChars)}... [truncated]`, wasTruncated: true };
|
|
5444
5734
|
}
|
|
5445
5735
|
|
|
5736
|
+
// ../../packages/shared-headless-capabilities/src/builtin/fs/file-observation-state.ts
|
|
5737
|
+
function snapshotsEqual(left, right) {
|
|
5738
|
+
return left.mtimeMs === right.mtimeMs && left.size === right.size && left.contentHash === right.contentHash;
|
|
5739
|
+
}
|
|
5740
|
+
function snapshotsHaveSameContent(left, right) {
|
|
5741
|
+
return left.size === right.size && left.contentHash === right.contentHash;
|
|
5742
|
+
}
|
|
5743
|
+
function snapshotFromStatAndContent(stat, content) {
|
|
5744
|
+
return {
|
|
5745
|
+
mtimeMs: stat.mtime.getTime(),
|
|
5746
|
+
size: stat.size,
|
|
5747
|
+
contentHash: hashText(content)
|
|
5748
|
+
};
|
|
5749
|
+
}
|
|
5750
|
+
function createFileObservationState(options = {}) {
|
|
5751
|
+
const files = /* @__PURE__ */ new Map();
|
|
5752
|
+
const maxFiles = Math.max(1, Math.floor(options.maxFiles ?? 100));
|
|
5753
|
+
const maxRangesPerFile = Math.max(0, Math.floor(options.maxRangesPerFile ?? 8));
|
|
5754
|
+
function touchFile(absolutePath, observed) {
|
|
5755
|
+
files.delete(absolutePath);
|
|
5756
|
+
files.set(absolutePath, observed);
|
|
5757
|
+
while (files.size > maxFiles) {
|
|
5758
|
+
const oldest = files.keys().next().value;
|
|
5759
|
+
if (oldest === void 0) break;
|
|
5760
|
+
files.delete(oldest);
|
|
5761
|
+
}
|
|
5762
|
+
}
|
|
5763
|
+
function touchRange(readRanges, rangeKey, range) {
|
|
5764
|
+
if (maxRangesPerFile === 0) {
|
|
5765
|
+
readRanges.clear();
|
|
5766
|
+
return;
|
|
5767
|
+
}
|
|
5768
|
+
readRanges.delete(rangeKey);
|
|
5769
|
+
readRanges.set(rangeKey, range);
|
|
5770
|
+
while (readRanges.size > maxRangesPerFile) {
|
|
5771
|
+
const oldest = readRanges.keys().next().value;
|
|
5772
|
+
if (oldest === void 0) break;
|
|
5773
|
+
readRanges.delete(oldest);
|
|
5774
|
+
}
|
|
5775
|
+
}
|
|
5776
|
+
return {
|
|
5777
|
+
isUnchangedReadRange(absolutePath, rangeKey, snapshot, selectedContentHash) {
|
|
5778
|
+
const observed = files.get(absolutePath);
|
|
5779
|
+
const range = observed?.readRanges.get(rangeKey);
|
|
5780
|
+
if (!observed || !range) {
|
|
5781
|
+
return false;
|
|
5782
|
+
}
|
|
5783
|
+
const unchanged = snapshotsEqual(range.snapshot, snapshot) && range.selectedContentHash === selectedContentHash;
|
|
5784
|
+
touchRange(observed.readRanges, rangeKey, range);
|
|
5785
|
+
touchFile(absolutePath, observed);
|
|
5786
|
+
return unchanged;
|
|
5787
|
+
},
|
|
5788
|
+
recordTextRead(absolutePath, rangeKey, snapshot, selectedContentHash, completeTextKnown) {
|
|
5789
|
+
const observed = files.get(absolutePath) ?? {
|
|
5790
|
+
snapshot,
|
|
5791
|
+
completeTextKnown: false,
|
|
5792
|
+
readRanges: /* @__PURE__ */ new Map()
|
|
5793
|
+
};
|
|
5794
|
+
const preservesFreshCompleteRead = observed.completeTextKnown && snapshotsHaveSameContent(observed.snapshot, snapshot);
|
|
5795
|
+
observed.snapshot = snapshot;
|
|
5796
|
+
observed.completeTextKnown = completeTextKnown || preservesFreshCompleteRead;
|
|
5797
|
+
touchRange(observed.readRanges, rangeKey, { snapshot, selectedContentHash });
|
|
5798
|
+
touchFile(absolutePath, observed);
|
|
5799
|
+
},
|
|
5800
|
+
getCompleteTextSnapshot(absolutePath) {
|
|
5801
|
+
const observed = files.get(absolutePath);
|
|
5802
|
+
if (!observed?.completeTextKnown) {
|
|
5803
|
+
return void 0;
|
|
5804
|
+
}
|
|
5805
|
+
touchFile(absolutePath, observed);
|
|
5806
|
+
return observed.snapshot;
|
|
5807
|
+
},
|
|
5808
|
+
recordCompleteTextWrite(absolutePath, snapshot) {
|
|
5809
|
+
touchFile(absolutePath, {
|
|
5810
|
+
snapshot,
|
|
5811
|
+
completeTextKnown: true,
|
|
5812
|
+
readRanges: /* @__PURE__ */ new Map()
|
|
5813
|
+
});
|
|
5814
|
+
},
|
|
5815
|
+
invalidate(absolutePath) {
|
|
5816
|
+
files.delete(absolutePath);
|
|
5817
|
+
},
|
|
5818
|
+
clear() {
|
|
5819
|
+
files.clear();
|
|
5820
|
+
}
|
|
5821
|
+
};
|
|
5822
|
+
}
|
|
5823
|
+
|
|
5446
5824
|
// ../../packages/shared-headless-capabilities/src/builtin/fs/read.ts
|
|
5447
5825
|
function createReadTool(cwd, options) {
|
|
5448
5826
|
const fs2 = getFileSystem();
|
|
5827
|
+
const fileObservationState = options?.fileObservationState ?? createFileObservationState();
|
|
5449
5828
|
const rootSchema = buildRootParameterSchema(cwd, options);
|
|
5450
5829
|
const readSchema = Type.Object({
|
|
5451
5830
|
path: Type.String({ description: "Path to the file to read. Example: 'Scripts/PlayerController.cs' or 'Assets/Gen/config.json'." }),
|
|
@@ -5464,7 +5843,7 @@ function createReadTool(cwd, options) {
|
|
|
5464
5843
|
const { path: path3, root, offset, limit, question } = params;
|
|
5465
5844
|
const absolutePath = resolveReadPath(path3, cwd, options, root);
|
|
5466
5845
|
return new Promise(
|
|
5467
|
-
(
|
|
5846
|
+
(resolve3, reject) => {
|
|
5468
5847
|
if (signal?.aborted) {
|
|
5469
5848
|
reject(new Error("Operation aborted"));
|
|
5470
5849
|
return;
|
|
@@ -5480,11 +5859,18 @@ function createReadTool(cwd, options) {
|
|
|
5480
5859
|
(async () => {
|
|
5481
5860
|
try {
|
|
5482
5861
|
await fs2.access(absolutePath);
|
|
5862
|
+
if (isSpecialDevicePath(absolutePath)) {
|
|
5863
|
+
throw new Error(`Refusing to read special device path: ${absolutePath}`);
|
|
5864
|
+
}
|
|
5865
|
+
const stat = await fs2.stat(absolutePath);
|
|
5866
|
+
if (!stat.isFile) {
|
|
5867
|
+
throw new Error(`Refusing to read non-regular file: ${absolutePath}`);
|
|
5868
|
+
}
|
|
5483
5869
|
if (aborted) return;
|
|
5484
5870
|
const imageInfo = await readImageInfo(absolutePath, fs2);
|
|
5485
5871
|
if (imageInfo) {
|
|
5486
5872
|
if (question && options?.understandFile) {
|
|
5487
|
-
|
|
5873
|
+
resolve3(await options.understandFile({
|
|
5488
5874
|
absolutePath,
|
|
5489
5875
|
displayPath: path3,
|
|
5490
5876
|
mimeType: imageInfo.mimeType,
|
|
@@ -5496,7 +5882,7 @@ function createReadTool(cwd, options) {
|
|
|
5496
5882
|
return;
|
|
5497
5883
|
}
|
|
5498
5884
|
const dimensionText = imageInfo.width && imageInfo.height ? `, ${imageInfo.width}x${imageInfo.height}` : "";
|
|
5499
|
-
|
|
5885
|
+
resolve3({
|
|
5500
5886
|
content: [
|
|
5501
5887
|
{ type: "text", text: `Read image file [${imageInfo.mimeType}${dimensionText}]. Original image attached as an image block; for very large images, prefer a smaller crop or resized copy before rereading.` },
|
|
5502
5888
|
{ type: "image", data: imageInfo.base64, mimeType: imageInfo.mimeType }
|
|
@@ -5513,7 +5899,7 @@ function createReadTool(cwd, options) {
|
|
|
5513
5899
|
}
|
|
5514
5900
|
const mediaInfo = detectUnderstandingKind(absolutePath);
|
|
5515
5901
|
if (mediaInfo && options?.understandFile) {
|
|
5516
|
-
|
|
5902
|
+
resolve3(await options.understandFile({
|
|
5517
5903
|
absolutePath,
|
|
5518
5904
|
displayPath: path3,
|
|
5519
5905
|
mimeType: mediaInfo.mimeType,
|
|
@@ -5524,7 +5910,7 @@ function createReadTool(cwd, options) {
|
|
|
5524
5910
|
return;
|
|
5525
5911
|
}
|
|
5526
5912
|
if (mediaInfo) {
|
|
5527
|
-
|
|
5913
|
+
resolve3({
|
|
5528
5914
|
content: [{
|
|
5529
5915
|
type: "text",
|
|
5530
5916
|
text: `Cannot read ${mediaInfo.kind} file ${path3} semantically: no media understanding handler is configured for ${mediaInfo.mimeType}.`
|
|
@@ -5540,24 +5926,62 @@ function createReadTool(cwd, options) {
|
|
|
5540
5926
|
});
|
|
5541
5927
|
return;
|
|
5542
5928
|
}
|
|
5543
|
-
const content = String(await fs2.readFile(absolutePath, "utf-8"));
|
|
5544
|
-
const allLines = content.split("\n");
|
|
5545
|
-
const totalFileLines = allLines.length;
|
|
5546
5929
|
const startLine = offset ? Math.max(0, offset - 1) : 0;
|
|
5547
5930
|
const startLineDisplay = startLine + 1;
|
|
5548
|
-
|
|
5549
|
-
|
|
5931
|
+
const range = await fs2.readTextRange(absolutePath, {
|
|
5932
|
+
offsetLine: startLine,
|
|
5933
|
+
limitLines: limit,
|
|
5934
|
+
maxOutputLines: DEFAULT_MAX_LINES,
|
|
5935
|
+
maxOutputBytes: DEFAULT_MAX_BYTES
|
|
5936
|
+
}, signal);
|
|
5937
|
+
const snapshot = {
|
|
5938
|
+
mtimeMs: range.mtimeMs,
|
|
5939
|
+
size: range.size,
|
|
5940
|
+
contentHash: range.contentHash
|
|
5941
|
+
};
|
|
5942
|
+
const totalFileLines = range.totalLines;
|
|
5943
|
+
if (startLine >= range.totalLines) {
|
|
5944
|
+
throw new Error(`Offset ${offset} is beyond end of file (${range.totalLines} lines total)`);
|
|
5550
5945
|
}
|
|
5551
|
-
let selectedContent;
|
|
5552
5946
|
let userLimitedLines;
|
|
5553
5947
|
if (limit !== void 0) {
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5948
|
+
userLimitedLines = range.selectedLines;
|
|
5949
|
+
}
|
|
5950
|
+
const rangeKey = `${startLineDisplay}:${limit === void 0 ? "all" : limit}`;
|
|
5951
|
+
const truncation = {
|
|
5952
|
+
content: range.content,
|
|
5953
|
+
truncated: range.truncated,
|
|
5954
|
+
truncatedBy: range.truncatedBy,
|
|
5955
|
+
totalLines: range.selectedLines,
|
|
5956
|
+
totalBytes: range.selectedBytes,
|
|
5957
|
+
outputLines: range.outputLines,
|
|
5958
|
+
outputBytes: range.outputBytes,
|
|
5959
|
+
lastLinePartial: false,
|
|
5960
|
+
firstLineExceedsLimit: range.firstLineExceedsLimit,
|
|
5961
|
+
maxLines: DEFAULT_MAX_LINES,
|
|
5962
|
+
maxBytes: DEFAULT_MAX_BYTES
|
|
5963
|
+
};
|
|
5964
|
+
const readStartsAtBeginning = offset === void 0 || offset === 1;
|
|
5965
|
+
const readReachedEndOfFile = startLine + range.selectedLines >= range.totalLines;
|
|
5966
|
+
const isCompleteTextKnown = readStartsAtBeginning && readReachedEndOfFile && !truncation.truncated && !truncation.firstLineExceedsLimit;
|
|
5967
|
+
if (fileObservationState.isUnchangedReadRange(absolutePath, rangeKey, snapshot, range.selectedContentHash)) {
|
|
5968
|
+
if (signal) {
|
|
5969
|
+
signal.removeEventListener("abort", onAbort);
|
|
5970
|
+
}
|
|
5971
|
+
resolve3({
|
|
5972
|
+
content: [{
|
|
5973
|
+
type: "text",
|
|
5974
|
+
text: "File unchanged since previous read of this range. Use a different offset/limit if you need another section."
|
|
5975
|
+
}],
|
|
5976
|
+
details: {
|
|
5977
|
+
unchangedSincePreviousRead: true,
|
|
5978
|
+
path: absolutePath,
|
|
5979
|
+
offset: startLineDisplay,
|
|
5980
|
+
limit
|
|
5981
|
+
}
|
|
5982
|
+
});
|
|
5983
|
+
return;
|
|
5559
5984
|
}
|
|
5560
|
-
const truncation = truncateHead(selectedContent);
|
|
5561
5985
|
let outputText;
|
|
5562
5986
|
let details;
|
|
5563
5987
|
const sourceHeader = `File: ${absolutePath}
|
|
@@ -5565,7 +5989,7 @@ Base directory: ${nodePath.dirname(absolutePath)}
|
|
|
5565
5989
|
|
|
5566
5990
|
`;
|
|
5567
5991
|
if (truncation.firstLineExceedsLimit) {
|
|
5568
|
-
const firstLineSize = formatSize(
|
|
5992
|
+
const firstLineSize = formatSize(range.firstLineBytes);
|
|
5569
5993
|
outputText = `[Line ${startLineDisplay} is ${firstLineSize}, exceeds ${formatSize(DEFAULT_MAX_BYTES)} limit.]`;
|
|
5570
5994
|
details = { truncation };
|
|
5571
5995
|
} else if (truncation.truncated) {
|
|
@@ -5582,8 +6006,8 @@ Base directory: ${nodePath.dirname(absolutePath)}
|
|
|
5582
6006
|
[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Use offset=${nextOffset} to continue.]`;
|
|
5583
6007
|
}
|
|
5584
6008
|
details = { truncation };
|
|
5585
|
-
} else if (userLimitedLines !== void 0 && startLine + userLimitedLines <
|
|
5586
|
-
const remaining =
|
|
6009
|
+
} else if (userLimitedLines !== void 0 && startLine + userLimitedLines < range.totalLines) {
|
|
6010
|
+
const remaining = range.totalLines - (startLine + userLimitedLines);
|
|
5587
6011
|
const nextOffset = startLine + userLimitedLines + 1;
|
|
5588
6012
|
outputText = truncation.content;
|
|
5589
6013
|
outputText += `
|
|
@@ -5592,13 +6016,20 @@ Base directory: ${nodePath.dirname(absolutePath)}
|
|
|
5592
6016
|
} else {
|
|
5593
6017
|
outputText = truncation.content;
|
|
5594
6018
|
}
|
|
6019
|
+
fileObservationState.recordTextRead(
|
|
6020
|
+
absolutePath,
|
|
6021
|
+
rangeKey,
|
|
6022
|
+
snapshot,
|
|
6023
|
+
range.selectedContentHash,
|
|
6024
|
+
isCompleteTextKnown
|
|
6025
|
+
);
|
|
5595
6026
|
outputText = sourceHeader + outputText;
|
|
5596
6027
|
const textContent = [{ type: "text", text: outputText }];
|
|
5597
6028
|
if (aborted) return;
|
|
5598
6029
|
if (signal) {
|
|
5599
6030
|
signal.removeEventListener("abort", onAbort);
|
|
5600
6031
|
}
|
|
5601
|
-
|
|
6032
|
+
resolve3({ content: textContent, details });
|
|
5602
6033
|
} catch (error) {
|
|
5603
6034
|
if (signal) {
|
|
5604
6035
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -5613,6 +6044,10 @@ Base directory: ${nodePath.dirname(absolutePath)}
|
|
|
5613
6044
|
}
|
|
5614
6045
|
};
|
|
5615
6046
|
}
|
|
6047
|
+
function isSpecialDevicePath(path3) {
|
|
6048
|
+
const normalized = nodePath.resolve(path3).replace(/\\/g, "/");
|
|
6049
|
+
return /^\/dev\/(?:zero|random|urandom|full|stdin|stdout|stderr|tty|console)$/.test(normalized) || /^\/dev\/fd\/[0-2]$/.test(normalized) || /^\/proc\/(?:self|\d+)\/fd\/[0-2]$/.test(normalized);
|
|
6050
|
+
}
|
|
5616
6051
|
function detectUnderstandingKind(filePath) {
|
|
5617
6052
|
const lower = filePath.toLowerCase();
|
|
5618
6053
|
if (/\.(mp4|mov|m4v|webm|avi|mkv)$/.test(lower)) {
|
|
@@ -5734,6 +6169,8 @@ function parseWebpDimensions(bytes) {
|
|
|
5734
6169
|
// ../../packages/shared-headless-capabilities/src/builtin/fs/write.ts
|
|
5735
6170
|
function createWriteTool(cwd, options) {
|
|
5736
6171
|
const fs2 = getFileSystem();
|
|
6172
|
+
const usesExternalFileObservationState = !!options?.fileObservationState;
|
|
6173
|
+
const fileObservationState = options?.fileObservationState ?? createFileObservationState();
|
|
5737
6174
|
const rootSchema = buildRootParameterSchema(cwd, options);
|
|
5738
6175
|
const writeSchema = Type.Object({
|
|
5739
6176
|
path: Type.String({ description: "Path to the file to write." }),
|
|
@@ -5744,14 +6181,14 @@ function createWriteTool(cwd, options) {
|
|
|
5744
6181
|
return {
|
|
5745
6182
|
name: "write",
|
|
5746
6183
|
label: "write",
|
|
5747
|
-
description: `Write
|
|
6184
|
+
description: `Write complete file contents. Use this to create a new file, or to intentionally replace an entire existing file after reading the latest full file with read_file. Do not use for targeted edits to existing files; use edit_file instead. For existing files, omitted content will be deleted. Automatically creates parent directories. ${pathResolutionDescription}`,
|
|
5748
6185
|
parameters: writeSchema,
|
|
5749
6186
|
execute: async (_toolCallId, params, signal) => {
|
|
5750
6187
|
const { path: filePath, root, content } = params;
|
|
5751
6188
|
const absolutePath = resolveToCwd(filePath, cwd, options, root);
|
|
5752
6189
|
const dir = fs2.dirname(absolutePath);
|
|
5753
6190
|
return new Promise(
|
|
5754
|
-
(
|
|
6191
|
+
(resolve3, reject) => {
|
|
5755
6192
|
if (signal?.aborted) {
|
|
5756
6193
|
reject(new Error("Operation aborted"));
|
|
5757
6194
|
return;
|
|
@@ -5768,12 +6205,38 @@ function createWriteTool(cwd, options) {
|
|
|
5768
6205
|
try {
|
|
5769
6206
|
await fs2.mkdir(dir, { recursive: true });
|
|
5770
6207
|
if (aborted) return;
|
|
6208
|
+
let fileExists = false;
|
|
6209
|
+
try {
|
|
6210
|
+
const stat = await fs2.stat(absolutePath);
|
|
6211
|
+
fileExists = stat.isFile;
|
|
6212
|
+
} catch {
|
|
6213
|
+
fileExists = false;
|
|
6214
|
+
}
|
|
6215
|
+
if (fileExists) {
|
|
6216
|
+
const currentContent = String(await fs2.readFile(absolutePath, "utf-8"));
|
|
6217
|
+
const currentStat = await fs2.stat(absolutePath);
|
|
6218
|
+
const currentSnapshot = snapshotFromStatAndContent(currentStat, currentContent);
|
|
6219
|
+
const observedSnapshot = fileObservationState.getCompleteTextSnapshot(absolutePath);
|
|
6220
|
+
if (!observedSnapshot) {
|
|
6221
|
+
throw new Error(
|
|
6222
|
+
usesExternalFileObservationState ? "Read the current full file with read_file before overwriting it, or use edit_file for targeted changes." : "Existing-file overwrite requires write_file to share a fileObservationState with read_file; use createSharedFileSystemTools or pass the same fileObservationState to both tools."
|
|
6223
|
+
);
|
|
6224
|
+
}
|
|
6225
|
+
if (!snapshotsHaveSameContent(observedSnapshot, currentSnapshot)) {
|
|
6226
|
+
throw new Error("File changed since it was read. Read the latest file contents before overwriting it.");
|
|
6227
|
+
}
|
|
6228
|
+
}
|
|
5771
6229
|
await fs2.writeFile(absolutePath, content, "utf-8");
|
|
6230
|
+
const writtenStat = await fs2.stat(absolutePath);
|
|
6231
|
+
fileObservationState.recordCompleteTextWrite(
|
|
6232
|
+
absolutePath,
|
|
6233
|
+
snapshotFromStatAndContent(writtenStat, content)
|
|
6234
|
+
);
|
|
5772
6235
|
if (aborted) return;
|
|
5773
6236
|
if (signal) {
|
|
5774
6237
|
signal.removeEventListener("abort", onAbort);
|
|
5775
6238
|
}
|
|
5776
|
-
|
|
6239
|
+
resolve3({
|
|
5777
6240
|
content: [{ type: "text", text: `Successfully wrote ${content.length} bytes to ${filePath}` }],
|
|
5778
6241
|
details: void 0
|
|
5779
6242
|
});
|
|
@@ -5839,28 +6302,30 @@ function buildFuzzyIndexMap(text) {
|
|
|
5839
6302
|
}
|
|
5840
6303
|
return { normalized, originalStartByNormalizedIndex, originalEndByNormalizedIndex };
|
|
5841
6304
|
}
|
|
5842
|
-
function
|
|
5843
|
-
if (needle.length === 0) return
|
|
5844
|
-
|
|
6305
|
+
function collectMatches(content, needle) {
|
|
6306
|
+
if (needle.length === 0) return [];
|
|
6307
|
+
const matches = [];
|
|
5845
6308
|
let index = 0;
|
|
5846
6309
|
while (true) {
|
|
5847
6310
|
const nextIndex = content.indexOf(needle, index);
|
|
5848
6311
|
if (nextIndex === -1) break;
|
|
5849
|
-
|
|
6312
|
+
matches.push({ index: nextIndex, matchLength: needle.length });
|
|
5850
6313
|
index = nextIndex + needle.length;
|
|
5851
6314
|
}
|
|
5852
|
-
return
|
|
6315
|
+
return matches;
|
|
5853
6316
|
}
|
|
5854
6317
|
function fuzzyFindText(content, oldText) {
|
|
5855
6318
|
const exactIndex = content.indexOf(oldText);
|
|
5856
6319
|
if (exactIndex !== -1) {
|
|
6320
|
+
const matches2 = collectMatches(content, oldText);
|
|
5857
6321
|
return {
|
|
5858
6322
|
found: true,
|
|
5859
6323
|
index: exactIndex,
|
|
5860
6324
|
matchLength: oldText.length,
|
|
5861
6325
|
usedFuzzyMatch: false,
|
|
5862
6326
|
contentForReplacement: content,
|
|
5863
|
-
occurrences:
|
|
6327
|
+
occurrences: matches2.length,
|
|
6328
|
+
matches: matches2,
|
|
5864
6329
|
matchKind: "exact"
|
|
5865
6330
|
};
|
|
5866
6331
|
}
|
|
@@ -5875,6 +6340,7 @@ function fuzzyFindText(content, oldText) {
|
|
|
5875
6340
|
usedFuzzyMatch: false,
|
|
5876
6341
|
contentForReplacement: content,
|
|
5877
6342
|
occurrences: 0,
|
|
6343
|
+
matches: [],
|
|
5878
6344
|
matchKind: "none"
|
|
5879
6345
|
};
|
|
5880
6346
|
}
|
|
@@ -5887,19 +6353,26 @@ function fuzzyFindText(content, oldText) {
|
|
|
5887
6353
|
usedFuzzyMatch: false,
|
|
5888
6354
|
contentForReplacement: content,
|
|
5889
6355
|
occurrences: 0,
|
|
6356
|
+
matches: [],
|
|
5890
6357
|
matchKind: "none"
|
|
5891
6358
|
};
|
|
5892
6359
|
}
|
|
5893
|
-
const
|
|
5894
|
-
const
|
|
5895
|
-
|
|
6360
|
+
const fuzzyMatches = collectMatches(fuzzyContent, fuzzyOldText);
|
|
6361
|
+
const matches = fuzzyMatches.map((match) => {
|
|
6362
|
+
const endFuzzyIndex = match.index + match.matchLength - 1;
|
|
6363
|
+
const originalStart = fuzzyMap.originalStartByNormalizedIndex[match.index] ?? match.index;
|
|
6364
|
+
const originalEnd = fuzzyMap.originalEndByNormalizedIndex[endFuzzyIndex] ?? originalStart + match.matchLength;
|
|
6365
|
+
return { index: originalStart, matchLength: originalEnd - originalStart };
|
|
6366
|
+
});
|
|
6367
|
+
const firstMatch = matches[0] ?? { index: -1, matchLength: 0 };
|
|
5896
6368
|
return {
|
|
5897
6369
|
found: true,
|
|
5898
|
-
index:
|
|
5899
|
-
matchLength:
|
|
6370
|
+
index: firstMatch.index,
|
|
6371
|
+
matchLength: firstMatch.matchLength,
|
|
5900
6372
|
usedFuzzyMatch: true,
|
|
5901
6373
|
contentForReplacement: content,
|
|
5902
|
-
occurrences:
|
|
6374
|
+
occurrences: matches.length,
|
|
6375
|
+
matches,
|
|
5903
6376
|
matchKind: "fuzzy"
|
|
5904
6377
|
};
|
|
5905
6378
|
}
|
|
@@ -5947,7 +6420,7 @@ function generateDiffString(oldContent, newContent, contextLines = 4) {
|
|
|
5947
6420
|
}
|
|
5948
6421
|
|
|
5949
6422
|
// ../../packages/shared-headless-capabilities/src/builtin/fs/edit.ts
|
|
5950
|
-
function validateEditArgs(path3, oldText, newText) {
|
|
6423
|
+
function validateEditArgs(path3, oldText, newText, replaceAll) {
|
|
5951
6424
|
const filePath = typeof path3 === "string" ? path3.trim() : "";
|
|
5952
6425
|
if (!filePath) {
|
|
5953
6426
|
throw new Error("Invalid edit_file arguments: path cannot be empty. Pass the file path in path and the exact text to replace in oldText.");
|
|
@@ -5961,11 +6434,14 @@ function validateEditArgs(path3, oldText, newText) {
|
|
|
5961
6434
|
if (oldText === newText) {
|
|
5962
6435
|
throw new Error("Invalid edit_file arguments: oldText and newText are identical, so the edit would make no change.");
|
|
5963
6436
|
}
|
|
6437
|
+
if (replaceAll !== void 0 && typeof replaceAll !== "boolean") {
|
|
6438
|
+
throw new Error("Invalid edit_file arguments: replaceAll must be a boolean when provided.");
|
|
6439
|
+
}
|
|
5964
6440
|
if (filePath.includes("<|tool") || oldText.includes("<|tool") || newText.includes("<|tool")) {
|
|
5965
6441
|
throw new Error("Invalid edit_file arguments: path, oldText, and newText must be real edit values, not tool-call markup.");
|
|
5966
6442
|
}
|
|
5967
6443
|
}
|
|
5968
|
-
function
|
|
6444
|
+
function countOccurrences(content, needle) {
|
|
5969
6445
|
if (needle.length === 0) return 0;
|
|
5970
6446
|
let count = 0;
|
|
5971
6447
|
let index = 0;
|
|
@@ -5992,6 +6468,8 @@ function createEditErrorResult(params) {
|
|
|
5992
6468
|
diff: "",
|
|
5993
6469
|
oldTextOccurrences: params.oldTextOccurrences,
|
|
5994
6470
|
newTextOccurrences: params.newTextOccurrences,
|
|
6471
|
+
replaceAll: params.replaceAll,
|
|
6472
|
+
replacedOccurrences: 0,
|
|
5995
6473
|
errorReason: params.errorReason,
|
|
5996
6474
|
recoveryHint: params.recoveryHint,
|
|
5997
6475
|
staleBaseLikely: params.staleBaseLikely,
|
|
@@ -6002,12 +6480,14 @@ function createEditErrorResult(params) {
|
|
|
6002
6480
|
}
|
|
6003
6481
|
function createEditTool(cwd, options) {
|
|
6004
6482
|
const fs2 = getFileSystem();
|
|
6483
|
+
const fileObservationState = options?.fileObservationState ?? createFileObservationState();
|
|
6005
6484
|
const rootSchema = buildRootParameterSchema(cwd, options);
|
|
6006
6485
|
const editSchema = Type.Object({
|
|
6007
6486
|
path: Type.String({ description: "Path to the file to edit." }),
|
|
6008
6487
|
...rootSchema ? { root: rootSchema } : {},
|
|
6009
6488
|
oldText: Type.String({ description: "Exact text to find and replace (must match exactly)" }),
|
|
6010
|
-
newText: Type.String({ description: "New text to replace the old text with" })
|
|
6489
|
+
newText: Type.String({ description: "New text to replace the old text with" }),
|
|
6490
|
+
replaceAll: Type.Optional(Type.Boolean({ description: "Replace every occurrence of oldText. Only set this to true when every matching occurrence should be replaced." }))
|
|
6011
6491
|
});
|
|
6012
6492
|
const pathResolutionDescription = buildPathResolutionDescription(cwd, options);
|
|
6013
6493
|
return {
|
|
@@ -6016,10 +6496,11 @@ function createEditTool(cwd, options) {
|
|
|
6016
6496
|
description: `Edit a file by replacing exact text. The oldText must match exactly (including whitespace). Use this for precise, surgical edits. ${pathResolutionDescription}`,
|
|
6017
6497
|
parameters: editSchema,
|
|
6018
6498
|
execute: async (_toolCallId, params, signal) => {
|
|
6019
|
-
const { path: path3, root, oldText, newText } = params;
|
|
6020
|
-
validateEditArgs(path3, oldText, newText);
|
|
6499
|
+
const { path: path3, root, oldText, newText, replaceAll } = params;
|
|
6500
|
+
validateEditArgs(path3, oldText, newText, replaceAll);
|
|
6501
|
+
const shouldReplaceAll = replaceAll === true;
|
|
6021
6502
|
const absolutePath = resolveToCwd(path3, cwd, options, root);
|
|
6022
|
-
return new Promise((
|
|
6503
|
+
return new Promise((resolve3, reject) => {
|
|
6023
6504
|
if (signal?.aborted) {
|
|
6024
6505
|
reject(new Error("Operation aborted"));
|
|
6025
6506
|
return;
|
|
@@ -6049,50 +6530,57 @@ function createEditTool(cwd, options) {
|
|
|
6049
6530
|
const normalizedContent = normalizeToLF(content);
|
|
6050
6531
|
const normalizedOldText = normalizeToLF(oldText);
|
|
6051
6532
|
const normalizedNewText = normalizeToLF(newText);
|
|
6052
|
-
const newTextOccurrences =
|
|
6533
|
+
const newTextOccurrences = countOccurrences(normalizedContent, normalizedNewText);
|
|
6053
6534
|
const matchResult = fuzzyFindText(normalizedContent, normalizedOldText);
|
|
6054
6535
|
if (!matchResult.found) {
|
|
6055
6536
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
6056
|
-
|
|
6537
|
+
resolve3(
|
|
6057
6538
|
createEditErrorResult({
|
|
6058
6539
|
message: `Could not find oldText in ${path3}.`,
|
|
6059
6540
|
matchKind: "none",
|
|
6060
6541
|
errorReason: "old_text_not_found",
|
|
6061
6542
|
oldTextOccurrences: 0,
|
|
6062
6543
|
newTextOccurrences,
|
|
6544
|
+
replaceAll: shouldReplaceAll,
|
|
6063
6545
|
staleBaseLikely: newTextOccurrences > 0,
|
|
6064
6546
|
alreadyAppliedCandidate: newTextOccurrences > 0,
|
|
6065
|
-
recoveryHint: newTextOccurrences > 0 ? "The replacement text already appears in the current file. Read the current file before retrying; this may be a stale oldText from an earlier edit in the same turn." : "Read the current file and retry with
|
|
6547
|
+
recoveryHint: newTextOccurrences > 0 ? "The replacement text already appears in the current file. Read the current file before retrying; this may be a stale oldText from an earlier edit in the same turn." : "Read the current file and retry with exact text copied from the latest file contents."
|
|
6066
6548
|
})
|
|
6067
6549
|
);
|
|
6068
6550
|
return;
|
|
6069
6551
|
}
|
|
6070
|
-
if (matchResult.occurrences > 1) {
|
|
6552
|
+
if (matchResult.occurrences > 1 && !shouldReplaceAll) {
|
|
6071
6553
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
6072
|
-
|
|
6554
|
+
resolve3(
|
|
6073
6555
|
createEditErrorResult({
|
|
6074
6556
|
message: `Found ${matchResult.occurrences} occurrences of oldText in ${path3}. The text must be unique.`,
|
|
6075
6557
|
matchKind: matchResult.matchKind,
|
|
6076
6558
|
errorReason: "old_text_not_unique",
|
|
6077
6559
|
oldTextOccurrences: matchResult.occurrences,
|
|
6078
6560
|
newTextOccurrences,
|
|
6079
|
-
|
|
6561
|
+
replaceAll: shouldReplaceAll,
|
|
6562
|
+
recoveryHint: "Read the current file and retry with a larger exact block copied from the latest file contents, or set replaceAll to true if every occurrence should be replaced."
|
|
6080
6563
|
})
|
|
6081
6564
|
);
|
|
6082
6565
|
return;
|
|
6083
6566
|
}
|
|
6084
6567
|
if (aborted) return;
|
|
6085
|
-
const baseContent =
|
|
6086
|
-
const
|
|
6568
|
+
const baseContent = matchResult.contentForReplacement;
|
|
6569
|
+
const matchesToReplace = shouldReplaceAll ? matchResult.matches : matchResult.matches.slice(0, 1);
|
|
6570
|
+
let newContent = baseContent;
|
|
6571
|
+
for (const match of [...matchesToReplace].sort((left, right) => right.index - left.index)) {
|
|
6572
|
+
newContent = newContent.substring(0, match.index) + normalizedNewText + newContent.substring(match.index + match.matchLength);
|
|
6573
|
+
}
|
|
6087
6574
|
if (baseContent === newContent) {
|
|
6088
6575
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
6089
|
-
|
|
6576
|
+
resolve3(
|
|
6090
6577
|
createEditErrorResult({
|
|
6091
6578
|
message: `No changes made to ${path3}. The replacement produced identical content.`,
|
|
6092
6579
|
matchKind: matchResult.matchKind,
|
|
6093
6580
|
errorReason: "replacement_no_change",
|
|
6094
6581
|
oldTextOccurrences: matchResult.occurrences,
|
|
6095
6582
|
newTextOccurrences,
|
|
6583
|
+
replaceAll: shouldReplaceAll,
|
|
6096
6584
|
recoveryHint: "Read the current file and verify whether the intended edit is already present or whether oldText/newText need a larger exact block.",
|
|
6097
6585
|
alreadyAppliedCandidate: newTextOccurrences > 0
|
|
6098
6586
|
})
|
|
@@ -6101,16 +6589,17 @@ function createEditTool(cwd, options) {
|
|
|
6101
6589
|
}
|
|
6102
6590
|
const finalContent = bom + restoreLineEndings(newContent, originalEnding);
|
|
6103
6591
|
await fs2.writeFile(absolutePath, finalContent, "utf-8");
|
|
6592
|
+
fileObservationState.invalidate(absolutePath);
|
|
6104
6593
|
if (aborted) return;
|
|
6105
6594
|
if (signal) {
|
|
6106
6595
|
signal.removeEventListener("abort", onAbort);
|
|
6107
6596
|
}
|
|
6108
6597
|
const diffResult = generateDiffString(content, restoreLineEndings(newContent, originalEnding));
|
|
6109
|
-
|
|
6598
|
+
resolve3({
|
|
6110
6599
|
content: [
|
|
6111
6600
|
{
|
|
6112
6601
|
type: "text",
|
|
6113
|
-
text: `Successfully replaced text in ${path3} using ${matchResult.matchKind} match.`
|
|
6602
|
+
text: `Successfully replaced text in ${path3} using ${matchResult.matchKind} match (${matchesToReplace.length} occurrence${matchesToReplace.length === 1 ? "" : "s"}).`
|
|
6114
6603
|
},
|
|
6115
6604
|
{ type: "text", text: diffResult.diff }
|
|
6116
6605
|
],
|
|
@@ -6121,7 +6610,9 @@ function createEditTool(cwd, options) {
|
|
|
6121
6610
|
diff: diffResult.diff,
|
|
6122
6611
|
firstChangedLine: diffResult.firstChangedLine,
|
|
6123
6612
|
oldTextOccurrences: matchResult.occurrences,
|
|
6124
|
-
newTextOccurrences
|
|
6613
|
+
newTextOccurrences,
|
|
6614
|
+
replaceAll: shouldReplaceAll,
|
|
6615
|
+
replacedOccurrences: matchesToReplace.length
|
|
6125
6616
|
}
|
|
6126
6617
|
});
|
|
6127
6618
|
} catch (error) {
|
|
@@ -6155,7 +6646,7 @@ function createLsTool(cwd, options) {
|
|
|
6155
6646
|
description: `List directory contents. Use this to inspect folders before reading or writing files. Example: list_dir path='Scripts' or list_dir path='Data'. ${pathResolutionDescription} Returns entries sorted alphabetically, with '/' suffix for directories. Includes dotfiles. Output is truncated to ${DEFAULT_LIMIT} entries or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first).`,
|
|
6156
6647
|
parameters: lsSchema,
|
|
6157
6648
|
execute: async (_toolCallId, params, signal) => {
|
|
6158
|
-
return new Promise((
|
|
6649
|
+
return new Promise((resolve3, reject) => {
|
|
6159
6650
|
if (signal?.aborted) {
|
|
6160
6651
|
reject(new Error("Operation aborted"));
|
|
6161
6652
|
return;
|
|
@@ -6205,7 +6696,7 @@ function createLsTool(cwd, options) {
|
|
|
6205
6696
|
}
|
|
6206
6697
|
signal?.removeEventListener("abort", onAbort);
|
|
6207
6698
|
if (results.length === 0) {
|
|
6208
|
-
|
|
6699
|
+
resolve3({ content: [{ type: "text", text: "(empty directory)" }], details: void 0 });
|
|
6209
6700
|
return;
|
|
6210
6701
|
}
|
|
6211
6702
|
const rawOutput = results.join("\n");
|
|
@@ -6226,7 +6717,7 @@ function createLsTool(cwd, options) {
|
|
|
6226
6717
|
|
|
6227
6718
|
[${notices.join(". ")}]`;
|
|
6228
6719
|
}
|
|
6229
|
-
|
|
6720
|
+
resolve3({
|
|
6230
6721
|
content: [{ type: "text", text: output }],
|
|
6231
6722
|
details: Object.keys(details).length > 0 ? details : void 0
|
|
6232
6723
|
});
|
|
@@ -6402,11 +6893,11 @@ function waitForFileCompletion(requestId) {
|
|
|
6402
6893
|
if (existing) {
|
|
6403
6894
|
return Promise.resolve(existing);
|
|
6404
6895
|
}
|
|
6405
|
-
return new Promise((
|
|
6406
|
-
store.waiters.set(requestId,
|
|
6896
|
+
return new Promise((resolve3) => {
|
|
6897
|
+
store.waiters.set(requestId, resolve3);
|
|
6407
6898
|
});
|
|
6408
6899
|
}
|
|
6409
|
-
async function runNativeGrep(searchPath, pattern, globPattern, ignoreCase, literal, contextLines, limit, signal) {
|
|
6900
|
+
async function runNativeGrep(searchPath, pattern, globPattern, ignoreCase, literal, contextLines, limit, outputMode, offset, signal) {
|
|
6410
6901
|
const bridge = getNativeFileBridge();
|
|
6411
6902
|
if (!bridge) {
|
|
6412
6903
|
throw new Error("CS.Pie.PieFileBridge not available");
|
|
@@ -6418,7 +6909,9 @@ async function runNativeGrep(searchPath, pattern, globPattern, ignoreCase, liter
|
|
|
6418
6909
|
!!ignoreCase,
|
|
6419
6910
|
!!literal,
|
|
6420
6911
|
contextLines,
|
|
6421
|
-
limit
|
|
6912
|
+
limit,
|
|
6913
|
+
outputMode,
|
|
6914
|
+
offset
|
|
6422
6915
|
);
|
|
6423
6916
|
const onAbort = () => bridge.CancelRequest(requestId);
|
|
6424
6917
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
@@ -6443,6 +6936,45 @@ async function runNativeGrep(searchPath, pattern, globPattern, ignoreCase, liter
|
|
|
6443
6936
|
bridge.ReleaseRequest(requestId);
|
|
6444
6937
|
}
|
|
6445
6938
|
}
|
|
6939
|
+
function normalizeOutputMode(value) {
|
|
6940
|
+
if (value === void 0 || value === null || value === "content") return "content";
|
|
6941
|
+
if (value === "files_with_matches" || value === "count") return value;
|
|
6942
|
+
throw new Error("Invalid grep_text arguments: outputMode must be one of content, files_with_matches, or count.");
|
|
6943
|
+
}
|
|
6944
|
+
function normalizeNonNegativeInteger(value, fallback, name) {
|
|
6945
|
+
if (value === void 0 || value === null) return fallback;
|
|
6946
|
+
const numberValue = Number(value);
|
|
6947
|
+
if (!Number.isInteger(numberValue) || numberValue < 0) {
|
|
6948
|
+
throw new Error(`Invalid grep_text arguments: ${name} must be a non-negative integer.`);
|
|
6949
|
+
}
|
|
6950
|
+
return numberValue;
|
|
6951
|
+
}
|
|
6952
|
+
function formatContentMatches(matches, contextLines) {
|
|
6953
|
+
const outputLines = [];
|
|
6954
|
+
let linesTruncated = false;
|
|
6955
|
+
for (const match of matches) {
|
|
6956
|
+
const start = contextLines > 0 ? Math.max(0, match.lineIndex - contextLines) : match.lineIndex;
|
|
6957
|
+
const end = contextLines > 0 ? Math.min(match.lines.length - 1, match.lineIndex + contextLines) : match.lineIndex;
|
|
6958
|
+
for (let c = start; c <= end; c++) {
|
|
6959
|
+
const lineText = match.lines[c];
|
|
6960
|
+
const { text: truncatedText, wasTruncated } = truncateLine(lineText);
|
|
6961
|
+
if (wasTruncated) linesTruncated = true;
|
|
6962
|
+
const currentLineNum = c + 1;
|
|
6963
|
+
if (c === match.lineIndex) {
|
|
6964
|
+
outputLines.push(`${match.relativePath}:${currentLineNum}: ${truncatedText}`);
|
|
6965
|
+
} else {
|
|
6966
|
+
outputLines.push(`${match.relativePath}-${currentLineNum}- ${truncatedText}`);
|
|
6967
|
+
}
|
|
6968
|
+
}
|
|
6969
|
+
}
|
|
6970
|
+
return { lines: outputLines, linesTruncated };
|
|
6971
|
+
}
|
|
6972
|
+
function sortFileSummariesByMtime(files) {
|
|
6973
|
+
return files.sort((left, right) => {
|
|
6974
|
+
if (right.mtimeMs !== left.mtimeMs) return right.mtimeMs - left.mtimeMs;
|
|
6975
|
+
return left.relativePath.localeCompare(right.relativePath);
|
|
6976
|
+
});
|
|
6977
|
+
}
|
|
6446
6978
|
async function collectFiles(dirPath, globPattern, fs2, isGitignored = () => false) {
|
|
6447
6979
|
const files = [];
|
|
6448
6980
|
const queue = [{ dir: dirPath, relPrefix: "" }];
|
|
@@ -6502,13 +7034,19 @@ function createGrepTextTool(cwd, options) {
|
|
|
6502
7034
|
context: Type.Optional(
|
|
6503
7035
|
Type.Number({ description: "Number of lines to show before and after each match (default: 0)" })
|
|
6504
7036
|
),
|
|
7037
|
+
outputMode: Type.Optional(Type.Union([
|
|
7038
|
+
Type.Literal("content"),
|
|
7039
|
+
Type.Literal("files_with_matches"),
|
|
7040
|
+
Type.Literal("count")
|
|
7041
|
+
], { description: "Result mode: content returns matching lines, files_with_matches returns matching file paths, count returns per-file match counts." })),
|
|
7042
|
+
offset: Type.Optional(Type.Number({ description: "Number of results to skip before returning output (default: 0)." })),
|
|
6505
7043
|
limit: Type.Optional(Type.Number({ description: "Maximum number of matches to return (default: 100)" }))
|
|
6506
7044
|
});
|
|
6507
7045
|
const pathResolutionDescription = buildPathResolutionDescription(cwd, options);
|
|
6508
7046
|
return {
|
|
6509
7047
|
name: "grep_text",
|
|
6510
7048
|
label: "grep_text",
|
|
6511
|
-
description: `Search file contents for a pattern. ${pathResolutionDescription}
|
|
7049
|
+
description: `Search file contents for a pattern. ${pathResolutionDescription} outputMode='content' returns matching lines with file paths and line numbers, outputMode='files_with_matches' returns matching file paths, and outputMode='count' returns per-file match counts. Output is truncated to ${DEFAULT_LIMIT2} results or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Long lines are truncated to ${GREP_MAX_LINE_LENGTH} chars.`,
|
|
6512
7050
|
parameters: grepSchema,
|
|
6513
7051
|
execute: async (_toolCallId, params, signal) => {
|
|
6514
7052
|
const {
|
|
@@ -6519,9 +7057,11 @@ function createGrepTextTool(cwd, options) {
|
|
|
6519
7057
|
ignoreCase,
|
|
6520
7058
|
literal,
|
|
6521
7059
|
context: contextLines,
|
|
7060
|
+
outputMode: rawOutputMode,
|
|
7061
|
+
offset: rawOffset,
|
|
6522
7062
|
limit
|
|
6523
7063
|
} = params;
|
|
6524
|
-
return new Promise((
|
|
7064
|
+
return new Promise((resolve3, reject) => {
|
|
6525
7065
|
if (signal?.aborted) {
|
|
6526
7066
|
reject(new Error("Operation aborted"));
|
|
6527
7067
|
return;
|
|
@@ -6531,6 +7071,8 @@ function createGrepTextTool(cwd, options) {
|
|
|
6531
7071
|
(async () => {
|
|
6532
7072
|
try {
|
|
6533
7073
|
const searchPath = resolveToCwd(searchDir || ".", cwd, options, root);
|
|
7074
|
+
const outputMode = normalizeOutputMode(rawOutputMode);
|
|
7075
|
+
const offset = normalizeNonNegativeInteger(rawOffset, 0, "offset");
|
|
6534
7076
|
const effectiveLimit = Math.max(1, limit ?? DEFAULT_LIMIT2);
|
|
6535
7077
|
const ctx = contextLines && contextLines > 0 ? contextLines : 0;
|
|
6536
7078
|
let isDir;
|
|
@@ -6558,6 +7100,8 @@ function createGrepTextTool(cwd, options) {
|
|
|
6558
7100
|
let matchCount = 0;
|
|
6559
7101
|
let matchLimitReached = false;
|
|
6560
7102
|
let linesTruncated = false;
|
|
7103
|
+
let filesScanned = 0;
|
|
7104
|
+
let totalFilesWithMatches = 0;
|
|
6561
7105
|
const isGitignored = await loadGitignoreMatcher(searchPath, fs2);
|
|
6562
7106
|
if (platform === "puerts" && getNativeFileBridge()) {
|
|
6563
7107
|
const nativeResult = await runNativeGrep(
|
|
@@ -6568,12 +7112,16 @@ function createGrepTextTool(cwd, options) {
|
|
|
6568
7112
|
!!literal,
|
|
6569
7113
|
ctx,
|
|
6570
7114
|
effectiveLimit,
|
|
7115
|
+
outputMode,
|
|
7116
|
+
offset,
|
|
6571
7117
|
signal
|
|
6572
7118
|
);
|
|
6573
7119
|
outputLines = nativeResult.Lines ?? [];
|
|
6574
|
-
matchCount = nativeResult.MatchCount ?? 0;
|
|
7120
|
+
matchCount = nativeResult.TotalMatches ?? nativeResult.MatchCount ?? 0;
|
|
6575
7121
|
matchLimitReached = !!nativeResult.MatchLimitReached;
|
|
6576
7122
|
linesTruncated = !!nativeResult.LinesTruncated;
|
|
7123
|
+
filesScanned = nativeResult.FilesScanned ?? 0;
|
|
7124
|
+
totalFilesWithMatches = nativeResult.TotalFilesWithMatches ?? 0;
|
|
6577
7125
|
globalThis.pieBridge?.log?.(
|
|
6578
7126
|
"info",
|
|
6579
7127
|
`[grep_text] path=${searchPath} pattern=${pattern} matches=${matchCount} files=${nativeResult.FilesScanned ?? 0}`
|
|
@@ -6586,11 +7134,9 @@ function createGrepTextTool(cwd, options) {
|
|
|
6586
7134
|
} else {
|
|
6587
7135
|
filePaths = [searchPath];
|
|
6588
7136
|
}
|
|
7137
|
+
const contentMatches = [];
|
|
7138
|
+
const fileSummaries = /* @__PURE__ */ new Map();
|
|
6589
7139
|
for (const filePath of filePaths) {
|
|
6590
|
-
if (matchCount >= effectiveLimit) {
|
|
6591
|
-
matchLimitReached = true;
|
|
6592
|
-
break;
|
|
6593
|
-
}
|
|
6594
7140
|
if (signal?.aborted) {
|
|
6595
7141
|
reject(new Error("Operation aborted"));
|
|
6596
7142
|
return;
|
|
@@ -6603,36 +7149,65 @@ function createGrepTextTool(cwd, options) {
|
|
|
6603
7149
|
}
|
|
6604
7150
|
const lines = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
6605
7151
|
const relativePath = isDir ? fs2.relative(searchPath, filePath) : fs2.basename(filePath);
|
|
7152
|
+
filesScanned++;
|
|
7153
|
+
let fileMatchCount = 0;
|
|
6606
7154
|
for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
|
|
6607
|
-
if (matchCount >= effectiveLimit) {
|
|
6608
|
-
matchLimitReached = true;
|
|
6609
|
-
break;
|
|
6610
|
-
}
|
|
6611
7155
|
regex.lastIndex = 0;
|
|
6612
7156
|
if (regex.test(lines[lineIdx])) {
|
|
6613
7157
|
matchCount++;
|
|
6614
|
-
|
|
6615
|
-
|
|
6616
|
-
|
|
6617
|
-
const lineText = lines[c];
|
|
6618
|
-
const { text: truncatedText, wasTruncated } = truncateLine(lineText);
|
|
6619
|
-
if (wasTruncated) linesTruncated = true;
|
|
6620
|
-
const currentLineNum = c + 1;
|
|
6621
|
-
if (c === lineIdx) {
|
|
6622
|
-
outputLines.push(`${relativePath}:${currentLineNum}: ${truncatedText}`);
|
|
6623
|
-
} else {
|
|
6624
|
-
outputLines.push(`${relativePath}-${currentLineNum}- ${truncatedText}`);
|
|
6625
|
-
}
|
|
7158
|
+
fileMatchCount++;
|
|
7159
|
+
if (outputMode === "content" && matchCount > offset && matchCount <= offset + effectiveLimit) {
|
|
7160
|
+
contentMatches.push({ filePath, relativePath, lineIndex: lineIdx, lines });
|
|
6626
7161
|
}
|
|
6627
7162
|
}
|
|
6628
7163
|
}
|
|
7164
|
+
if (fileMatchCount > 0) {
|
|
7165
|
+
const fileStat = await fs2.stat(filePath);
|
|
7166
|
+
fileSummaries.set(filePath, {
|
|
7167
|
+
filePath,
|
|
7168
|
+
relativePath,
|
|
7169
|
+
mtimeMs: fileStat.mtime.getTime(),
|
|
7170
|
+
matchCount: fileMatchCount
|
|
7171
|
+
});
|
|
7172
|
+
}
|
|
7173
|
+
}
|
|
7174
|
+
totalFilesWithMatches = fileSummaries.size;
|
|
7175
|
+
if (outputMode === "content") {
|
|
7176
|
+
matchLimitReached = matchCount > offset + effectiveLimit;
|
|
7177
|
+
const formatted = formatContentMatches(contentMatches.slice(0, effectiveLimit), ctx);
|
|
7178
|
+
outputLines = formatted.lines;
|
|
7179
|
+
linesTruncated = formatted.linesTruncated;
|
|
7180
|
+
} else {
|
|
7181
|
+
const sortedFiles = sortFileSummariesByMtime([...fileSummaries.values()]);
|
|
7182
|
+
const page = sortedFiles.slice(offset, offset + effectiveLimit);
|
|
7183
|
+
if (offset + effectiveLimit < sortedFiles.length) {
|
|
7184
|
+
matchLimitReached = true;
|
|
7185
|
+
}
|
|
7186
|
+
if (outputMode === "files_with_matches") {
|
|
7187
|
+
outputLines = page.map((file) => file.relativePath);
|
|
7188
|
+
} else {
|
|
7189
|
+
outputLines = [
|
|
7190
|
+
`Total matches: ${matchCount}`,
|
|
7191
|
+
`Files with matches: ${sortedFiles.length}`,
|
|
7192
|
+
...page.map((file) => `${file.relativePath}: ${file.matchCount}`)
|
|
7193
|
+
];
|
|
7194
|
+
}
|
|
6629
7195
|
}
|
|
6630
7196
|
}
|
|
6631
7197
|
signal?.removeEventListener("abort", onAbort);
|
|
6632
7198
|
if (matchCount === 0) {
|
|
6633
|
-
|
|
7199
|
+
resolve3({
|
|
6634
7200
|
content: [{ type: "text", text: "No matches found" }],
|
|
6635
|
-
details: {
|
|
7201
|
+
details: {
|
|
7202
|
+
noMatches: true,
|
|
7203
|
+
outputMode,
|
|
7204
|
+
totalMatches: 0,
|
|
7205
|
+
totalFilesWithMatches: 0,
|
|
7206
|
+
filesScanned,
|
|
7207
|
+
offset,
|
|
7208
|
+
limit: effectiveLimit,
|
|
7209
|
+
resultLimitReached: false
|
|
7210
|
+
}
|
|
6636
7211
|
});
|
|
6637
7212
|
return;
|
|
6638
7213
|
}
|
|
@@ -6640,10 +7215,17 @@ function createGrepTextTool(cwd, options) {
|
|
|
6640
7215
|
const truncation = truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER });
|
|
6641
7216
|
let output = truncation.content;
|
|
6642
7217
|
const details = {};
|
|
7218
|
+
details.outputMode = outputMode;
|
|
7219
|
+
details.totalMatches = matchCount;
|
|
7220
|
+
details.totalFilesWithMatches = totalFilesWithMatches;
|
|
7221
|
+
details.filesScanned = filesScanned;
|
|
7222
|
+
details.offset = offset;
|
|
7223
|
+
details.limit = effectiveLimit;
|
|
7224
|
+
details.resultLimitReached = matchLimitReached;
|
|
6643
7225
|
const notices = [];
|
|
6644
7226
|
if (matchLimitReached) {
|
|
6645
7227
|
notices.push(
|
|
6646
|
-
`${effectiveLimit} matches limit reached. Use
|
|
7228
|
+
outputMode === "content" ? `${effectiveLimit} matches limit reached. Use offset=${offset + effectiveLimit} for more, increase limit, or refine pattern` : `${effectiveLimit} results limit reached. Use offset=${offset + effectiveLimit} for more, increase limit, or refine pattern`
|
|
6647
7229
|
);
|
|
6648
7230
|
details.matchLimitReached = effectiveLimit;
|
|
6649
7231
|
}
|
|
@@ -6662,7 +7244,7 @@ function createGrepTextTool(cwd, options) {
|
|
|
6662
7244
|
|
|
6663
7245
|
[${notices.join(". ")}]`;
|
|
6664
7246
|
}
|
|
6665
|
-
|
|
7247
|
+
resolve3({
|
|
6666
7248
|
content: [{ type: "text", text: output }],
|
|
6667
7249
|
details: Object.keys(details).length > 0 ? details : void 0
|
|
6668
7250
|
});
|
|
@@ -6745,8 +7327,8 @@ function waitForFileCompletion2(requestId) {
|
|
|
6745
7327
|
if (existing) {
|
|
6746
7328
|
return Promise.resolve(existing);
|
|
6747
7329
|
}
|
|
6748
|
-
return new Promise((
|
|
6749
|
-
store.waiters.set(requestId,
|
|
7330
|
+
return new Promise((resolve3) => {
|
|
7331
|
+
store.waiters.set(requestId, resolve3);
|
|
6750
7332
|
});
|
|
6751
7333
|
}
|
|
6752
7334
|
async function runNativeFind(searchPath, pattern, limit, signal) {
|
|
@@ -6836,7 +7418,7 @@ function createFindFilesTool(cwd, options) {
|
|
|
6836
7418
|
parameters: findSchema,
|
|
6837
7419
|
execute: async (_toolCallId, params, signal) => {
|
|
6838
7420
|
const { pattern, path: searchDir, root, limit } = params;
|
|
6839
|
-
return new Promise((
|
|
7421
|
+
return new Promise((resolve3, reject) => {
|
|
6840
7422
|
if (signal?.aborted) {
|
|
6841
7423
|
reject(new Error("Operation aborted"));
|
|
6842
7424
|
return;
|
|
@@ -6852,10 +7434,12 @@ function createFindFilesTool(cwd, options) {
|
|
|
6852
7434
|
return;
|
|
6853
7435
|
}
|
|
6854
7436
|
let results;
|
|
7437
|
+
let resultLimitReached = false;
|
|
6855
7438
|
const isGitignored = await loadGitignoreMatcher(searchPath, fs2);
|
|
6856
7439
|
if (platform === "puerts" && getNativeFileBridge2()) {
|
|
6857
7440
|
const nativeResult = await runNativeFind(searchPath, pattern, effectiveLimit, signal);
|
|
6858
7441
|
results = (nativeResult.Results ?? []).filter((filePath) => !isGitignored(filePath, false));
|
|
7442
|
+
resultLimitReached = !!nativeResult.LimitReached && results.length >= effectiveLimit;
|
|
6859
7443
|
globalThis.pieBridge?.log?.(
|
|
6860
7444
|
"info",
|
|
6861
7445
|
`[find_files] path=${searchPath} pattern=${pattern} matches=${results.length} dirs=${nativeResult.ScannedDirectories ?? 0} files=${nativeResult.ScannedFiles ?? 0}`
|
|
@@ -6864,28 +7448,33 @@ function createFindFilesTool(cwd, options) {
|
|
|
6864
7448
|
try {
|
|
6865
7449
|
const _require = globalThis.require || __require;
|
|
6866
7450
|
const { globSync } = _require("glob");
|
|
6867
|
-
|
|
7451
|
+
const allResults = globSync(pattern, {
|
|
6868
7452
|
cwd: searchPath,
|
|
6869
7453
|
ignore: ["**/node_modules/**", "**/.git/**"],
|
|
6870
7454
|
nodir: false,
|
|
6871
7455
|
absolute: false,
|
|
6872
7456
|
dot: true
|
|
6873
|
-
}).filter((filePath) => !isGitignored(filePath, false))
|
|
7457
|
+
}).filter((filePath) => !isGitignored(filePath, false));
|
|
7458
|
+
resultLimitReached = allResults.length > effectiveLimit;
|
|
7459
|
+
results = allResults.slice(0, effectiveLimit);
|
|
6874
7460
|
} catch {
|
|
6875
|
-
|
|
7461
|
+
const allResults = await globFiles(searchPath, pattern, fs2, effectiveLimit + 1, signal, isGitignored);
|
|
7462
|
+
resultLimitReached = allResults.length > effectiveLimit;
|
|
7463
|
+
results = allResults.slice(0, effectiveLimit);
|
|
6876
7464
|
}
|
|
6877
7465
|
} else {
|
|
6878
|
-
|
|
7466
|
+
const allResults = await globFiles(searchPath, pattern, fs2, effectiveLimit + 1, signal, isGitignored);
|
|
7467
|
+
resultLimitReached = allResults.length > effectiveLimit;
|
|
7468
|
+
results = allResults.slice(0, effectiveLimit);
|
|
6879
7469
|
}
|
|
6880
7470
|
signal?.removeEventListener("abort", onAbort);
|
|
6881
7471
|
if (results.length === 0) {
|
|
6882
|
-
|
|
7472
|
+
resolve3({
|
|
6883
7473
|
content: [{ type: "text", text: "No files found matching pattern" }],
|
|
6884
7474
|
details: { noMatches: true }
|
|
6885
7475
|
});
|
|
6886
7476
|
return;
|
|
6887
7477
|
}
|
|
6888
|
-
const resultLimitReached = results.length >= effectiveLimit;
|
|
6889
7478
|
const rawOutput = results.join("\n");
|
|
6890
7479
|
const truncation = truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER });
|
|
6891
7480
|
let resultOutput = truncation.content;
|
|
@@ -6902,7 +7491,7 @@ function createFindFilesTool(cwd, options) {
|
|
|
6902
7491
|
if (notices.length > 0) {
|
|
6903
7492
|
resultOutput += "\n\n[" + notices.join(". ") + "]";
|
|
6904
7493
|
}
|
|
6905
|
-
|
|
7494
|
+
resolve3({
|
|
6906
7495
|
content: [{ type: "text", text: resultOutput }],
|
|
6907
7496
|
details: Object.keys(details).length > 0 ? details : void 0
|
|
6908
7497
|
});
|
|
@@ -6918,10 +7507,12 @@ function createFindFilesTool(cwd, options) {
|
|
|
6918
7507
|
|
|
6919
7508
|
// ../../packages/shared-headless-capabilities/src/builtin/fs/index.ts
|
|
6920
7509
|
function createSharedFileSystemTools(sandboxRoot, options) {
|
|
7510
|
+
const fileObservationState = options?.fileObservationState ?? createFileObservationState();
|
|
7511
|
+
const sharedOptions = { ...options, fileObservationState };
|
|
6921
7512
|
return [
|
|
6922
|
-
createReadTool(sandboxRoot,
|
|
6923
|
-
createWriteTool(sandboxRoot,
|
|
6924
|
-
createEditTool(sandboxRoot,
|
|
7513
|
+
createReadTool(sandboxRoot, sharedOptions),
|
|
7514
|
+
createWriteTool(sandboxRoot, sharedOptions),
|
|
7515
|
+
createEditTool(sandboxRoot, sharedOptions),
|
|
6925
7516
|
createLsTool(sandboxRoot, options),
|
|
6926
7517
|
createGrepTextTool(sandboxRoot, options),
|
|
6927
7518
|
createFindFilesTool(sandboxRoot, options)
|
|
@@ -7048,23 +7639,23 @@ async function understandFileWithModel(deps, request) {
|
|
|
7048
7639
|
}
|
|
7049
7640
|
|
|
7050
7641
|
// ../../packages/shared-headless-capabilities/src/session-trace.ts
|
|
7051
|
-
function
|
|
7642
|
+
function isRecord2(value) {
|
|
7052
7643
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
7053
7644
|
}
|
|
7054
7645
|
function getTextFromMessageContent(content) {
|
|
7055
7646
|
if (typeof content === "string") return content;
|
|
7056
7647
|
if (!Array.isArray(content)) return "";
|
|
7057
|
-
return content.filter((block) =>
|
|
7648
|
+
return content.filter((block) => isRecord2(block) && block.type === "text").map((block) => String(block.text || "")).join("");
|
|
7058
7649
|
}
|
|
7059
7650
|
function summarizeToolResult(result) {
|
|
7060
|
-
if (!
|
|
7651
|
+
if (!isRecord2(result)) return void 0;
|
|
7061
7652
|
const content = Array.isArray(result.content) ? result.content : [];
|
|
7062
|
-
const text = content.filter((item) =>
|
|
7653
|
+
const text = content.filter((item) => isRecord2(item) && item.type === "text").map((item) => String(item.text || "")).join("\n").trim();
|
|
7063
7654
|
if (!text) return void 0;
|
|
7064
7655
|
return text.length > 500 ? `${text.slice(0, 500)}...` : text;
|
|
7065
7656
|
}
|
|
7066
7657
|
function summarizeToolResultDetails(result) {
|
|
7067
|
-
if (!
|
|
7658
|
+
if (!isRecord2(result) || !isRecord2(result.details)) return void 0;
|
|
7068
7659
|
const details = result.details;
|
|
7069
7660
|
const keys = [
|
|
7070
7661
|
"providerMode",
|
|
@@ -7099,7 +7690,7 @@ function summarizeToolResultDetails(result) {
|
|
|
7099
7690
|
const value = details[key];
|
|
7100
7691
|
if (key === "attempts" && Array.isArray(value)) {
|
|
7101
7692
|
summary[key] = value.slice(-5).map((attempt) => {
|
|
7102
|
-
if (!
|
|
7693
|
+
if (!isRecord2(attempt)) return attempt;
|
|
7103
7694
|
return {
|
|
7104
7695
|
provider: attempt.provider,
|
|
7105
7696
|
modelId: attempt.modelId,
|
|
@@ -7117,7 +7708,7 @@ function summarizeToolResultDetails(result) {
|
|
|
7117
7708
|
summary[key] = {
|
|
7118
7709
|
count: value.length,
|
|
7119
7710
|
items: value.slice(0, 5).map((entry) => {
|
|
7120
|
-
if (!
|
|
7711
|
+
if (!isRecord2(entry)) return entry;
|
|
7121
7712
|
return {
|
|
7122
7713
|
url: entry.finalUrl || entry.url,
|
|
7123
7714
|
title: entry.title,
|
|
@@ -7133,7 +7724,7 @@ function summarizeToolResultDetails(result) {
|
|
|
7133
7724
|
}
|
|
7134
7725
|
if (key === "providerHealth" && Array.isArray(value)) {
|
|
7135
7726
|
summary[key] = value.slice(0, 5).map((entry) => {
|
|
7136
|
-
if (!
|
|
7727
|
+
if (!isRecord2(entry)) return entry;
|
|
7137
7728
|
return {
|
|
7138
7729
|
routeKey: entry.routeKey,
|
|
7139
7730
|
attempts: entry.attempts,
|
|
@@ -7148,7 +7739,7 @@ function summarizeToolResultDetails(result) {
|
|
|
7148
7739
|
}
|
|
7149
7740
|
if (key === "providerAttempts" && Array.isArray(value)) {
|
|
7150
7741
|
summary[key] = value.slice(-5).map((attempt) => {
|
|
7151
|
-
if (!
|
|
7742
|
+
if (!isRecord2(attempt)) return attempt;
|
|
7152
7743
|
return {
|
|
7153
7744
|
provider: attempt.provider,
|
|
7154
7745
|
modelId: attempt.modelId,
|
|
@@ -7180,10 +7771,10 @@ function summarizeString(value, maxLength = 300) {
|
|
|
7180
7771
|
return value.length > maxLength ? `${value.slice(0, maxLength)}...` : value;
|
|
7181
7772
|
}
|
|
7182
7773
|
function summarizeMessage(message) {
|
|
7183
|
-
if (!
|
|
7774
|
+
if (!isRecord2(message)) return typeof message;
|
|
7184
7775
|
if ("contentBlocks" in message && !("content" in message)) return message;
|
|
7185
7776
|
const content = Array.isArray(message.content) ? message.content : [];
|
|
7186
|
-
const blockTypes = content.filter(
|
|
7777
|
+
const blockTypes = content.filter(isRecord2).map((block) => String(block.type || "unknown")).slice(0, 10);
|
|
7187
7778
|
const text = getTextFromMessageContent(content);
|
|
7188
7779
|
return {
|
|
7189
7780
|
role: message.role,
|
|
@@ -7195,12 +7786,12 @@ function summarizeMessage(message) {
|
|
|
7195
7786
|
};
|
|
7196
7787
|
}
|
|
7197
7788
|
function summarizeToolResults(value) {
|
|
7198
|
-
if (
|
|
7789
|
+
if (isRecord2(value) && typeof value.count === "number" && !Array.isArray(value)) return value;
|
|
7199
7790
|
if (!Array.isArray(value)) return typeof value;
|
|
7200
7791
|
let errors = 0;
|
|
7201
7792
|
const toolNames = [];
|
|
7202
7793
|
for (const result of value) {
|
|
7203
|
-
if (!
|
|
7794
|
+
if (!isRecord2(result)) continue;
|
|
7204
7795
|
if (result.isError === true) errors++;
|
|
7205
7796
|
const toolName = typeof result.toolName === "string" ? result.toolName : typeof result.name === "string" ? result.name : void 0;
|
|
7206
7797
|
if (toolName && toolNames.length < 10) toolNames.push(toolName);
|
|
@@ -7212,7 +7803,7 @@ function collectTextLength(content) {
|
|
|
7212
7803
|
if (!Array.isArray(content)) return 0;
|
|
7213
7804
|
let length = 0;
|
|
7214
7805
|
for (const block of content) {
|
|
7215
|
-
if (!
|
|
7806
|
+
if (!isRecord2(block)) continue;
|
|
7216
7807
|
if (typeof block.text === "string") length += block.text.length;
|
|
7217
7808
|
}
|
|
7218
7809
|
return length;
|
|
@@ -7221,7 +7812,7 @@ function collectArgumentLength(content) {
|
|
|
7221
7812
|
if (!Array.isArray(content)) return 0;
|
|
7222
7813
|
let length = 0;
|
|
7223
7814
|
for (const block of content) {
|
|
7224
|
-
if (!
|
|
7815
|
+
if (!isRecord2(block) || !("arguments" in block)) continue;
|
|
7225
7816
|
if (typeof block.arguments === "string") {
|
|
7226
7817
|
length += block.arguments.length;
|
|
7227
7818
|
continue;
|
|
@@ -7244,7 +7835,7 @@ function summarizeMessages(value) {
|
|
|
7244
7835
|
let textLength = 0;
|
|
7245
7836
|
let argumentLength = 0;
|
|
7246
7837
|
for (const message of value) {
|
|
7247
|
-
if (!
|
|
7838
|
+
if (!isRecord2(message)) continue;
|
|
7248
7839
|
const role = typeof message.role === "string" ? message.role : void 0;
|
|
7249
7840
|
if (role && roles.length < 20) roles.push(role);
|
|
7250
7841
|
if (typeof message.stopReason === "string" && stopReasons.length < 20) stopReasons.push(message.stopReason);
|
|
@@ -7255,7 +7846,7 @@ function summarizeMessages(value) {
|
|
|
7255
7846
|
textLength += collectTextLength(message.content);
|
|
7256
7847
|
argumentLength += collectArgumentLength(message.content);
|
|
7257
7848
|
for (const block of content) {
|
|
7258
|
-
if (
|
|
7849
|
+
if (isRecord2(block) && typeof block.name === "string" && toolNames.length < 20) {
|
|
7259
7850
|
toolNames.push(block.name);
|
|
7260
7851
|
}
|
|
7261
7852
|
}
|
|
@@ -7272,7 +7863,7 @@ function summarizeMessages(value) {
|
|
|
7272
7863
|
};
|
|
7273
7864
|
}
|
|
7274
7865
|
function summarizeFailure(value) {
|
|
7275
|
-
if (!
|
|
7866
|
+
if (!isRecord2(value)) return typeof value;
|
|
7276
7867
|
return {
|
|
7277
7868
|
kind: value.kind,
|
|
7278
7869
|
retryable: value.retryable,
|
|
@@ -7283,7 +7874,7 @@ function summarizeFailure(value) {
|
|
|
7283
7874
|
};
|
|
7284
7875
|
}
|
|
7285
7876
|
function summarizeRuntimeEvent(value) {
|
|
7286
|
-
if (!
|
|
7877
|
+
if (!isRecord2(value)) return typeof value;
|
|
7287
7878
|
const summary = {
|
|
7288
7879
|
type: value.type
|
|
7289
7880
|
};
|
|
@@ -7316,7 +7907,7 @@ function recomputeRuntimeSummaryFromTimeline(events) {
|
|
|
7316
7907
|
return summary;
|
|
7317
7908
|
}
|
|
7318
7909
|
function summarizeMeta(meta) {
|
|
7319
|
-
if (!
|
|
7910
|
+
if (!isRecord2(meta)) return meta;
|
|
7320
7911
|
const summary = {};
|
|
7321
7912
|
for (const [key, value] of Object.entries(meta)) {
|
|
7322
7913
|
if (key === "messages" || key === "queuedInputs" || key === "result") {
|
|
@@ -7343,7 +7934,7 @@ function summarizeMeta(meta) {
|
|
|
7343
7934
|
summary[key] = summarizeRuntimeEvent(value);
|
|
7344
7935
|
continue;
|
|
7345
7936
|
}
|
|
7346
|
-
if (key === "queuedInput" &&
|
|
7937
|
+
if (key === "queuedInput" && isRecord2(value)) {
|
|
7347
7938
|
summary[key] = {
|
|
7348
7939
|
mode: value.mode,
|
|
7349
7940
|
source: value.source,
|
|
@@ -7363,18 +7954,18 @@ function toStringArray(value) {
|
|
|
7363
7954
|
}
|
|
7364
7955
|
function summarizeExcludedTools(value) {
|
|
7365
7956
|
if (!Array.isArray(value)) return void 0;
|
|
7366
|
-
const entries = value.filter(
|
|
7957
|
+
const entries = value.filter(isRecord2).map((entry) => ({
|
|
7367
7958
|
name: String(entry.name || ""),
|
|
7368
7959
|
reason: String(entry.reason || "")
|
|
7369
7960
|
})).filter((entry) => entry.name);
|
|
7370
7961
|
return entries.length > 0 ? entries : void 0;
|
|
7371
7962
|
}
|
|
7372
7963
|
function extractSubagentSummaries(toolCallId, toolName, result) {
|
|
7373
|
-
if (toolName !== "spawn_subagents_parallel" || !
|
|
7374
|
-
const details =
|
|
7964
|
+
if (toolName !== "spawn_subagents_parallel" || !isRecord2(result)) return [];
|
|
7965
|
+
const details = isRecord2(result.details) ? result.details : void 0;
|
|
7375
7966
|
const tasks = Array.isArray(details?.tasks) ? details.tasks : [];
|
|
7376
|
-
return tasks.filter(
|
|
7377
|
-
const plan =
|
|
7967
|
+
return tasks.filter(isRecord2).map((task) => {
|
|
7968
|
+
const plan = isRecord2(task.plan) ? task.plan : void 0;
|
|
7378
7969
|
const evidence = Array.isArray(task.evidence) ? task.evidence : [];
|
|
7379
7970
|
const confidenceSignals = Array.isArray(task.confidenceSignals) ? task.confidenceSignals : [];
|
|
7380
7971
|
const runtimeEvidence = Array.isArray(task.runtimeEvidence) ? task.runtimeEvidence : [];
|
|
@@ -7401,7 +7992,7 @@ function extractSubagentSummaries(toolCallId, toolName, result) {
|
|
|
7401
7992
|
}
|
|
7402
7993
|
function timelineEventFromTraceEvent(type, meta) {
|
|
7403
7994
|
const normalizedType = type.startsWith("runtime:") ? type.slice("runtime:".length) : type;
|
|
7404
|
-
const data =
|
|
7995
|
+
const data = isRecord2(meta) ? meta : {};
|
|
7405
7996
|
const detail = summarizeMeta(meta);
|
|
7406
7997
|
if (normalizedType === "assistant_update" || normalizedType === "dispatch_settled" || normalizedType === "wait_for_idle_settled") {
|
|
7407
7998
|
return null;
|
|
@@ -8197,14 +8788,14 @@ function extractAnthropicContent(content, originalQuery) {
|
|
|
8197
8788
|
results.push(makeTextSegment(block.text));
|
|
8198
8789
|
}
|
|
8199
8790
|
if (block?.type === "server_tool_use") {
|
|
8200
|
-
const input =
|
|
8791
|
+
const input = isRecord3(block.input) ? block.input : void 0;
|
|
8201
8792
|
if (typeof input?.query === "string" && input.query.trim()) {
|
|
8202
8793
|
actualQueries.push(input.query.trim());
|
|
8203
8794
|
}
|
|
8204
8795
|
}
|
|
8205
8796
|
if (block?.type === "web_search_tool_result") {
|
|
8206
8797
|
if (!Array.isArray(block.content)) {
|
|
8207
|
-
const errorCode =
|
|
8798
|
+
const errorCode = isRecord3(block.content) && typeof block.content.error_code === "string" ? block.content.error_code : "unknown";
|
|
8208
8799
|
results.push({
|
|
8209
8800
|
type: "error",
|
|
8210
8801
|
toolUseId: typeof block.tool_use_id === "string" ? block.tool_use_id : void 0,
|
|
@@ -8248,7 +8839,7 @@ function collectUrlStrings(text, sources) {
|
|
|
8248
8839
|
function resolveResponsesEndpoint(baseUrl) {
|
|
8249
8840
|
return `${baseUrl.replace(/\/+$/, "")}/responses`;
|
|
8250
8841
|
}
|
|
8251
|
-
function
|
|
8842
|
+
function isRecord3(value) {
|
|
8252
8843
|
return typeof value === "object" && value !== null;
|
|
8253
8844
|
}
|
|
8254
8845
|
function collectUrlCitations(value, sources) {
|
|
@@ -8256,7 +8847,7 @@ function collectUrlCitations(value, sources) {
|
|
|
8256
8847
|
for (const item of value) collectUrlCitations(item, sources);
|
|
8257
8848
|
return;
|
|
8258
8849
|
}
|
|
8259
|
-
if (!
|
|
8850
|
+
if (!isRecord3(value)) return;
|
|
8260
8851
|
if (value.type === "url_citation" && typeof value.url === "string") {
|
|
8261
8852
|
sources.push(sourceFromUrl(value.url, typeof value.title === "string" ? value.title : void 0));
|
|
8262
8853
|
}
|
|
@@ -8275,7 +8866,7 @@ function collectOutputText(value, chunks) {
|
|
|
8275
8866
|
for (const item of value) collectOutputText(item, chunks);
|
|
8276
8867
|
return;
|
|
8277
8868
|
}
|
|
8278
|
-
if (!
|
|
8869
|
+
if (!isRecord3(value)) return;
|
|
8279
8870
|
if (value.type === "output_text" && typeof value.text === "string") {
|
|
8280
8871
|
chunks.push(value.text);
|
|
8281
8872
|
return;
|
|
@@ -8289,7 +8880,7 @@ function collectOutputText(value, chunks) {
|
|
|
8289
8880
|
}
|
|
8290
8881
|
}
|
|
8291
8882
|
function extractOpenAIResponsesContent(response, originalQuery) {
|
|
8292
|
-
const body =
|
|
8883
|
+
const body = isRecord3(response) ? response : {};
|
|
8293
8884
|
const sources = [];
|
|
8294
8885
|
collectUrlCitations(body.output, sources);
|
|
8295
8886
|
if (typeof body.output_text === "string" && body.output_text.trim()) {
|
|
@@ -8306,12 +8897,12 @@ function extractOpenAIResponsesContent(response, originalQuery) {
|
|
|
8306
8897
|
return { text, sources: uniqueSources(sources), results: text ? [makeTextSegment(text)] : [], actualQueries: [originalQuery] };
|
|
8307
8898
|
}
|
|
8308
8899
|
function extractChatCompletionsContent(response, originalQuery) {
|
|
8309
|
-
const body =
|
|
8900
|
+
const body = isRecord3(response) ? response : {};
|
|
8310
8901
|
const sources = [];
|
|
8311
8902
|
collectUrlCitations(body, sources);
|
|
8312
8903
|
const choices = Array.isArray(body.choices) ? body.choices : [];
|
|
8313
|
-
const firstChoice =
|
|
8314
|
-
const message =
|
|
8904
|
+
const firstChoice = isRecord3(choices[0]) ? choices[0] : {};
|
|
8905
|
+
const message = isRecord3(firstChoice.message) ? firstChoice.message : {};
|
|
8315
8906
|
const content = typeof message.content === "string" ? message.content : "";
|
|
8316
8907
|
if (content) {
|
|
8317
8908
|
collectUrlStrings(content, sources);
|
|
@@ -8526,8 +9117,8 @@ async function runQueuedSearch(model, fn) {
|
|
|
8526
9117
|
const key = `${model.provider}/${model.id}`;
|
|
8527
9118
|
const previous = SEARCH_PROVIDER_QUEUE.get(key) ?? Promise.resolve();
|
|
8528
9119
|
let release;
|
|
8529
|
-
const next = new Promise((
|
|
8530
|
-
release =
|
|
9120
|
+
const next = new Promise((resolve3) => {
|
|
9121
|
+
release = resolve3;
|
|
8531
9122
|
});
|
|
8532
9123
|
const queued = previous.catch(() => void 0).then(() => next);
|
|
8533
9124
|
SEARCH_PROVIDER_QUEUE.set(key, queued);
|
|
@@ -8898,10 +9489,10 @@ function assistantText(message) {
|
|
|
8898
9489
|
async function withAbort(promise, signal) {
|
|
8899
9490
|
if (!signal) return promise;
|
|
8900
9491
|
if (signal.aborted) throw signal.reason ?? new Error("operation aborted");
|
|
8901
|
-
return await new Promise((
|
|
9492
|
+
return await new Promise((resolve3, reject) => {
|
|
8902
9493
|
const onAbort = () => reject(signal.reason ?? new Error("operation aborted"));
|
|
8903
9494
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
8904
|
-
promise.then(
|
|
9495
|
+
promise.then(resolve3, reject).finally(() => signal.removeEventListener("abort", onAbort));
|
|
8905
9496
|
});
|
|
8906
9497
|
}
|
|
8907
9498
|
async function applyPromptWithModel(deps, prompt, url, content, signal) {
|
|
@@ -9123,7 +9714,7 @@ var webResearchSchema = Type.Object({
|
|
|
9123
9714
|
var DEFAULT_WEB_RESEARCH_SEARCH_ATTEMPT_TIMEOUT_MS = 45e3;
|
|
9124
9715
|
var DEFAULT_WEB_RESEARCH_FETCH_ATTEMPT_TIMEOUT_MS = 3e4;
|
|
9125
9716
|
var DEFAULT_WEB_RESEARCH_BROWSER_ATTEMPT_TIMEOUT_MS = 45e3;
|
|
9126
|
-
function
|
|
9717
|
+
function isRecord4(value) {
|
|
9127
9718
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
9128
9719
|
}
|
|
9129
9720
|
function textFromResult(result) {
|
|
@@ -9337,7 +9928,7 @@ function updateProviderHealth(current, attempts) {
|
|
|
9337
9928
|
return [...byKey.values()].sort((a, b) => b.updatedAt - a.updatedAt).slice(0, 20);
|
|
9338
9929
|
}
|
|
9339
9930
|
function contentDetails(result) {
|
|
9340
|
-
return
|
|
9931
|
+
return isRecord4(result.details) ? result.details : {};
|
|
9341
9932
|
}
|
|
9342
9933
|
function excerpt(text, max = 1400) {
|
|
9343
9934
|
const clean = text.replace(/\s+/g, " ").trim();
|
|
@@ -9402,13 +9993,13 @@ async function runWithDeadline(operation, timeoutMs, message, parentSignal) {
|
|
|
9402
9993
|
const child = createChildSignal(parentSignal);
|
|
9403
9994
|
let timer;
|
|
9404
9995
|
try {
|
|
9405
|
-
return await new Promise((
|
|
9996
|
+
return await new Promise((resolve3, reject) => {
|
|
9406
9997
|
timer = setTimeout(() => {
|
|
9407
9998
|
const error = new Error(message);
|
|
9408
9999
|
child.abort(error);
|
|
9409
10000
|
reject(error);
|
|
9410
10001
|
}, timeoutMs);
|
|
9411
|
-
operation(child.signal).then(
|
|
10002
|
+
operation(child.signal).then(resolve3, reject);
|
|
9412
10003
|
});
|
|
9413
10004
|
} finally {
|
|
9414
10005
|
if (timer) clearTimeout(timer);
|
|
@@ -9963,6 +10554,8 @@ export {
|
|
|
9963
10554
|
getAutoCompactTokenLimit,
|
|
9964
10555
|
findFirstKeptEntryId,
|
|
9965
10556
|
createCompactionSummary,
|
|
10557
|
+
generateEntryId,
|
|
10558
|
+
FileSessionStore,
|
|
9966
10559
|
createSessionManager,
|
|
9967
10560
|
AgentSessionController,
|
|
9968
10561
|
registerInteractionHandler,
|
|
@@ -9972,6 +10565,7 @@ export {
|
|
|
9972
10565
|
buildProjectContextSection,
|
|
9973
10566
|
createAskUserCapability,
|
|
9974
10567
|
createPolicyEnforcedTools,
|
|
10568
|
+
createFileObservationState,
|
|
9975
10569
|
createSharedFileSystemTools,
|
|
9976
10570
|
buildToolsPromptSection,
|
|
9977
10571
|
createSharedWebSearchTool,
|