@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.
@@ -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-VE2HDCNB.js";
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-5DA2D3K2.js";
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((resolve2) => setTimeout(resolve2, ms));
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
- return deleteDurableFile(this.gateway, filePath);
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 (file.endsWith("-files.json") || file.endsWith("-files.json.bak") || file.endsWith("-files.json.prev")) {
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
- const sessionIds = await this.list();
2405
- const metadata = [];
2406
- for (const id of sessionIds) {
2407
- const meta = await this.getMetadata(id);
2408
- if (meta) {
2409
- metadata.push(meta);
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 metadata.sort((a, b) => b.updatedAt - a.updatedAt);
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
- console.warn(`[SessionManager] Session not found: ${sessionId}`);
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((resolve2) => {
3042
- this.resolvePendingWait = resolve2;
3331
+ this.pendingWait = new Promise((resolve3) => {
3332
+ this.resolvePendingWait = resolve3;
3043
3333
  });
3044
3334
  }
3045
3335
  resolvePending() {
3046
- const resolve2 = this.resolvePendingWait;
3336
+ const resolve3 = this.resolvePendingWait;
3047
3337
  this.pendingWait = void 0;
3048
3338
  this.resolvePendingWait = void 0;
3049
- resolve2?.();
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((resolve2) => {
3417
+ await new Promise((resolve3) => {
3128
3418
  this.autoContinueTimer = setTimer(() => {
3129
3419
  this.autoContinueTimer = void 0;
3130
3420
  if (this.options.getDisposed()) {
3131
- resolve2();
3421
+ resolve3();
3132
3422
  return;
3133
3423
  }
3134
3424
  void this.options.continueTurn().catch(() => {
3135
- }).finally(resolve2);
3425
+ }).finally(resolve3);
3136
3426
  }, 0);
3137
3427
  });
3138
3428
  }
3139
3429
  ensurePending() {
3140
3430
  if (this.pendingAutoContinue) return;
3141
- this.pendingAutoContinue = new Promise((resolve2) => {
3142
- this.resolvePendingAutoContinue = resolve2;
3431
+ this.pendingAutoContinue = new Promise((resolve3) => {
3432
+ this.resolvePendingAutoContinue = resolve3;
3143
3433
  });
3144
3434
  }
3145
3435
  resolvePending() {
3146
- const resolve2 = this.resolvePendingAutoContinue;
3436
+ const resolve3 = this.resolvePendingAutoContinue;
3147
3437
  this.pendingAutoContinue = void 0;
3148
3438
  this.resolvePendingAutoContinue = void 0;
3149
- resolve2?.();
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((resolve2) => {
3559
- this.resolvePendingDispatch = resolve2;
3848
+ this.pendingDispatch = new Promise((resolve3) => {
3849
+ this.resolvePendingDispatch = resolve3;
3560
3850
  });
3561
3851
  }
3562
3852
  resolvePending() {
3563
- const resolve2 = this.resolvePendingDispatch;
3853
+ const resolve3 = this.resolvePendingDispatch;
3564
3854
  this.pendingDispatch = void 0;
3565
3855
  this.resolvePendingDispatch = void 0;
3566
- resolve2?.();
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((resolve2) => {
3721
- this.resolvePendingRetry = resolve2;
4010
+ this.pendingRetry = new Promise((resolve3) => {
4011
+ this.resolvePendingRetry = resolve3;
3722
4012
  });
3723
4013
  }
3724
4014
  resolvePending() {
3725
- const resolve2 = this.resolvePendingRetry;
4015
+ const resolve3 = this.resolvePendingRetry;
3726
4016
  this.pendingRetry = void 0;
3727
4017
  this.resolvePendingRetry = void 0;
3728
- resolve2?.();
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
- (resolve2, reject) => {
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
- resolve2(await options.understandFile({
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
- resolve2({
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
- resolve2(await options.understandFile({
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
- resolve2({
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
- if (startLine >= allLines.length) {
5549
- throw new Error(`Offset ${offset} is beyond end of file (${allLines.length} lines total)`);
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
- const endLine = Math.min(startLine + limit, allLines.length);
5555
- selectedContent = allLines.slice(startLine, endLine).join("\n");
5556
- userLimitedLines = endLine - startLine;
5557
- } else {
5558
- selectedContent = allLines.slice(startLine).join("\n");
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(Buffer.byteLength(allLines[startLine], "utf-8"));
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 < allLines.length) {
5586
- const remaining = allLines.length - (startLine + userLimitedLines);
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
- resolve2({ content: textContent, details });
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 content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories. ${pathResolutionDescription}`,
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
- (resolve2, reject) => {
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
- resolve2({
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 countOccurrences(content, needle) {
5843
- if (needle.length === 0) return 0;
5844
- let count = 0;
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
- count++;
6312
+ matches.push({ index: nextIndex, matchLength: needle.length });
5850
6313
  index = nextIndex + needle.length;
5851
6314
  }
5852
- return count;
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: countOccurrences(content, oldText),
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 endFuzzyIndex = fuzzyIndex + fuzzyOldText.length - 1;
5894
- const originalStart = fuzzyMap.originalStartByNormalizedIndex[fuzzyIndex];
5895
- const originalEnd = fuzzyMap.originalEndByNormalizedIndex[endFuzzyIndex];
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: originalStart,
5899
- matchLength: originalEnd - originalStart,
6370
+ index: firstMatch.index,
6371
+ matchLength: firstMatch.matchLength,
5900
6372
  usedFuzzyMatch: true,
5901
6373
  contentForReplacement: content,
5902
- occurrences: countOccurrences(fuzzyContent, fuzzyOldText),
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 countOccurrences2(content, needle) {
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((resolve2, reject) => {
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 = countOccurrences2(normalizedContent, normalizedNewText);
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
- resolve2(
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 a larger exact block copied from the latest file contents."
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
- resolve2(
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
- recoveryHint: "Read the current file and retry with a larger exact block that uniquely identifies the intended location."
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 = normalizedContent;
6086
- const newContent = baseContent.substring(0, matchResult.index) + normalizedNewText + baseContent.substring(matchResult.index + matchResult.matchLength);
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
- resolve2(
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
- resolve2({
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((resolve2, reject) => {
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
- resolve2({ content: [{ type: "text", text: "(empty directory)" }], details: void 0 });
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
- resolve2({
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((resolve2) => {
6406
- store.waiters.set(requestId, resolve2);
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} Returns matching lines with file paths and line numbers. Output is truncated to ${DEFAULT_LIMIT2} matches or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Long lines are truncated to ${GREP_MAX_LINE_LENGTH} chars.`,
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((resolve2, reject) => {
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
- const start = ctx > 0 ? Math.max(0, lineIdx - ctx) : lineIdx;
6615
- const end = ctx > 0 ? Math.min(lines.length - 1, lineIdx + ctx) : lineIdx;
6616
- for (let c = start; c <= end; c++) {
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
- resolve2({
7199
+ resolve3({
6634
7200
  content: [{ type: "text", text: "No matches found" }],
6635
- details: { noMatches: true }
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 limit=${effectiveLimit * 2} for more, or refine pattern`
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
- resolve2({
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((resolve2) => {
6749
- store.waiters.set(requestId, resolve2);
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((resolve2, reject) => {
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
- results = globSync(pattern, {
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)).slice(0, effectiveLimit);
7457
+ }).filter((filePath) => !isGitignored(filePath, false));
7458
+ resultLimitReached = allResults.length > effectiveLimit;
7459
+ results = allResults.slice(0, effectiveLimit);
6874
7460
  } catch {
6875
- results = await globFiles(searchPath, pattern, fs2, effectiveLimit, signal, isGitignored);
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
- results = await globFiles(searchPath, pattern, fs2, effectiveLimit, signal, isGitignored);
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
- resolve2({
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
- resolve2({
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, options),
6923
- createWriteTool(sandboxRoot, options),
6924
- createEditTool(sandboxRoot, options),
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 isRecord(value) {
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) => isRecord(block) && block.type === "text").map((block) => String(block.text || "")).join("");
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 (!isRecord(result)) return void 0;
7651
+ if (!isRecord2(result)) return void 0;
7061
7652
  const content = Array.isArray(result.content) ? result.content : [];
7062
- const text = content.filter((item) => isRecord(item) && item.type === "text").map((item) => String(item.text || "")).join("\n").trim();
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 (!isRecord(result) || !isRecord(result.details)) return void 0;
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 (!isRecord(attempt)) return attempt;
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 (!isRecord(entry)) return entry;
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 (!isRecord(entry)) return entry;
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 (!isRecord(attempt)) return attempt;
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 (!isRecord(message)) return typeof message;
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(isRecord).map((block) => String(block.type || "unknown")).slice(0, 10);
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 (isRecord(value) && typeof value.count === "number" && !Array.isArray(value)) return value;
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 (!isRecord(result)) continue;
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 (!isRecord(block)) continue;
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 (!isRecord(block) || !("arguments" in block)) continue;
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 (!isRecord(message)) continue;
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 (isRecord(block) && typeof block.name === "string" && toolNames.length < 20) {
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 (!isRecord(value)) return typeof value;
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 (!isRecord(value)) return typeof value;
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 (!isRecord(meta)) return meta;
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" && isRecord(value)) {
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(isRecord).map((entry) => ({
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" || !isRecord(result)) return [];
7374
- const details = isRecord(result.details) ? result.details : void 0;
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(isRecord).map((task) => {
7377
- const plan = isRecord(task.plan) ? task.plan : void 0;
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 = isRecord(meta) ? meta : {};
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 = isRecord2(block.input) ? block.input : void 0;
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 = isRecord2(block.content) && typeof block.content.error_code === "string" ? block.content.error_code : "unknown";
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 isRecord2(value) {
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 (!isRecord2(value)) return;
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 (!isRecord2(value)) return;
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 = isRecord2(response) ? response : {};
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 = isRecord2(response) ? response : {};
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 = isRecord2(choices[0]) ? choices[0] : {};
8314
- const message = isRecord2(firstChoice.message) ? firstChoice.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((resolve2) => {
8530
- release = resolve2;
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((resolve2, reject) => {
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(resolve2, reject).finally(() => signal.removeEventListener("abort", onAbort));
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 isRecord3(value) {
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 isRecord3(result.details) ? result.details : {};
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((resolve2, reject) => {
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(resolve2, reject);
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,