zeitlich 0.2.45 → 0.2.47

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.
Files changed (109) hide show
  1. package/README.md +137 -11
  2. package/dist/{activities-Coafq5zr.d.cts → activities-CPwKoUlD.d.cts} +22 -2
  3. package/dist/{activities-CrN-ghLo.d.ts → activities-DlaBxNID.d.ts} +22 -2
  4. package/dist/adapters/thread/anthropic/index.cjs +276 -71
  5. package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
  6. package/dist/adapters/thread/anthropic/index.d.cts +62 -8
  7. package/dist/adapters/thread/anthropic/index.d.ts +62 -8
  8. package/dist/adapters/thread/anthropic/index.js +275 -72
  9. package/dist/adapters/thread/anthropic/index.js.map +1 -1
  10. package/dist/adapters/thread/anthropic/workflow.cjs +38 -20
  11. package/dist/adapters/thread/anthropic/workflow.cjs.map +1 -1
  12. package/dist/adapters/thread/anthropic/workflow.d.cts +5 -4
  13. package/dist/adapters/thread/anthropic/workflow.d.ts +5 -4
  14. package/dist/adapters/thread/anthropic/workflow.js +38 -20
  15. package/dist/adapters/thread/anthropic/workflow.js.map +1 -1
  16. package/dist/adapters/thread/google-genai/index.cjs +171 -69
  17. package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
  18. package/dist/adapters/thread/google-genai/index.d.cts +6 -4
  19. package/dist/adapters/thread/google-genai/index.d.ts +6 -4
  20. package/dist/adapters/thread/google-genai/index.js +171 -69
  21. package/dist/adapters/thread/google-genai/index.js.map +1 -1
  22. package/dist/adapters/thread/google-genai/workflow.cjs +38 -20
  23. package/dist/adapters/thread/google-genai/workflow.cjs.map +1 -1
  24. package/dist/adapters/thread/google-genai/workflow.d.cts +7 -4
  25. package/dist/adapters/thread/google-genai/workflow.d.ts +7 -4
  26. package/dist/adapters/thread/google-genai/workflow.js +38 -20
  27. package/dist/adapters/thread/google-genai/workflow.js.map +1 -1
  28. package/dist/adapters/thread/langchain/index.cjs +170 -66
  29. package/dist/adapters/thread/langchain/index.cjs.map +1 -1
  30. package/dist/adapters/thread/langchain/index.d.cts +19 -4
  31. package/dist/adapters/thread/langchain/index.d.ts +19 -4
  32. package/dist/adapters/thread/langchain/index.js +170 -66
  33. package/dist/adapters/thread/langchain/index.js.map +1 -1
  34. package/dist/adapters/thread/langchain/workflow.cjs +38 -20
  35. package/dist/adapters/thread/langchain/workflow.cjs.map +1 -1
  36. package/dist/adapters/thread/langchain/workflow.d.cts +5 -4
  37. package/dist/adapters/thread/langchain/workflow.d.ts +5 -4
  38. package/dist/adapters/thread/langchain/workflow.js +38 -20
  39. package/dist/adapters/thread/langchain/workflow.js.map +1 -1
  40. package/dist/cold-store-BDgJpwLI.d.ts +114 -0
  41. package/dist/cold-store-Z2wvK2cV.d.cts +114 -0
  42. package/dist/index.cjs +440 -67
  43. package/dist/index.cjs.map +1 -1
  44. package/dist/index.d.cts +150 -8
  45. package/dist/index.d.ts +150 -8
  46. package/dist/index.js +432 -68
  47. package/dist/index.js.map +1 -1
  48. package/dist/proxy-CDh3Rsa7.d.cts +40 -0
  49. package/dist/proxy-Du8ggERu.d.ts +40 -0
  50. package/dist/{thread-manager-wRVVBFgj.d.cts → thread-manager-BjoYYXgd.d.cts} +8 -2
  51. package/dist/{thread-manager-BsLO3Fgc.d.cts → thread-manager-D8zKNFZ9.d.cts} +8 -2
  52. package/dist/{thread-manager-Bi1XlbpJ.d.ts → thread-manager-DtHYws2F.d.ts} +8 -2
  53. package/dist/{thread-manager-BhkOyQ1I.d.ts → thread-manager-Dw96FKH1.d.ts} +8 -2
  54. package/dist/{types-C66-BVBr.d.cts → types-BMJrsHo0.d.cts} +17 -1
  55. package/dist/{types-BkX4HLzi.d.ts → types-CtdOquo3.d.ts} +17 -1
  56. package/dist/{types-CdALEF3z.d.cts → types-DNEl5uxQ.d.cts} +38 -0
  57. package/dist/{types-ChAy_jSP.d.ts → types-qQVZfhoT.d.ts} +38 -0
  58. package/dist/{workflow-DMmiaw6w.d.cts → workflow-BH9ImDGq.d.cts} +48 -2
  59. package/dist/{workflow-BwT5EybR.d.ts → workflow-Cdw3-RNB.d.ts} +48 -2
  60. package/dist/workflow.cjs +47 -4
  61. package/dist/workflow.cjs.map +1 -1
  62. package/dist/workflow.d.cts +2 -2
  63. package/dist/workflow.d.ts +2 -2
  64. package/dist/workflow.js +47 -5
  65. package/dist/workflow.js.map +1 -1
  66. package/package.json +14 -3
  67. package/src/adapters/thread/anthropic/activities.ts +82 -39
  68. package/src/adapters/thread/anthropic/index.ts +8 -0
  69. package/src/adapters/thread/anthropic/model-invoker.test.ts +110 -0
  70. package/src/adapters/thread/anthropic/model-invoker.ts +26 -5
  71. package/src/adapters/thread/anthropic/prompt-cache.test.ts +134 -0
  72. package/src/adapters/thread/anthropic/prompt-cache.ts +163 -0
  73. package/src/adapters/thread/anthropic/proxy.ts +1 -0
  74. package/src/adapters/thread/anthropic/thread-manager.ts +9 -1
  75. package/src/adapters/thread/google-genai/activities.ts +64 -40
  76. package/src/adapters/thread/google-genai/proxy.ts +1 -0
  77. package/src/adapters/thread/google-genai/thread-manager.ts +9 -1
  78. package/src/adapters/thread/langchain/activities.ts +63 -36
  79. package/src/adapters/thread/langchain/proxy.ts +1 -0
  80. package/src/adapters/thread/langchain/thread-manager.ts +9 -1
  81. package/src/index.ts +21 -2
  82. package/src/lib/session/session-edge-cases.integration.test.ts +12 -0
  83. package/src/lib/session/session.integration.test.ts +138 -0
  84. package/src/lib/session/session.ts +29 -0
  85. package/src/lib/session/types.ts +22 -0
  86. package/src/lib/subagent/define.ts +1 -0
  87. package/src/lib/subagent/handler.ts +11 -2
  88. package/src/lib/subagent/subagent.integration.test.ts +139 -0
  89. package/src/lib/subagent/types.ts +16 -0
  90. package/src/lib/thread/cold-store.test.ts +221 -0
  91. package/src/lib/thread/cold-store.ts +269 -0
  92. package/src/lib/thread/index.ts +32 -0
  93. package/src/lib/thread/keys.ts +20 -0
  94. package/src/lib/thread/manager.ts +16 -27
  95. package/src/lib/thread/proxy.ts +79 -27
  96. package/src/lib/thread/snapshot.test.ts +443 -0
  97. package/src/lib/thread/snapshot.ts +163 -0
  98. package/src/lib/thread/test-utils.ts +228 -0
  99. package/src/lib/thread/tiered.test.ts +281 -0
  100. package/src/lib/thread/tiered.ts +135 -0
  101. package/src/lib/thread/types.ts +16 -0
  102. package/src/tools/edit/handler.test.ts +177 -0
  103. package/src/tools/edit/handler.ts +249 -47
  104. package/src/tools/edit/tool.ts +40 -0
  105. package/src/tools/task-create/handler.ts +1 -1
  106. package/src/tools/task-update/handler.ts +1 -1
  107. package/src/workflow.ts +2 -2
  108. package/dist/proxy-Bf7uI-Hw.d.cts +0 -24
  109. package/dist/proxy-COqA95FW.d.ts +0 -24
package/dist/index.js CHANGED
@@ -3,6 +3,10 @@ import z14, { z } from 'zod';
3
3
  import { randomUUID, randomFillSync } from 'crypto';
4
4
  import { ApplicationFailure as ApplicationFailure$1 } from '@temporalio/common';
5
5
  import { join, resolve, posix } from 'path';
6
+ import { gzip, gunzip } from 'zlib';
7
+ import { promisify } from 'util';
8
+ import { DeleteObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';
9
+ import { Upload } from '@aws-sdk/lib-storage';
6
10
  import { Context } from '@temporalio/activity';
7
11
  import { promises } from 'fs';
8
12
 
@@ -564,7 +568,8 @@ function createSubagentHandler(subagents) {
564
568
  }
565
569
  const threadMode = config.thread ?? "new";
566
570
  const allowsContinuation = threadMode !== "new";
567
- const continuationThreadId = args.threadId && allowsContinuation ? args.threadId : void 0;
571
+ const newThreadSource = config.newThreadSource ?? "new";
572
+ const continuationThreadId = !allowsContinuation ? void 0 : args.threadId ?? (newThreadSource === "from-parent" ? context.threadId : void 0);
568
573
  let thread;
569
574
  if (continuationThreadId) {
570
575
  thread = {
@@ -1032,7 +1037,9 @@ async function createSession(config) {
1032
1037
  appendAgentMessage,
1033
1038
  forkThread,
1034
1039
  loadThreadState,
1035
- saveThreadState
1040
+ saveThreadState,
1041
+ hydrateThread,
1042
+ flushThread
1036
1043
  } = threadOps;
1037
1044
  const plugins = [];
1038
1045
  let destroySubagentSandboxes;
@@ -1176,10 +1183,12 @@ async function createSession(config) {
1176
1183
  });
1177
1184
  };
1178
1185
  if (threadMode === "fork" && sourceThreadId) {
1186
+ await hydrateThread(sourceThreadId, threadKey);
1179
1187
  await forkThread(sourceThreadId, threadId, threadKey);
1180
1188
  const forkedSlice = await loadThreadState(threadId, threadKey);
1181
1189
  if (forkedSlice) rehydrateFromSlice(forkedSlice);
1182
1190
  } else if (threadMode === "continue") {
1191
+ await hydrateThread(threadId, threadKey);
1183
1192
  const continuedSlice = await loadThreadState(threadId, threadKey);
1184
1193
  if (continuedSlice) rehydrateFromSlice(continuedSlice);
1185
1194
  } else {
@@ -1361,6 +1370,15 @@ async function createSession(config) {
1361
1370
  error: persistError instanceof Error ? persistError.message : String(persistError)
1362
1371
  });
1363
1372
  }
1373
+ try {
1374
+ await flushThread(threadId, threadKey);
1375
+ } catch (flushError) {
1376
+ log.warn("failed to flush thread to cold tier", {
1377
+ agentName,
1378
+ threadId,
1379
+ error: flushError instanceof Error ? flushError.message : String(flushError)
1380
+ });
1381
+ }
1364
1382
  await callSessionEnd(exitReason, stateManager.getTurns());
1365
1383
  if (sandboxOwned && sandboxId && sandboxOps) {
1366
1384
  switch (resolvedShutdown) {
@@ -1444,6 +1462,9 @@ function getThreadMetaKey(threadKey, threadId) {
1444
1462
  function getThreadStateKey(threadKey, threadId) {
1445
1463
  return `${threadKey}:state:thread:${threadId}`;
1446
1464
  }
1465
+ function getThreadDedupKey(threadId, dedupId) {
1466
+ return `dedup:${dedupId}:thread:${threadId}`;
1467
+ }
1447
1468
 
1448
1469
  // src/lib/types.ts
1449
1470
  function isTerminalStatus(status) {
@@ -2196,6 +2217,13 @@ IMPORTANT:
2196
2217
  }),
2197
2218
  strict: true
2198
2219
  };
2220
+ var textEditSchema = z.object({
2221
+ old_string: z.string().describe("The exact text to replace"),
2222
+ new_string: z.string().describe("The text to replace it with"),
2223
+ replace_all: z.boolean().optional().describe(
2224
+ "If true, replace all occurrences of old_string for this edit (default: false)"
2225
+ )
2226
+ });
2199
2227
  var editTool = {
2200
2228
  name: "FileEdit",
2201
2229
  description: `Edit specific sections of a file by replacing text.
@@ -2224,6 +2252,27 @@ IMPORTANT:
2224
2252
  }),
2225
2253
  strict: true
2226
2254
  };
2255
+ var multiEditTool = {
2256
+ name: "FileMultiEdit",
2257
+ description: `Apply multiple exact text replacements to one file in order.
2258
+
2259
+ Usage:
2260
+ - Use this when a task needs several related edits in the same file
2261
+ - Each edit is applied to the file content produced by the prior edit
2262
+ - The operation is atomic: if any edit fails, the file is left unchanged
2263
+
2264
+ IMPORTANT:
2265
+ - You must read the file first (in this session) before editing it
2266
+ - Each old_string must match exactly (whitespace-sensitive)
2267
+ - Each old_string must be unique unless that edit uses replace_all: true
2268
+ - old_string and new_string must be different for every edit
2269
+ `,
2270
+ schema: z.object({
2271
+ file_path: z.string().describe("The absolute virtual path to the file to modify"),
2272
+ edits: z.array(textEditSchema).min(1).describe("Exact replacements to apply sequentially to the file")
2273
+ }),
2274
+ strict: true
2275
+ };
2227
2276
  var taskCreateTool = {
2228
2277
  name: "TaskCreate",
2229
2278
  description: `Use this tool to create a structured task list. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user.
@@ -2294,7 +2343,7 @@ function createTaskCreateHandler(stateManager) {
2294
2343
  };
2295
2344
  stateManager.setTask(task);
2296
2345
  return {
2297
- toolResponse: JSON.stringify(task, null, 2),
2346
+ toolResponse: `Task ${task.id} created`,
2298
2347
  data: task
2299
2348
  };
2300
2349
  };
@@ -2393,7 +2442,7 @@ function createTaskUpdateHandler(stateManager) {
2393
2442
  }
2394
2443
  stateManager.setTask(task);
2395
2444
  return {
2396
- toolResponse: JSON.stringify(task, null, 2),
2445
+ toolResponse: `Task ${task.id} updated`,
2397
2446
  data: task
2398
2447
  };
2399
2448
  };
@@ -2588,9 +2637,6 @@ redis.call('EXPIRE', KEYS[2], tonumber(ARGV[1]))
2588
2637
  redis.call('SET', KEYS[1], '1', 'EX', tonumber(ARGV[1]))
2589
2638
  return 1
2590
2639
  `;
2591
- function getDedupKey(threadId, id) {
2592
- return `dedup:${id}:thread:${threadId}`;
2593
- }
2594
2640
  function createThreadManager(config) {
2595
2641
  const {
2596
2642
  redis,
@@ -2598,11 +2644,13 @@ function createThreadManager(config) {
2598
2644
  key = "messages",
2599
2645
  serialize = (m) => JSON.stringify(m),
2600
2646
  deserialize = (raw) => JSON.parse(raw),
2601
- idOf
2647
+ idOf,
2648
+ ttlSeconds = THREAD_TTL_SECONDS
2602
2649
  } = config;
2603
2650
  const redisKey = getThreadListKey(key, threadId);
2604
2651
  const metaKey = getThreadMetaKey(key, threadId);
2605
2652
  const stateKey = getThreadStateKey(key, threadId);
2653
+ const dedupKey = (id) => getThreadDedupKey(threadId, id);
2606
2654
  async function assertThreadExists() {
2607
2655
  const exists = await redis.exists(metaKey);
2608
2656
  if (!exists) {
@@ -2612,7 +2660,7 @@ function createThreadManager(config) {
2612
2660
  return {
2613
2661
  async initialize() {
2614
2662
  await redis.del(redisKey);
2615
- await redis.set(metaKey, "1", "EX", THREAD_TTL_SECONDS);
2663
+ await redis.set(metaKey, "1", "EX", ttlSeconds);
2616
2664
  },
2617
2665
  async load() {
2618
2666
  await assertThreadExists();
@@ -2624,18 +2672,17 @@ function createThreadManager(config) {
2624
2672
  await assertThreadExists();
2625
2673
  if (idOf) {
2626
2674
  const dedupId = messages.map(idOf).join(":");
2627
- const dedupKey = getDedupKey(threadId, dedupId);
2628
2675
  await redis.eval(
2629
2676
  APPEND_IDEMPOTENT_SCRIPT,
2630
2677
  2,
2631
- dedupKey,
2678
+ dedupKey(dedupId),
2632
2679
  redisKey,
2633
- String(THREAD_TTL_SECONDS),
2680
+ String(ttlSeconds),
2634
2681
  ...messages.map(serialize)
2635
2682
  );
2636
2683
  } else {
2637
2684
  await redis.rpush(redisKey, ...messages.map(serialize));
2638
- await redis.expire(redisKey, THREAD_TTL_SECONDS);
2685
+ await redis.expire(redisKey, ttlSeconds);
2639
2686
  }
2640
2687
  },
2641
2688
  async fork(newThreadId) {
@@ -2650,11 +2697,11 @@ function createThreadManager(config) {
2650
2697
  if (data.length > 0) {
2651
2698
  const newKey = getThreadListKey(key, newThreadId);
2652
2699
  await redis.rpush(newKey, ...data);
2653
- await redis.expire(newKey, THREAD_TTL_SECONDS);
2700
+ await redis.expire(newKey, ttlSeconds);
2654
2701
  }
2655
2702
  if (stateRaw != null) {
2656
2703
  const newStateKey = getThreadStateKey(key, newThreadId);
2657
- await redis.set(newStateKey, stateRaw, "EX", THREAD_TTL_SECONDS);
2704
+ await redis.set(newStateKey, stateRaw, "EX", ttlSeconds);
2658
2705
  }
2659
2706
  return forked;
2660
2707
  },
@@ -2669,15 +2716,13 @@ function createThreadManager(config) {
2669
2716
  const existingIds = existing.map((raw) => idOf(deserialize(raw))).filter((id) => typeof id === "string");
2670
2717
  await redis.del(redisKey);
2671
2718
  if (existingIds.length > 0) {
2672
- await redis.del(
2673
- ...existingIds.map((id) => getDedupKey(threadId, id))
2674
- );
2719
+ await redis.del(...existingIds.map(dedupKey));
2675
2720
  }
2676
2721
  if (messages.length > 0) {
2677
2722
  await redis.rpush(redisKey, ...messages.map(serialize));
2678
- await redis.expire(redisKey, THREAD_TTL_SECONDS);
2723
+ await redis.expire(redisKey, ttlSeconds);
2679
2724
  }
2680
- await redis.expire(metaKey, THREAD_TTL_SECONDS);
2725
+ await redis.expire(metaKey, ttlSeconds);
2681
2726
  },
2682
2727
  async delete() {
2683
2728
  await redis.del(redisKey, metaKey, stateKey);
@@ -2689,12 +2734,7 @@ function createThreadManager(config) {
2689
2734
  },
2690
2735
  async saveState(state) {
2691
2736
  await assertThreadExists();
2692
- await redis.set(
2693
- stateKey,
2694
- JSON.stringify(state),
2695
- "EX",
2696
- THREAD_TTL_SECONDS
2697
- );
2737
+ await redis.set(stateKey, JSON.stringify(state), "EX", ttlSeconds);
2698
2738
  },
2699
2739
  async deleteState() {
2700
2740
  await redis.del(stateKey);
@@ -2723,15 +2763,13 @@ function createThreadManager(config) {
2723
2763
  if (idx === -1) return;
2724
2764
  if (idx === 0) {
2725
2765
  await redis.del(redisKey);
2726
- await redis.expire(metaKey, THREAD_TTL_SECONDS);
2766
+ await redis.expire(metaKey, ttlSeconds);
2727
2767
  } else {
2728
2768
  await redis.ltrim(redisKey, 0, idx - 1);
2729
- await redis.expire(redisKey, THREAD_TTL_SECONDS);
2769
+ await redis.expire(redisKey, ttlSeconds);
2730
2770
  }
2731
2771
  if (removedIds.length > 0) {
2732
- await redis.del(
2733
- ...removedIds.map((id) => getDedupKey(threadId, id))
2734
- );
2772
+ await redis.del(...removedIds.map(dedupKey));
2735
2773
  }
2736
2774
  }
2737
2775
  };
@@ -2771,6 +2809,199 @@ function withParentWorkflowState(client, handler) {
2771
2809
  };
2772
2810
  }
2773
2811
 
2812
+ // src/lib/thread/cold-store.ts
2813
+ var gzipAsync = promisify(gzip);
2814
+ var gunzipAsync = promisify(gunzip);
2815
+ function joinKey(parts) {
2816
+ return parts.map((p) => p.replace(/^\/+|\/+$/g, "")).filter((p) => p.length > 0).join("/");
2817
+ }
2818
+ function buildKey(prefix, threadKey, threadId, gzip) {
2819
+ const ext = gzip ? "json.gz" : "json";
2820
+ return joinKey([
2821
+ prefix ?? "threads",
2822
+ threadKey,
2823
+ `${threadId}.${ext}`
2824
+ ]);
2825
+ }
2826
+ async function streamToBuffer(body, onChunk) {
2827
+ if (body == null) return Buffer.alloc(0);
2828
+ if (body instanceof Uint8Array) return Buffer.from(body);
2829
+ if (typeof body[Symbol.asyncIterator] === "function") {
2830
+ const chunks = [];
2831
+ for await (const chunk of body) {
2832
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
2833
+ onChunk?.();
2834
+ }
2835
+ return Buffer.concat(chunks);
2836
+ }
2837
+ if (typeof body.transformToByteArray === "function") {
2838
+ const bytes = await body.transformToByteArray();
2839
+ return Buffer.from(bytes);
2840
+ }
2841
+ if (typeof body.arrayBuffer === "function") {
2842
+ const ab = await body.arrayBuffer();
2843
+ return Buffer.from(ab);
2844
+ }
2845
+ return Buffer.alloc(0);
2846
+ }
2847
+ function createS3ColdStore(config) {
2848
+ const { s3, bucket, prefix, gzip = true } = config;
2849
+ const contentType = config.contentType ?? (gzip ? "application/gzip" : "application/json");
2850
+ return {
2851
+ async read(threadKey, threadId) {
2852
+ const Key = buildKey(prefix, threadKey, threadId, gzip);
2853
+ try {
2854
+ const resp = await s3.send(
2855
+ new GetObjectCommand({ Bucket: bucket, Key })
2856
+ );
2857
+ const { heartbeat } = getActivityContext();
2858
+ const buf = await streamToBuffer(resp.Body, heartbeat);
2859
+ const json = gzip ? (await gunzipAsync(buf)).toString("utf8") : buf.toString("utf8");
2860
+ return JSON.parse(json);
2861
+ } catch (err) {
2862
+ if (isNotFound(err)) return null;
2863
+ throw err;
2864
+ }
2865
+ },
2866
+ async write(threadKey, threadId, snapshot) {
2867
+ const Key = buildKey(prefix, threadKey, threadId, gzip);
2868
+ const json = JSON.stringify(snapshot);
2869
+ const body = gzip ? await gzipAsync(Buffer.from(json, "utf8")) : json;
2870
+ const upload = new Upload({
2871
+ client: s3,
2872
+ params: { Bucket: bucket, Key, Body: body, ContentType: contentType }
2873
+ });
2874
+ const { heartbeat } = getActivityContext();
2875
+ if (heartbeat) upload.on("httpUploadProgress", heartbeat);
2876
+ await upload.done();
2877
+ },
2878
+ async delete(threadKey, threadId) {
2879
+ const Key = buildKey(prefix, threadKey, threadId, gzip);
2880
+ await s3.send(new DeleteObjectCommand({ Bucket: bucket, Key }));
2881
+ }
2882
+ };
2883
+ }
2884
+ function isNotFound(err) {
2885
+ if (typeof err !== "object" || err === null) return false;
2886
+ const e = err;
2887
+ return e.name === "NoSuchKey" || e.Code === "NoSuchKey" || e.code === "NoSuchKey" || e.name === "NotFound" || e.$metadata?.httpStatusCode === 404;
2888
+ }
2889
+
2890
+ // src/lib/thread/snapshot.ts
2891
+ async function encodeSnapshot(config) {
2892
+ const { redis, threadKey, threadId, idOf } = config;
2893
+ const metaKey = getThreadMetaKey(threadKey, threadId);
2894
+ if (await redis.exists(metaKey) === 0) {
2895
+ return null;
2896
+ }
2897
+ const listKey = getThreadListKey(threadKey, threadId);
2898
+ const stateKey = getThreadStateKey(threadKey, threadId);
2899
+ const messages = await redis.lrange(listKey, 0, -1);
2900
+ const stateRaw = await redis.get(stateKey);
2901
+ const state = stateRaw == null ? null : JSON.parse(stateRaw);
2902
+ const dedupIds = idOf ? messages.map(idOf) : [];
2903
+ return { v: 1, messages, state, dedupIds };
2904
+ }
2905
+ async function applySnapshot(config) {
2906
+ const {
2907
+ redis,
2908
+ threadKey,
2909
+ threadId,
2910
+ snapshot,
2911
+ ttlSeconds = THREAD_TTL_SECONDS
2912
+ } = config;
2913
+ const metaKey = getThreadMetaKey(threadKey, threadId);
2914
+ if (await redis.exists(metaKey) === 1) {
2915
+ return;
2916
+ }
2917
+ const listKey = getThreadListKey(threadKey, threadId);
2918
+ const stateKey = getThreadStateKey(threadKey, threadId);
2919
+ await redis.del(listKey, stateKey);
2920
+ const pipeline = redis.pipeline();
2921
+ if (snapshot.messages.length > 0) {
2922
+ pipeline.rpush(listKey, ...snapshot.messages);
2923
+ pipeline.expire(listKey, ttlSeconds);
2924
+ }
2925
+ if (snapshot.state != null) {
2926
+ pipeline.set(stateKey, JSON.stringify(snapshot.state), "EX", ttlSeconds);
2927
+ }
2928
+ for (const id of snapshot.dedupIds) {
2929
+ pipeline.set(getThreadDedupKey(threadId, id), "1", "EX", ttlSeconds);
2930
+ }
2931
+ const results = await pipeline.exec();
2932
+ if (results) {
2933
+ const firstErr = results.find(([err]) => err)?.[0] ?? null;
2934
+ if (firstErr) {
2935
+ await redis.del(
2936
+ listKey,
2937
+ stateKey,
2938
+ ...snapshot.dedupIds.map((id) => getThreadDedupKey(threadId, id))
2939
+ ).catch(() => void 0);
2940
+ throw firstErr;
2941
+ }
2942
+ }
2943
+ await redis.set(metaKey, "1", "EX", ttlSeconds);
2944
+ }
2945
+ async function clearHotTier(config) {
2946
+ const { redis, threadKey, threadId, dedupIds = [] } = config;
2947
+ const keys = [
2948
+ getThreadListKey(threadKey, threadId),
2949
+ getThreadMetaKey(threadKey, threadId),
2950
+ getThreadStateKey(threadKey, threadId),
2951
+ ...dedupIds.map((id) => getThreadDedupKey(threadId, id))
2952
+ ];
2953
+ await redis.del(...keys);
2954
+ }
2955
+
2956
+ // src/lib/thread/tiered.ts
2957
+ function createTieredThreadManager(config) {
2958
+ const {
2959
+ redis,
2960
+ threadId,
2961
+ key = "messages",
2962
+ coldStore,
2963
+ idOf,
2964
+ deserialize = (raw) => JSON.parse(raw),
2965
+ ttlSeconds = THREAD_TTL_SECONDS
2966
+ } = config;
2967
+ const base = createThreadManager(config);
2968
+ const rawIdOf = idOf ? (raw) => idOf(deserialize(raw)) : void 0;
2969
+ return Object.assign(base, {
2970
+ async hydrate() {
2971
+ if (!coldStore) return;
2972
+ const snapshot = await coldStore.read(key, threadId);
2973
+ if (!snapshot) return;
2974
+ await applySnapshot({
2975
+ redis,
2976
+ threadKey: key,
2977
+ threadId,
2978
+ snapshot,
2979
+ ttlSeconds
2980
+ });
2981
+ },
2982
+ async flush(opts) {
2983
+ if (!coldStore) return;
2984
+ const snapshot = await encodeSnapshot({
2985
+ redis,
2986
+ threadKey: key,
2987
+ threadId,
2988
+ ...rawIdOf ? { idOf: rawIdOf } : {}
2989
+ });
2990
+ if (!snapshot) return;
2991
+ await coldStore.write(key, threadId, snapshot);
2992
+ const deleteHot = opts?.deleteHot ?? true;
2993
+ if (deleteHot) {
2994
+ await clearHotTier({
2995
+ redis,
2996
+ threadKey: key,
2997
+ threadId,
2998
+ dedupIds: snapshot.dedupIds
2999
+ });
3000
+ }
3001
+ }
3002
+ });
3003
+ }
3004
+
2774
3005
  // src/lib/sandbox/manager.ts
2775
3006
  var CAP_METHOD_TO_CAPABILITY = [
2776
3007
  { method: "pause", capability: "pause" },
@@ -3441,57 +3672,190 @@ ${result.stderr}`,
3441
3672
  };
3442
3673
 
3443
3674
  // src/tools/edit/handler.ts
3444
- function escapeRegExp(str) {
3445
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3675
+ function splitLines(text) {
3676
+ if (text.length === 0) return [];
3677
+ return text.replace(/\r\n/g, "\n").split("\n");
3678
+ }
3679
+ function lineNumberAt(content, index) {
3680
+ let line = 1;
3681
+ for (let i = 0; i < index; i++) {
3682
+ if (content.charCodeAt(i) === 10) line++;
3683
+ }
3684
+ return line;
3685
+ }
3686
+ function lineEnd(startLine, lines) {
3687
+ return lines.length === 0 ? startLine : startLine + lines.length - 1;
3688
+ }
3689
+ function indicesOf(content, needle) {
3690
+ const indices = [];
3691
+ let cursor = 0;
3692
+ while (cursor <= content.length) {
3693
+ const index = content.indexOf(needle, cursor);
3694
+ if (index === -1) break;
3695
+ indices.push(index);
3696
+ cursor = index + needle.length;
3697
+ }
3698
+ return indices;
3699
+ }
3700
+ function makeHunk(editIndex, beforeContent, replacementIndex, oldString, newString) {
3701
+ const oldStartLine = lineNumberAt(beforeContent, replacementIndex);
3702
+ const oldLines = splitLines(oldString);
3703
+ const newLines = splitLines(newString);
3704
+ return {
3705
+ editIndex,
3706
+ oldStartLine,
3707
+ oldEndLine: lineEnd(oldStartLine, oldLines),
3708
+ newStartLine: oldStartLine,
3709
+ newEndLine: lineEnd(oldStartLine, newLines),
3710
+ oldLines,
3711
+ newLines
3712
+ };
3713
+ }
3714
+ function applyOneEdit(content, edit, editIndex) {
3715
+ const { old_string, new_string, replace_all = false } = edit;
3716
+ if (old_string.length === 0) {
3717
+ return {
3718
+ ok: false,
3719
+ editIndex,
3720
+ message: `Error: old_string for edit ${editIndex} must not be empty.`
3721
+ };
3722
+ }
3723
+ if (old_string === new_string) {
3724
+ return {
3725
+ ok: false,
3726
+ editIndex,
3727
+ message: `Error: old_string and new_string must be different for edit ${editIndex}.`
3728
+ };
3729
+ }
3730
+ const matches = indicesOf(content, old_string);
3731
+ if (matches.length === 0) {
3732
+ return {
3733
+ ok: false,
3734
+ editIndex,
3735
+ message: `Error: Could not find old_string for edit ${editIndex}. Make sure it matches exactly (whitespace-sensitive).`
3736
+ };
3737
+ }
3738
+ if (!replace_all && matches.length > 1) {
3739
+ return {
3740
+ ok: false,
3741
+ editIndex,
3742
+ message: `Error: old_string for edit ${editIndex} appears ${matches.length} times. Provide more context to make it unique, or use replace_all: true for that edit.`
3743
+ };
3744
+ }
3745
+ if (replace_all) {
3746
+ const hunks = matches.map(
3747
+ (index2) => makeHunk(editIndex, content, index2, old_string, new_string)
3748
+ );
3749
+ return {
3750
+ ok: true,
3751
+ content: content.split(old_string).join(new_string),
3752
+ replacements: matches.length,
3753
+ hunks
3754
+ };
3755
+ }
3756
+ const index = matches[0];
3757
+ if (index === void 0) {
3758
+ return {
3759
+ ok: false,
3760
+ editIndex,
3761
+ message: `Error: Could not find old_string for edit ${editIndex}.`
3762
+ };
3763
+ }
3764
+ return {
3765
+ ok: true,
3766
+ content: content.slice(0, index) + new_string + content.slice(index + old_string.length),
3767
+ replacements: 1,
3768
+ hunks: [makeHunk(editIndex, content, index, old_string, new_string)]
3769
+ };
3770
+ }
3771
+ function applyEditPlan(content, edits) {
3772
+ if (edits.length === 0) {
3773
+ return {
3774
+ ok: false,
3775
+ message: "Error: edits must contain at least one edit."
3776
+ };
3777
+ }
3778
+ let current = content;
3779
+ let replacements = 0;
3780
+ const hunks = [];
3781
+ for (const [index, edit] of edits.entries()) {
3782
+ const result = applyOneEdit(current, edit, index);
3783
+ if (!result.ok) return result;
3784
+ current = result.content;
3785
+ replacements += result.replacements;
3786
+ hunks.push(...result.hunks);
3787
+ }
3788
+ return { ok: true, content: current, replacements, hunks };
3789
+ }
3790
+ function editFailureResult(filePath, message) {
3791
+ return {
3792
+ toolResponse: message,
3793
+ data: { path: filePath, success: false, replacements: 0 }
3794
+ };
3446
3795
  }
3447
3796
  var editHandler = async (args, { sandbox }) => {
3448
3797
  const { fs } = sandbox;
3449
3798
  const { file_path, old_string, new_string, replace_all = false } = args;
3450
- if (old_string === new_string) {
3799
+ try {
3800
+ const exists = await fs.exists(file_path);
3801
+ if (!exists) {
3802
+ return editFailureResult(
3803
+ file_path,
3804
+ `Error: File "${file_path}" does not exist.`
3805
+ );
3806
+ }
3807
+ const content = await fs.readFile(file_path);
3808
+ const result = applyEditPlan(content, [
3809
+ { old_string, new_string, replace_all }
3810
+ ]);
3811
+ if (!result.ok) {
3812
+ return editFailureResult(file_path, result.message);
3813
+ }
3814
+ await fs.writeFile(file_path, result.content);
3815
+ const summary = replace_all ? `Replaced ${result.replacements} occurrence(s)` : `Replaced 1 occurrence`;
3816
+ return {
3817
+ toolResponse: `${summary} in ${file_path}`,
3818
+ data: {
3819
+ path: file_path,
3820
+ success: true,
3821
+ replacements: result.replacements,
3822
+ hunks: result.hunks
3823
+ }
3824
+ };
3825
+ } catch (error) {
3826
+ const message = error instanceof Error ? error.message : "Unknown error";
3451
3827
  return {
3452
- toolResponse: `Error: old_string and new_string must be different.`,
3828
+ toolResponse: `Error editing file "${file_path}": ${message}`,
3453
3829
  data: { path: file_path, success: false, replacements: 0 }
3454
3830
  };
3455
3831
  }
3832
+ };
3833
+ var multiEditHandler = async (args, { sandbox }) => {
3834
+ const { fs } = sandbox;
3835
+ const { file_path, edits } = args;
3456
3836
  try {
3457
3837
  const exists = await fs.exists(file_path);
3458
3838
  if (!exists) {
3459
- return {
3460
- toolResponse: `Error: File "${file_path}" does not exist.`,
3461
- data: { path: file_path, success: false, replacements: 0 }
3462
- };
3839
+ return editFailureResult(
3840
+ file_path,
3841
+ `Error: File "${file_path}" does not exist.`
3842
+ );
3463
3843
  }
3464
3844
  const content = await fs.readFile(file_path);
3465
- if (!content.includes(old_string)) {
3466
- return {
3467
- toolResponse: `Error: Could not find the specified text in "${file_path}". Make sure old_string matches exactly (whitespace-sensitive).`,
3468
- data: { path: file_path, success: false, replacements: 0 }
3469
- };
3845
+ const result = applyEditPlan(content, edits);
3846
+ if (!result.ok) {
3847
+ const suffix = result.editIndex === void 0 ? "" : ` in ${file_path}`;
3848
+ return editFailureResult(file_path, `${result.message}${suffix}`);
3470
3849
  }
3471
- const escapedOldString = escapeRegExp(old_string);
3472
- const globalRegex = new RegExp(escapedOldString, "g");
3473
- const occurrences = (content.match(globalRegex) || []).length;
3474
- if (!replace_all && occurrences > 1) {
3475
- return {
3476
- toolResponse: `Error: old_string appears ${occurrences} times in "${file_path}". Either provide more context to make it unique, or use replace_all: true.`,
3477
- data: { path: file_path, success: false, replacements: 0 }
3478
- };
3479
- }
3480
- let newContent;
3481
- let replacements;
3482
- if (replace_all) {
3483
- newContent = content.split(old_string).join(new_string);
3484
- replacements = occurrences;
3485
- } else {
3486
- const index = content.indexOf(old_string);
3487
- newContent = content.slice(0, index) + new_string + content.slice(index + old_string.length);
3488
- replacements = 1;
3489
- }
3490
- await fs.writeFile(file_path, newContent);
3491
- const summary = replace_all ? `Replaced ${replacements} occurrence(s)` : `Replaced 1 occurrence`;
3850
+ await fs.writeFile(file_path, result.content);
3492
3851
  return {
3493
- toolResponse: `${summary} in ${file_path}`,
3494
- data: { path: file_path, success: true, replacements }
3852
+ toolResponse: `Applied ${edits.length} edit(s), ${result.replacements} replacement(s) in ${file_path}`,
3853
+ data: {
3854
+ path: file_path,
3855
+ success: true,
3856
+ replacements: result.replacements,
3857
+ hunks: result.hunks
3858
+ }
3495
3859
  };
3496
3860
  } catch (error) {
3497
3861
  const message = error instanceof Error ? error.message : "Unknown error";
@@ -3671,6 +4035,6 @@ var toTree = async (fs, opts = {}) => {
3671
4035
  return base + subtree;
3672
4036
  };
3673
4037
 
3674
- export { DEFAULT_SUBAGENT_WORKFLOW_RUN_TIMEOUT, FileSystemSkillProvider, NodeFsSandboxFileSystem, SandboxManager, SandboxNotFoundError, SandboxNotSupportedError, THREAD_TTL_SECONDS, VirtualFileSystem, applyVirtualTreeMutations, askUserQuestionTool, bashHandler, bashTool, composeHooks, createAgentStateManager, createAskUserQuestionHandler, createBashToolDescription, createObservabilityHooks, createReadSkillHandler, createReadSkillTool, createRunAgentActivity, createSession, createTaskCreateHandler, createTaskGetHandler, createTaskListHandler, createTaskUpdateHandler, createThreadManager, createToolRouter, createVirtualFsActivities, defineSubagent, defineSubagentWorkflow, defineTool, defineWorkflow, editHandler, editTool, filesWithMimeType, formatVirtualFileTree, getActivityContext, getShortId, getThreadListKey, getThreadMetaKey, globHandler, globTool, grepTool, hasDirectory, hasFileWithMimeType, hasNoOtherToolCalls, isTerminalStatus, parseSkillFile, proxyRunAgent, proxyVirtualFsOps, queryParentWorkflowState, readFileHandler, readFileTool, taskCreateTool, taskGetTool, taskListTool, taskUpdateTool, toTree, withAutoAppend, withParentWorkflowState, withSandbox, withVirtualFs, writeFileHandler, writeFileTool };
4038
+ export { DEFAULT_SUBAGENT_WORKFLOW_RUN_TIMEOUT, FileSystemSkillProvider, NodeFsSandboxFileSystem, SandboxManager, SandboxNotFoundError, SandboxNotSupportedError, THREAD_TTL_SECONDS, VirtualFileSystem, applySnapshot, applyVirtualTreeMutations, askUserQuestionTool, bashHandler, bashTool, clearHotTier, composeHooks, createAgentStateManager, createAskUserQuestionHandler, createBashToolDescription, createObservabilityHooks, createReadSkillHandler, createReadSkillTool, createRunAgentActivity, createS3ColdStore, createSession, createTaskCreateHandler, createTaskGetHandler, createTaskListHandler, createTaskUpdateHandler, createThreadManager, createTieredThreadManager, createToolRouter, createVirtualFsActivities, defineSubagent, defineSubagentWorkflow, defineTool, defineWorkflow, editHandler, editTool, encodeSnapshot, filesWithMimeType, formatVirtualFileTree, getActivityContext, getShortId, getThreadDedupKey, getThreadListKey, getThreadMetaKey, getThreadStateKey, globHandler, globTool, grepTool, hasDirectory, hasFileWithMimeType, hasNoOtherToolCalls, isTerminalStatus, multiEditHandler, multiEditTool, parseSkillFile, proxyRunAgent, proxyVirtualFsOps, queryParentWorkflowState, readFileHandler, readFileTool, taskCreateTool, taskGetTool, taskListTool, taskUpdateTool, toTree, withAutoAppend, withParentWorkflowState, withSandbox, withVirtualFs, writeFileHandler, writeFileTool };
3675
4039
  //# sourceMappingURL=index.js.map
3676
4040
  //# sourceMappingURL=index.js.map