@posthog/agent 2.3.401 → 2.3.403

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 (40) hide show
  1. package/README.md +11 -14
  2. package/dist/agent.js +1 -7
  3. package/dist/agent.js.map +1 -1
  4. package/dist/handoff-checkpoint.d.ts +0 -3
  5. package/dist/handoff-checkpoint.js +38 -69
  6. package/dist/handoff-checkpoint.js.map +1 -1
  7. package/dist/index.d.ts +0 -2
  8. package/dist/index.js +0 -2
  9. package/dist/index.js.map +1 -1
  10. package/dist/posthog-api.js +1 -5
  11. package/dist/posthog-api.js.map +1 -1
  12. package/dist/resume.d.ts +5 -6
  13. package/dist/resume.js +2 -41
  14. package/dist/resume.js.map +1 -1
  15. package/dist/server/agent-server.d.ts +1 -2
  16. package/dist/server/agent-server.js +103 -815
  17. package/dist/server/agent-server.js.map +1 -1
  18. package/dist/server/bin.cjs +101 -806
  19. package/dist/server/bin.cjs.map +1 -1
  20. package/dist/types.d.ts +2 -13
  21. package/dist/types.js.map +1 -1
  22. package/package.json +3 -7
  23. package/src/acp-extensions.ts +0 -3
  24. package/src/handoff-checkpoint.test.ts +2 -17
  25. package/src/handoff-checkpoint.ts +15 -61
  26. package/src/resume.ts +5 -11
  27. package/src/sagas/resume-saga.test.ts +27 -77
  28. package/src/sagas/resume-saga.ts +3 -44
  29. package/src/sagas/test-fixtures.ts +17 -76
  30. package/src/server/agent-server.ts +22 -103
  31. package/src/test/fixtures/api.ts +2 -15
  32. package/src/types.ts +0 -16
  33. package/dist/tree-tracker.d.ts +0 -68
  34. package/dist/tree-tracker.js +0 -6462
  35. package/dist/tree-tracker.js.map +0 -1
  36. package/src/sagas/apply-snapshot-saga.test.ts +0 -691
  37. package/src/sagas/apply-snapshot-saga.ts +0 -114
  38. package/src/sagas/capture-tree-saga.test.ts +0 -910
  39. package/src/sagas/capture-tree-saga.ts +0 -165
  40. package/src/tree-tracker.ts +0 -173
@@ -809,10 +809,10 @@ var require_src2 = __commonJS({
809
809
  var fs_1 = __require("fs");
810
810
  var debug_1 = __importDefault(require_src());
811
811
  var log = debug_1.default("@kwsites/file-exists");
812
- function check(path17, isFile2, isDirectory) {
813
- log(`checking %s`, path17);
812
+ function check(path16, isFile2, isDirectory) {
813
+ log(`checking %s`, path16);
814
814
  try {
815
- const stat4 = fs_1.statSync(path17);
815
+ const stat4 = fs_1.statSync(path16);
816
816
  if (stat4.isFile() && isFile2) {
817
817
  log(`[OK] path represents a file`);
818
818
  return true;
@@ -832,8 +832,8 @@ var require_src2 = __commonJS({
832
832
  throw e;
833
833
  }
834
834
  }
835
- function exists2(path17, type = exports2.READABLE) {
836
- return check(path17, (type & exports2.FILE) > 0, (type & exports2.FOLDER) > 0);
835
+ function exists2(path16, type = exports2.READABLE) {
836
+ return check(path16, (type & exports2.FILE) > 0, (type & exports2.FOLDER) > 0);
837
837
  }
838
838
  exports2.exists = exists2;
839
839
  exports2.FILE = 1;
@@ -929,11 +929,11 @@ var require_tree_sitter = __commonJS({
929
929
  throw toThrow;
930
930
  };
931
931
  var scriptDirectory = "";
932
- function locateFile(path17) {
932
+ function locateFile(path16) {
933
933
  if (Module["locateFile"]) {
934
- return Module["locateFile"](path17, scriptDirectory);
934
+ return Module["locateFile"](path16, scriptDirectory);
935
935
  }
936
- return scriptDirectory + path17;
936
+ return scriptDirectory + path16;
937
937
  }
938
938
  var readAsync, readBinary;
939
939
  if (ENVIRONMENT_IS_NODE) {
@@ -3454,8 +3454,8 @@ var require_tree_sitter = __commonJS({
3454
3454
  } else {
3455
3455
  const url = input;
3456
3456
  if (typeof process !== "undefined" && process.versions && process.versions.node) {
3457
- const fs14 = __require("fs");
3458
- bytes = Promise.resolve(fs14.readFileSync(url));
3457
+ const fs13 = __require("fs");
3458
+ bytes = Promise.resolve(fs13.readFileSync(url));
3459
3459
  } else {
3460
3460
  bytes = fetch(url).then((response) => response.arrayBuffer().then((buffer) => {
3461
3461
  if (response.ok) {
@@ -3784,8 +3784,8 @@ ${JSON.stringify(symbolNames, null, 2)}`);
3784
3784
  });
3785
3785
 
3786
3786
  // src/server/agent-server.ts
3787
- import { mkdir as mkdir8, writeFile as writeFile6 } from "fs/promises";
3788
- import { basename as basename2, join as join14 } from "path";
3787
+ import { mkdir as mkdir4, writeFile as writeFile4 } from "fs/promises";
3788
+ import { basename as basename2, join as join11 } from "path";
3789
3789
  import { pathToFileURL } from "url";
3790
3790
  import {
3791
3791
  ClientSideConnection as ClientSideConnection2,
@@ -3835,8 +3835,8 @@ function pathspec(...paths) {
3835
3835
  cache.set(key, paths);
3836
3836
  return key;
3837
3837
  }
3838
- function isPathSpec(path17) {
3839
- return path17 instanceof String && cache.has(path17);
3838
+ function isPathSpec(path16) {
3839
+ return path16 instanceof String && cache.has(path16);
3840
3840
  }
3841
3841
  function toPaths(pathSpec) {
3842
3842
  return cache.get(pathSpec) || [];
@@ -3925,8 +3925,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
3925
3925
  function forEachLineWithContent(input, callback) {
3926
3926
  return toLinesWithContent(input, true).map((line) => callback(line));
3927
3927
  }
3928
- function folderExists(path17) {
3929
- return (0, import_file_exists.exists)(path17, import_file_exists.FOLDER);
3928
+ function folderExists(path16) {
3929
+ return (0, import_file_exists.exists)(path16, import_file_exists.FOLDER);
3930
3930
  }
3931
3931
  function append(target, item) {
3932
3932
  if (Array.isArray(target)) {
@@ -4330,8 +4330,8 @@ function checkIsRepoRootTask() {
4330
4330
  commands,
4331
4331
  format: "utf-8",
4332
4332
  onError,
4333
- parser(path17) {
4334
- return /^\.(git)?$/.test(path17.trim());
4333
+ parser(path16) {
4334
+ return /^\.(git)?$/.test(path16.trim());
4335
4335
  }
4336
4336
  };
4337
4337
  }
@@ -4765,11 +4765,11 @@ function parseGrep(grep) {
4765
4765
  const paths = /* @__PURE__ */ new Set();
4766
4766
  const results = {};
4767
4767
  forEachLineWithContent(grep, (input) => {
4768
- const [path17, line, preview] = input.split(NULL);
4769
- paths.add(path17);
4770
- (results[path17] = results[path17] || []).push({
4768
+ const [path16, line, preview] = input.split(NULL);
4769
+ paths.add(path16);
4770
+ (results[path16] = results[path16] || []).push({
4771
4771
  line: asNumber(line),
4772
- path: path17,
4772
+ path: path16,
4773
4773
  preview
4774
4774
  });
4775
4775
  });
@@ -5534,14 +5534,14 @@ var init_hash_object = __esm({
5534
5534
  init_task();
5535
5535
  }
5536
5536
  });
5537
- function parseInit(bare, path17, text2) {
5537
+ function parseInit(bare, path16, text2) {
5538
5538
  const response = String(text2).trim();
5539
5539
  let result;
5540
5540
  if (result = initResponseRegex.exec(response)) {
5541
- return new InitSummary(bare, path17, false, result[1]);
5541
+ return new InitSummary(bare, path16, false, result[1]);
5542
5542
  }
5543
5543
  if (result = reInitResponseRegex.exec(response)) {
5544
- return new InitSummary(bare, path17, true, result[1]);
5544
+ return new InitSummary(bare, path16, true, result[1]);
5545
5545
  }
5546
5546
  let gitDir = "";
5547
5547
  const tokens = response.split(" ");
@@ -5552,7 +5552,7 @@ function parseInit(bare, path17, text2) {
5552
5552
  break;
5553
5553
  }
5554
5554
  }
5555
- return new InitSummary(bare, path17, /^re/i.test(response), gitDir);
5555
+ return new InitSummary(bare, path16, /^re/i.test(response), gitDir);
5556
5556
  }
5557
5557
  var InitSummary;
5558
5558
  var initResponseRegex;
@@ -5561,9 +5561,9 @@ var init_InitSummary = __esm({
5561
5561
  "src/lib/responses/InitSummary.ts"() {
5562
5562
  "use strict";
5563
5563
  InitSummary = class {
5564
- constructor(bare, path17, existing, gitDir) {
5564
+ constructor(bare, path16, existing, gitDir) {
5565
5565
  this.bare = bare;
5566
- this.path = path17;
5566
+ this.path = path16;
5567
5567
  this.existing = existing;
5568
5568
  this.gitDir = gitDir;
5569
5569
  }
@@ -5575,7 +5575,7 @@ var init_InitSummary = __esm({
5575
5575
  function hasBareCommand(command) {
5576
5576
  return command.includes(bareCommand);
5577
5577
  }
5578
- function initTask(bare = false, path17, customArgs) {
5578
+ function initTask(bare = false, path16, customArgs) {
5579
5579
  const commands = ["init", ...customArgs];
5580
5580
  if (bare && !hasBareCommand(commands)) {
5581
5581
  commands.splice(1, 0, bareCommand);
@@ -5584,7 +5584,7 @@ function initTask(bare = false, path17, customArgs) {
5584
5584
  commands,
5585
5585
  format: "utf-8",
5586
5586
  parser(text2) {
5587
- return parseInit(commands.includes("--bare"), path17, text2);
5587
+ return parseInit(commands.includes("--bare"), path16, text2);
5588
5588
  }
5589
5589
  };
5590
5590
  }
@@ -6400,12 +6400,12 @@ var init_FileStatusSummary = __esm({
6400
6400
  "use strict";
6401
6401
  fromPathRegex = /^(.+)\0(.+)$/;
6402
6402
  FileStatusSummary = class {
6403
- constructor(path17, index, working_dir) {
6404
- this.path = path17;
6403
+ constructor(path16, index, working_dir) {
6404
+ this.path = path16;
6405
6405
  this.index = index;
6406
6406
  this.working_dir = working_dir;
6407
6407
  if (index === "R" || working_dir === "R") {
6408
- const detail = fromPathRegex.exec(path17) || [null, path17, path17];
6408
+ const detail = fromPathRegex.exec(path16) || [null, path16, path16];
6409
6409
  this.from = detail[2] || "";
6410
6410
  this.path = detail[1] || "";
6411
6411
  }
@@ -6436,14 +6436,14 @@ function splitLine(result, lineStr) {
6436
6436
  default:
6437
6437
  return;
6438
6438
  }
6439
- function data(index, workingDir, path17) {
6439
+ function data(index, workingDir, path16) {
6440
6440
  const raw = `${index}${workingDir}`;
6441
6441
  const handler = parsers6.get(raw);
6442
6442
  if (handler) {
6443
- handler(result, path17);
6443
+ handler(result, path16);
6444
6444
  }
6445
6445
  if (raw !== "##" && raw !== "!!") {
6446
- result.files.push(new FileStatusSummary(path17, index, workingDir));
6446
+ result.files.push(new FileStatusSummary(path16, index, workingDir));
6447
6447
  }
6448
6448
  }
6449
6449
  }
@@ -6756,9 +6756,9 @@ var init_simple_git_api = __esm({
6756
6756
  next
6757
6757
  );
6758
6758
  }
6759
- hashObject(path17, write) {
6759
+ hashObject(path16, write) {
6760
6760
  return this._runTask(
6761
- hashObjectTask(path17, write === true),
6761
+ hashObjectTask(path16, write === true),
6762
6762
  trailingFunctionArgument(arguments)
6763
6763
  );
6764
6764
  }
@@ -7111,8 +7111,8 @@ var init_branch = __esm({
7111
7111
  }
7112
7112
  });
7113
7113
  function toPath(input) {
7114
- const path17 = input.trim().replace(/^["']|["']$/g, "");
7115
- return path17 && normalize(path17);
7114
+ const path16 = input.trim().replace(/^["']|["']$/g, "");
7115
+ return path16 && normalize(path16);
7116
7116
  }
7117
7117
  var parseCheckIgnore;
7118
7118
  var init_CheckIgnore = __esm({
@@ -7426,8 +7426,8 @@ __export(sub_module_exports, {
7426
7426
  subModuleTask: () => subModuleTask,
7427
7427
  updateSubModuleTask: () => updateSubModuleTask
7428
7428
  });
7429
- function addSubModuleTask(repo, path17) {
7430
- return subModuleTask(["add", repo, path17]);
7429
+ function addSubModuleTask(repo, path16) {
7430
+ return subModuleTask(["add", repo, path16]);
7431
7431
  }
7432
7432
  function initSubModuleTask(customArgs) {
7433
7433
  return subModuleTask(["init", ...customArgs]);
@@ -7757,8 +7757,8 @@ var require_git = __commonJS2({
7757
7757
  }
7758
7758
  return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
7759
7759
  };
7760
- Git2.prototype.submoduleAdd = function(repo, path17, then) {
7761
- return this._runTask(addSubModuleTask2(repo, path17), trailingFunctionArgument2(arguments));
7760
+ Git2.prototype.submoduleAdd = function(repo, path16, then) {
7761
+ return this._runTask(addSubModuleTask2(repo, path16), trailingFunctionArgument2(arguments));
7762
7762
  };
7763
7763
  Git2.prototype.submoduleUpdate = function(args2, then) {
7764
7764
  return this._runTask(
@@ -8591,12 +8591,6 @@ async function listWorktrees(baseDir, options) {
8591
8591
  return worktrees;
8592
8592
  }, { signal: options?.abortSignal });
8593
8593
  }
8594
- async function getHeadSha(baseDir, options) {
8595
- const manager = getGitOperationManager();
8596
- return manager.executeRead(baseDir, (git) => git.revparse(["HEAD"]), {
8597
- signal: options?.abortSignal
8598
- });
8599
- }
8600
8594
 
8601
8595
  // src/server/agent-server.ts
8602
8596
  import { Hono } from "hono";
@@ -8605,7 +8599,7 @@ import { z as z4 } from "zod";
8605
8599
  // package.json
8606
8600
  var package_default = {
8607
8601
  name: "@posthog/agent",
8608
- version: "2.3.401",
8602
+ version: "2.3.403",
8609
8603
  repository: "https://github.com/PostHog/code",
8610
8604
  description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
8611
8605
  exports: {
@@ -8673,10 +8667,6 @@ var package_default = {
8673
8667
  types: "./dist/handoff-checkpoint.d.ts",
8674
8668
  import: "./dist/handoff-checkpoint.js"
8675
8669
  },
8676
- "./tree-tracker": {
8677
- types: "./dist/tree-tracker.d.ts",
8678
- import: "./dist/tree-tracker.js"
8679
- },
8680
8670
  "./server": {
8681
8671
  types: "./dist/server/agent-server.d.ts",
8682
8672
  import: "./dist/server/agent-server.js"
@@ -8772,8 +8762,6 @@ var POSTHOG_NOTIFICATIONS = {
8772
8762
  CONSOLE: "_posthog/console",
8773
8763
  /** Maps taskRunId to agent's sessionId and adapter type (for resumption) */
8774
8764
  SDK_SESSION: "_posthog/sdk_session",
8775
- /** Tree state snapshot captured (git tree hash + file archive) */
8776
- TREE_SNAPSHOT: "_posthog/tree_snapshot",
8777
8765
  /** Git checkpoint captured for handoff */
8778
8766
  GIT_CHECKPOINT: "_posthog/git_checkpoint",
8779
8767
  /** Agent mode changed (interactive/background) */
@@ -8812,9 +8800,6 @@ function matchesExt(method, expected) {
8812
8800
  if (!method) return false;
8813
8801
  return method === expected || method === `_${expected}`;
8814
8802
  }
8815
- function isNotification(method, expected) {
8816
- return matchesExt(method, expected);
8817
- }
8818
8803
  function isMethod(method, expected) {
8819
8804
  return matchesExt(method, expected);
8820
8805
  }
@@ -13450,8 +13435,8 @@ var ToolContentBuilder = class {
13450
13435
  this.items.push({ type: "content", content: image(data, mimeType, uri) });
13451
13436
  return this;
13452
13437
  }
13453
- diff(path17, oldText, newText) {
13454
- this.items.push({ type: "diff", path: path17, oldText, newText });
13438
+ diff(path16, oldText, newText) {
13439
+ this.items.push({ type: "diff", path: path16, oldText, newText });
13455
13440
  return this;
13456
13441
  }
13457
13442
  build() {
@@ -18444,19 +18429,14 @@ function createCodexConnection(config) {
18444
18429
  }
18445
18430
 
18446
18431
  // src/handoff-checkpoint.ts
18447
- import {
18448
- mkdir as mkdir4,
18449
- readdir,
18450
- readFile as readFile4,
18451
- rm as rm4,
18452
- rmdir,
18453
- writeFile as writeFile2
18454
- } from "fs/promises";
18455
- import { join as join9 } from "path";
18432
+ import { mkdtemp as mkdtemp2, readFile as readFile4, rm as rm4, writeFile as writeFile2 } from "fs/promises";
18433
+ import { tmpdir as tmpdir2 } from "os";
18434
+ import { dirname as dirname6, join as join9 } from "path";
18456
18435
 
18457
18436
  // ../git/dist/handoff.js
18458
18437
  import { spawn as spawn4 } from "child_process";
18459
- import { copyFile, mkdir as mkdir3, readFile as readFile3, rm as rm3, stat as stat3 } from "fs/promises";
18438
+ import { copyFile, mkdtemp, readFile as readFile3, rm as rm3, stat as stat3 } from "fs/promises";
18439
+ import { tmpdir } from "os";
18460
18440
  import path13 from "path";
18461
18441
 
18462
18442
  // ../git/dist/sagas/checkpoint.js
@@ -18864,7 +18844,7 @@ var GitHandoffTracker = class {
18864
18844
  this.repositoryPath = config.repositoryPath;
18865
18845
  this.logger = config.logger;
18866
18846
  }
18867
- async captureForHandoff(localGitState) {
18847
+ async captureForHandoff(_localGitState) {
18868
18848
  const captureSaga = new CaptureCheckpointSaga(this.logger);
18869
18849
  const result = await captureSaga.run({ baseDir: this.repositoryPath });
18870
18850
  if (!result.success) {
@@ -18872,15 +18852,19 @@ var GitHandoffTracker = class {
18872
18852
  }
18873
18853
  const checkpoint = result.data;
18874
18854
  const git = createGitClient(this.repositoryPath);
18875
- const tempDir = await this.getTempDir(git);
18855
+ const tempDir = await this.createTempDir(checkpoint.checkpointId);
18876
18856
  const checkpointRef = `${CHECKPOINT_REF_PREFIX2}${checkpoint.checkpointId}`;
18877
- const shouldIncludeHead = !!checkpoint.head && checkpoint.head !== localGitState?.head;
18878
- const headRef = shouldIncludeHead ? `${HANDOFF_HEAD_REF_PREFIX}${checkpoint.checkpointId}` : void 0;
18857
+ const packRefs = [
18858
+ checkpoint.head,
18859
+ checkpoint.indexTree,
18860
+ checkpoint.worktreeTree
18861
+ ].filter((ref) => !!ref);
18862
+ const headRef = checkpoint.head ? `${HANDOFF_HEAD_REF_PREFIX}${checkpoint.checkpointId}` : void 0;
18879
18863
  const packPrefix = path13.join(tempDir, checkpoint.checkpointId);
18880
18864
  try {
18881
18865
  const [headPack, indexFile, tracking] = await Promise.all([
18882
- shouldIncludeHead && checkpoint.head ? this.captureHeadPack(packPrefix, checkpoint.head) : Promise.resolve(void 0),
18883
- this.copyIndexFile(git, checkpoint.checkpointId),
18866
+ this.captureObjectPack(packPrefix, packRefs),
18867
+ this.copyIndexFile(git, checkpoint.checkpointId, tempDir),
18884
18868
  getTrackingMetadata(git, checkpoint.branch)
18885
18869
  ]);
18886
18870
  return {
@@ -18927,6 +18911,8 @@ var GitHandoffTracker = class {
18927
18911
  } else if (checkpoint.head) {
18928
18912
  await git.checkout(checkpoint.head);
18929
18913
  }
18914
+ await git.clean(["f", "d"]);
18915
+ await git.raw(["read-tree", "--reset", "-u", checkpoint.worktreeTree]);
18930
18916
  if (indexPath) {
18931
18917
  await this.restoreIndexFile(git, indexPath);
18932
18918
  }
@@ -18938,8 +18924,8 @@ var GitHandoffTracker = class {
18938
18924
  totalBytes: packBytes + indexBytes
18939
18925
  };
18940
18926
  }
18941
- async captureHeadPack(packPrefix, headCommit) {
18942
- const hash = await this.runGitWithInput(["pack-objects", packPrefix, "--revs"], `${headCommit}
18927
+ async captureObjectPack(packPrefix, refs) {
18928
+ const hash = await this.runGitWithInput(["pack-objects", packPrefix, "--revs"], `${refs.join("\n")}
18943
18929
  `);
18944
18930
  const packPath = `${packPrefix}-${hash.trim()}.pack`;
18945
18931
  const rawBytes = await this.getFileSize(packPath);
@@ -18947,9 +18933,8 @@ var GitHandoffTracker = class {
18947
18933
  });
18948
18934
  return { path: packPath, rawBytes };
18949
18935
  }
18950
- async copyIndexFile(git, checkpointId) {
18936
+ async copyIndexFile(git, checkpointId, tempDir) {
18951
18937
  const indexPath = await this.getGitPath(git, "index");
18952
- const tempDir = await this.getTempDir(git);
18953
18938
  const copiedIndexPath = path13.join(tempDir, `${checkpointId}.index`);
18954
18939
  await copyFile(indexPath, copiedIndexPath);
18955
18940
  return {
@@ -19077,13 +19062,8 @@ var GitHandoffTracker = class {
19077
19062
  ]);
19078
19063
  return exitCode === 0;
19079
19064
  }
19080
- async getTempDir(git) {
19081
- const raw = await git.raw(["rev-parse", "--git-common-dir"]);
19082
- const commonDir = raw.trim() || ".git";
19083
- const resolved = path13.isAbsolute(commonDir) ? commonDir : path13.resolve(this.repositoryPath, commonDir);
19084
- const tempDir = path13.join(resolved, "posthog-code-tmp");
19085
- await mkdir3(tempDir, { recursive: true });
19086
- return tempDir;
19065
+ async createTempDir(checkpointId) {
19066
+ return mkdtemp(joinTempPrefix(checkpointId));
19087
19067
  }
19088
19068
  async getGitPath(git, gitPath) {
19089
19069
  const raw = await git.raw(["rev-parse", "--git-path", gitPath]);
@@ -19150,6 +19130,9 @@ var GitHandoffTracker = class {
19150
19130
  });
19151
19131
  }
19152
19132
  };
19133
+ function joinTempPrefix(checkpointId) {
19134
+ return path13.join(tmpdir(), `posthog-code-handoff-${checkpointId}-`);
19135
+ }
19153
19136
  async function getCurrentBranchName(git) {
19154
19137
  try {
19155
19138
  const raw = await git.revparse(["--abbrev-ref", "HEAD"]);
@@ -19236,8 +19219,11 @@ var HandoffCheckpointTracker = class {
19236
19219
  indexArtifactPath: uploads.index?.storagePath
19237
19220
  };
19238
19221
  } finally {
19222
+ const tempDir = capture.headPack?.path ? dirname6(capture.headPack.path) : dirname6(capture.indexFile.path);
19239
19223
  await this.removeIfPresent(capture.headPack?.path);
19240
19224
  await this.removeIfPresent(capture.indexFile.path);
19225
+ await rm4(tempDir, { recursive: true, force: true }).catch(() => {
19226
+ });
19241
19227
  }
19242
19228
  }
19243
19229
  async applyFromHandoff(checkpoint, options) {
@@ -19247,8 +19233,9 @@ var HandoffCheckpointTracker = class {
19247
19233
  );
19248
19234
  }
19249
19235
  const gitTracker = this.createGitTracker();
19250
- const tmpDir = join9(this.repositoryPath, ".posthog", "tmp");
19251
- await mkdir4(tmpDir, { recursive: true });
19236
+ const tmpDir = await mkdtemp2(
19237
+ join9(tmpdir2(), `posthog-code-handoff-${checkpoint.checkpointId}-`)
19238
+ );
19252
19239
  const packPath = join9(tmpDir, `${checkpoint.checkpointId}.pack`);
19253
19240
  const indexPath = join9(tmpDir, `${checkpoint.checkpointId}.index`);
19254
19241
  try {
@@ -19282,7 +19269,8 @@ var HandoffCheckpointTracker = class {
19282
19269
  } finally {
19283
19270
  await this.removeIfPresent(packPath);
19284
19271
  await this.removeIfPresent(indexPath);
19285
- await this.removeTmpDirIfEmpty(tmpDir);
19272
+ await rm4(tmpDir, { recursive: true, force: true }).catch(() => {
19273
+ });
19286
19274
  }
19287
19275
  }
19288
19276
  toGitCheckpoint(checkpoint) {
@@ -19390,50 +19378,24 @@ var HandoffCheckpointTracker = class {
19390
19378
  }
19391
19379
  logCaptureMetrics(checkpoint, uploads) {
19392
19380
  this.logger.info("Captured handoff checkpoint", {
19393
- checkpointId: checkpoint.checkpointId,
19394
19381
  branch: checkpoint.branch,
19395
- head: checkpoint.head,
19396
- artifactPath: uploads.pack?.storagePath,
19397
- indexArtifactPath: uploads.index?.storagePath,
19398
- ...this.buildMetricPayload(uploads)
19382
+ head: checkpoint.head?.slice(0, 7),
19383
+ totalBytes: this.sumRawBytes(uploads.pack, uploads.index)
19399
19384
  });
19400
19385
  }
19401
- logApplyMetrics(checkpoint, downloads, totalBytes) {
19386
+ logApplyMetrics(checkpoint, _downloads, totalBytes) {
19402
19387
  this.logger.info("Applied handoff checkpoint", {
19403
- checkpointId: checkpoint.checkpointId,
19404
- commit: checkpoint.commit,
19405
19388
  branch: checkpoint.branch,
19406
- head: checkpoint.head,
19407
- packBytes: downloads.pack?.rawBytes ?? 0,
19408
- packWireBytes: downloads.pack?.wireBytes ?? 0,
19409
- indexBytes: downloads.index?.rawBytes ?? 0,
19410
- indexWireBytes: downloads.index?.wireBytes ?? 0,
19411
- totalBytes,
19412
- totalWireBytes: this.sumWireBytes(downloads.pack, downloads.index)
19389
+ head: checkpoint.head?.slice(0, 7),
19390
+ totalBytes
19413
19391
  });
19414
19392
  }
19415
- buildMetricPayload(metrics) {
19416
- return {
19417
- packBytes: metrics.pack?.rawBytes ?? 0,
19418
- packWireBytes: metrics.pack?.wireBytes ?? 0,
19419
- indexBytes: metrics.index?.rawBytes ?? 0,
19420
- indexWireBytes: metrics.index?.wireBytes ?? 0,
19421
- totalBytes: this.sumRawBytes(metrics.pack, metrics.index),
19422
- totalWireBytes: this.sumWireBytes(metrics.pack, metrics.index)
19423
- };
19424
- }
19425
19393
  sumRawBytes(...artifacts) {
19426
19394
  return artifacts.reduce(
19427
19395
  (total, artifact) => total + (artifact?.rawBytes ?? 0),
19428
19396
  0
19429
19397
  );
19430
19398
  }
19431
- sumWireBytes(...artifacts) {
19432
- return artifacts.reduce(
19433
- (total, artifact) => total + (artifact?.wireBytes ?? 0),
19434
- 0
19435
- );
19436
- }
19437
19399
  async removeIfPresent(filePath) {
19438
19400
  if (!filePath) {
19439
19401
  return;
@@ -19441,14 +19403,6 @@ var HandoffCheckpointTracker = class {
19441
19403
  await rm4(filePath, { force: true }).catch(() => {
19442
19404
  });
19443
19405
  }
19444
- async removeTmpDirIfEmpty(tmpDir) {
19445
- const entries = await readdir(tmpDir).catch(() => null);
19446
- if (!entries || entries.length > 0) {
19447
- return;
19448
- }
19449
- await rmdir(tmpDir).catch(() => {
19450
- });
19451
- }
19452
19406
  };
19453
19407
 
19454
19408
  // src/utils/gateway.ts
@@ -19721,21 +19675,10 @@ var ResumeSaga = class extends Saga {
19721
19675
  return this.emptyResult();
19722
19676
  }
19723
19677
  this.log.info("Fetched log entries", { count: entries.length });
19724
- const latestSnapshot = await this.readOnlyStep(
19725
- "find_snapshot",
19726
- () => Promise.resolve(this.findLatestTreeSnapshot(entries))
19727
- );
19728
19678
  const latestGitCheckpoint = await this.readOnlyStep(
19729
19679
  "find_git_checkpoint",
19730
19680
  () => Promise.resolve(this.findLatestGitCheckpoint(entries))
19731
19681
  );
19732
- if (latestSnapshot) {
19733
- this.log.info("Found tree snapshot", {
19734
- treeHash: latestSnapshot.treeHash,
19735
- hasArchiveUrl: !!latestSnapshot.archiveUrl,
19736
- changes: latestSnapshot.changes?.length ?? 0
19737
- });
19738
- }
19739
19682
  if (latestGitCheckpoint) {
19740
19683
  this.log.info("Found git checkpoint", {
19741
19684
  checkpointId: latestGitCheckpoint.checkpointId,
@@ -19752,15 +19695,13 @@ var ResumeSaga = class extends Saga {
19752
19695
  );
19753
19696
  this.log.info("Resume state rebuilt", {
19754
19697
  turns: conversation.length,
19755
- hasSnapshot: !!latestSnapshot,
19756
19698
  hasGitCheckpoint: !!latestGitCheckpoint,
19757
- interrupted: latestSnapshot?.interrupted ?? false
19699
+ interrupted: false
19758
19700
  });
19759
19701
  return {
19760
19702
  conversation,
19761
- latestSnapshot,
19762
19703
  latestGitCheckpoint,
19763
- interrupted: latestSnapshot?.interrupted ?? false,
19704
+ interrupted: false,
19764
19705
  lastDevice,
19765
19706
  logEntryCount: entries.length
19766
19707
  };
@@ -19768,27 +19709,11 @@ var ResumeSaga = class extends Saga {
19768
19709
  emptyResult() {
19769
19710
  return {
19770
19711
  conversation: [],
19771
- latestSnapshot: null,
19772
19712
  latestGitCheckpoint: null,
19773
19713
  interrupted: false,
19774
19714
  logEntryCount: 0
19775
19715
  };
19776
19716
  }
19777
- findLatestTreeSnapshot(entries) {
19778
- for (let i2 = entries.length - 1; i2 >= 0; i2--) {
19779
- const entry = entries[i2];
19780
- if (isNotification(
19781
- entry.notification?.method,
19782
- POSTHOG_NOTIFICATIONS.TREE_SNAPSHOT
19783
- )) {
19784
- const params = entry.notification.params;
19785
- if (params?.treeHash) {
19786
- return params;
19787
- }
19788
- }
19789
- }
19790
- return null;
19791
- }
19792
19717
  findLatestGitCheckpoint(entries) {
19793
19718
  const sdkPrefixedMethod = `_${POSTHOG_NOTIFICATIONS.GIT_CHECKPOINT}`;
19794
19719
  for (let i2 = entries.length - 1; i2 >= 0; i2--) {
@@ -19950,7 +19875,6 @@ async function resumeFromLog(config) {
19950
19875
  }
19951
19876
  return {
19952
19877
  conversation: result.data.conversation,
19953
- latestSnapshot: result.data.latestSnapshot,
19954
19878
  latestGitCheckpoint: result.data.latestGitCheckpoint,
19955
19879
  interrupted: result.data.interrupted,
19956
19880
  lastDevice: result.data.lastDevice,
@@ -20338,575 +20262,6 @@ var SessionLogWriter = class _SessionLogWriter {
20338
20262
  }
20339
20263
  };
20340
20264
 
20341
- // src/sagas/apply-snapshot-saga.ts
20342
- import { mkdir as mkdir7, readdir as readdir2, rm as rm6, rmdir as rmdir2, writeFile as writeFile5 } from "fs/promises";
20343
- import { join as join12 } from "path";
20344
-
20345
- // ../git/dist/sagas/tree.js
20346
- import { existsSync as existsSync5 } from "fs";
20347
- import * as fs13 from "fs/promises";
20348
- import * as path16 from "path";
20349
- import * as tar from "tar";
20350
- var CaptureTreeSaga = class extends GitSaga {
20351
- sagaName = "CaptureTreeSaga";
20352
- tempIndexPath = null;
20353
- async executeGitOperations(input) {
20354
- const { baseDir, lastTreeHash, archivePath, signal } = input;
20355
- const tmpDir = path16.join(baseDir, ".git", "posthog-code-tmp");
20356
- await this.step({
20357
- name: "create_tmp_dir",
20358
- execute: () => fs13.mkdir(tmpDir, { recursive: true }),
20359
- rollback: async () => {
20360
- }
20361
- });
20362
- this.tempIndexPath = path16.join(tmpDir, `index-${Date.now()}`);
20363
- const tempIndexGit = this.git.env({
20364
- ...process.env,
20365
- GIT_INDEX_FILE: this.tempIndexPath
20366
- });
20367
- await this.step({
20368
- name: "init_temp_index",
20369
- execute: () => tempIndexGit.raw(["read-tree", "HEAD"]),
20370
- rollback: async () => {
20371
- if (this.tempIndexPath) {
20372
- await fs13.rm(this.tempIndexPath, { force: true }).catch(() => {
20373
- });
20374
- }
20375
- }
20376
- });
20377
- await this.readOnlyStep("stage_files", () => tempIndexGit.raw(["add", "-A"]));
20378
- const treeHash = await this.readOnlyStep("write_tree", () => tempIndexGit.raw(["write-tree"]));
20379
- if (lastTreeHash && treeHash === lastTreeHash) {
20380
- this.log.debug("No changes since last capture", { treeHash });
20381
- await fs13.rm(this.tempIndexPath, { force: true }).catch(() => {
20382
- });
20383
- return { snapshot: null, changed: false };
20384
- }
20385
- const baseCommit = await this.readOnlyStep("get_base_commit", async () => {
20386
- try {
20387
- return await getHeadSha(baseDir, { abortSignal: signal });
20388
- } catch {
20389
- return null;
20390
- }
20391
- });
20392
- const changes = await this.readOnlyStep("get_changes", () => this.getChanges(this.git, baseCommit, treeHash));
20393
- await fs13.rm(this.tempIndexPath, { force: true }).catch(() => {
20394
- });
20395
- const snapshot = {
20396
- treeHash,
20397
- baseCommit,
20398
- changes,
20399
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
20400
- };
20401
- let createdArchivePath;
20402
- if (archivePath) {
20403
- createdArchivePath = await this.createArchive(baseDir, archivePath, changes);
20404
- }
20405
- this.log.info("Tree captured", {
20406
- treeHash,
20407
- changes: changes.length,
20408
- archived: !!createdArchivePath
20409
- });
20410
- return { snapshot, archivePath: createdArchivePath, changed: true };
20411
- }
20412
- async createArchive(baseDir, archivePath, changes) {
20413
- const filesToArchive = changes.filter((c) => c.status !== "D").map((c) => c.path);
20414
- if (filesToArchive.length === 0) {
20415
- return void 0;
20416
- }
20417
- const existingFiles = filesToArchive.filter((f) => existsSync5(path16.join(baseDir, f)));
20418
- if (existingFiles.length === 0) {
20419
- return void 0;
20420
- }
20421
- await this.step({
20422
- name: "create_archive",
20423
- execute: async () => {
20424
- const archiveDir = path16.dirname(archivePath);
20425
- await fs13.mkdir(archiveDir, { recursive: true });
20426
- await tar.create({
20427
- gzip: true,
20428
- file: archivePath,
20429
- cwd: baseDir
20430
- }, existingFiles);
20431
- },
20432
- rollback: async () => {
20433
- await fs13.rm(archivePath, { force: true }).catch(() => {
20434
- });
20435
- }
20436
- });
20437
- return archivePath;
20438
- }
20439
- async getChanges(git, fromRef, toRef) {
20440
- if (!fromRef) {
20441
- const stdout2 = await git.raw(["ls-tree", "-r", "--name-only", toRef]);
20442
- return stdout2.split("\n").filter((p) => p.trim()).map((p) => ({ path: p, status: "A" }));
20443
- }
20444
- const stdout = await git.raw([
20445
- "diff-tree",
20446
- "-r",
20447
- "--name-status",
20448
- fromRef,
20449
- toRef
20450
- ]);
20451
- const changes = [];
20452
- for (const line of stdout.split("\n")) {
20453
- if (!line.trim())
20454
- continue;
20455
- const [status, filePath] = line.split(" ");
20456
- if (!filePath)
20457
- continue;
20458
- let normalizedStatus;
20459
- if (status === "D") {
20460
- normalizedStatus = "D";
20461
- } else if (status === "A") {
20462
- normalizedStatus = "A";
20463
- } else {
20464
- normalizedStatus = "M";
20465
- }
20466
- changes.push({ path: filePath, status: normalizedStatus });
20467
- }
20468
- return changes;
20469
- }
20470
- };
20471
- var ApplyTreeSaga = class extends GitSaga {
20472
- sagaName = "ApplyTreeSaga";
20473
- originalHead = null;
20474
- originalBranch = null;
20475
- extractedFiles = [];
20476
- fileBackups = /* @__PURE__ */ new Map();
20477
- async executeGitOperations(input) {
20478
- const { baseDir, treeHash, baseCommit, changes, archivePath } = input;
20479
- const headInfo = await this.readOnlyStep("get_current_head", async () => {
20480
- let head = null;
20481
- let branch = null;
20482
- try {
20483
- head = await this.git.revparse(["HEAD"]);
20484
- } catch {
20485
- head = null;
20486
- }
20487
- try {
20488
- branch = await this.git.raw(["symbolic-ref", "--short", "HEAD"]);
20489
- } catch {
20490
- branch = null;
20491
- }
20492
- return { head, branch };
20493
- });
20494
- this.originalHead = headInfo.head;
20495
- this.originalBranch = headInfo.branch;
20496
- let checkoutPerformed = false;
20497
- if (baseCommit && baseCommit !== this.originalHead) {
20498
- await this.readOnlyStep("check_working_tree", async () => {
20499
- const status = await this.git.status();
20500
- if (!status.isClean()) {
20501
- const changedFiles = status.modified.length + status.staged.length + status.deleted.length;
20502
- throw new Error(`Cannot apply tree: ${changedFiles} uncommitted change(s) exist. Commit or stash your changes first.`);
20503
- }
20504
- });
20505
- await this.step({
20506
- name: "checkout_base",
20507
- execute: async () => {
20508
- await this.git.checkout(baseCommit);
20509
- checkoutPerformed = true;
20510
- this.log.warn("Applied tree from different commit - now in detached HEAD state", {
20511
- originalHead: this.originalHead,
20512
- originalBranch: this.originalBranch,
20513
- baseCommit
20514
- });
20515
- },
20516
- rollback: async () => {
20517
- try {
20518
- if (this.originalBranch) {
20519
- await this.git.checkout(this.originalBranch);
20520
- } else if (this.originalHead) {
20521
- await this.git.checkout(this.originalHead);
20522
- }
20523
- } catch (error) {
20524
- this.log.warn("Failed to rollback checkout", { error });
20525
- }
20526
- }
20527
- });
20528
- }
20529
- if (archivePath) {
20530
- const filesToExtract = changes.filter((c) => c.status !== "D").map((c) => c.path);
20531
- await this.readOnlyStep("backup_existing_files", async () => {
20532
- for (const filePath of filesToExtract) {
20533
- const fullPath = path16.join(baseDir, filePath);
20534
- try {
20535
- const content = await fs13.readFile(fullPath);
20536
- this.fileBackups.set(filePath, content);
20537
- } catch {
20538
- }
20539
- }
20540
- });
20541
- await this.step({
20542
- name: "extract_archive",
20543
- execute: async () => {
20544
- await tar.extract({
20545
- file: archivePath,
20546
- cwd: baseDir
20547
- });
20548
- this.extractedFiles = filesToExtract;
20549
- },
20550
- rollback: async () => {
20551
- for (const filePath of this.extractedFiles) {
20552
- const fullPath = path16.join(baseDir, filePath);
20553
- const backup = this.fileBackups.get(filePath);
20554
- if (backup) {
20555
- const dir = path16.dirname(fullPath);
20556
- await fs13.mkdir(dir, { recursive: true }).catch(() => {
20557
- });
20558
- await fs13.writeFile(fullPath, backup).catch(() => {
20559
- });
20560
- } else {
20561
- await fs13.rm(fullPath, { force: true }).catch(() => {
20562
- });
20563
- }
20564
- }
20565
- }
20566
- });
20567
- }
20568
- for (const change of changes.filter((c) => c.status === "D")) {
20569
- const fullPath = path16.join(baseDir, change.path);
20570
- const backupContent = await this.readOnlyStep(`backup_${change.path}`, async () => {
20571
- try {
20572
- return await fs13.readFile(fullPath);
20573
- } catch {
20574
- return null;
20575
- }
20576
- });
20577
- await this.step({
20578
- name: `delete_${change.path}`,
20579
- execute: async () => {
20580
- await fs13.rm(fullPath, { force: true });
20581
- this.log.debug(`Deleted file: ${change.path}`);
20582
- },
20583
- rollback: async () => {
20584
- if (backupContent) {
20585
- const dir = path16.dirname(fullPath);
20586
- await fs13.mkdir(dir, { recursive: true }).catch(() => {
20587
- });
20588
- await fs13.writeFile(fullPath, backupContent).catch(() => {
20589
- });
20590
- }
20591
- }
20592
- });
20593
- }
20594
- const deletedCount = changes.filter((c) => c.status === "D").length;
20595
- this.log.info("Tree applied", {
20596
- treeHash,
20597
- totalChanges: changes.length,
20598
- deletedFiles: deletedCount,
20599
- checkoutPerformed
20600
- });
20601
- return { treeHash, checkoutPerformed };
20602
- }
20603
- };
20604
-
20605
- // src/sagas/apply-snapshot-saga.ts
20606
- var ApplySnapshotSaga = class extends Saga {
20607
- sagaName = "ApplySnapshotSaga";
20608
- archivePath = null;
20609
- async execute(input) {
20610
- const { snapshot, repositoryPath, apiClient, taskId, runId } = input;
20611
- const tmpDir = join12(repositoryPath, ".posthog", "tmp");
20612
- if (!snapshot.archiveUrl) {
20613
- throw new Error("Cannot apply snapshot: no archive URL");
20614
- }
20615
- const archiveUrl = snapshot.archiveUrl;
20616
- try {
20617
- await this.step({
20618
- name: "create_tmp_dir",
20619
- execute: () => mkdir7(tmpDir, { recursive: true }),
20620
- rollback: async () => {
20621
- }
20622
- });
20623
- const archivePath = join12(tmpDir, `${snapshot.treeHash}.tar.gz`);
20624
- this.archivePath = archivePath;
20625
- await this.step({
20626
- name: "download_archive",
20627
- execute: async () => {
20628
- const arrayBuffer = await apiClient.downloadArtifact(
20629
- taskId,
20630
- runId,
20631
- archiveUrl
20632
- );
20633
- if (!arrayBuffer) {
20634
- throw new Error("Failed to download archive");
20635
- }
20636
- const base64Content = Buffer.from(arrayBuffer).toString("utf-8");
20637
- const binaryContent = Buffer.from(base64Content, "base64");
20638
- await writeFile5(archivePath, binaryContent);
20639
- this.log.info("Tree archive downloaded", {
20640
- treeHash: snapshot.treeHash,
20641
- snapshotBytes: binaryContent.byteLength,
20642
- snapshotWireBytes: arrayBuffer.byteLength,
20643
- totalBytes: binaryContent.byteLength,
20644
- totalWireBytes: arrayBuffer.byteLength
20645
- });
20646
- },
20647
- rollback: async () => {
20648
- if (this.archivePath) {
20649
- await rm6(this.archivePath, { force: true }).catch(() => {
20650
- });
20651
- }
20652
- }
20653
- });
20654
- const gitApplySaga = new ApplyTreeSaga(this.log);
20655
- const applyResult = await gitApplySaga.run({
20656
- baseDir: repositoryPath,
20657
- treeHash: snapshot.treeHash,
20658
- baseCommit: snapshot.baseCommit,
20659
- changes: snapshot.changes,
20660
- archivePath: this.archivePath
20661
- });
20662
- if (!applyResult.success) {
20663
- throw new Error(`Failed to apply tree: ${applyResult.error}`);
20664
- }
20665
- this.log.info("Tree snapshot applied", {
20666
- treeHash: snapshot.treeHash,
20667
- totalChanges: snapshot.changes.length,
20668
- deletedFiles: snapshot.changes.filter((c) => c.status === "D").length
20669
- });
20670
- return { treeHash: snapshot.treeHash };
20671
- } finally {
20672
- if (this.archivePath) {
20673
- await rm6(this.archivePath, { force: true }).catch(() => {
20674
- });
20675
- }
20676
- await this.removeTmpDirIfEmpty(tmpDir);
20677
- this.archivePath = null;
20678
- }
20679
- }
20680
- async removeTmpDirIfEmpty(tmpDir) {
20681
- const entries = await readdir2(tmpDir).catch(() => null);
20682
- if (!entries || entries.length > 0) {
20683
- return;
20684
- }
20685
- await rmdir2(tmpDir).catch(() => {
20686
- });
20687
- }
20688
- };
20689
-
20690
- // src/sagas/capture-tree-saga.ts
20691
- import { existsSync as existsSync6 } from "fs";
20692
- import { readdir as readdir3, readFile as readFile6, rm as rm7, rmdir as rmdir3 } from "fs/promises";
20693
- import { join as join13 } from "path";
20694
- var CaptureTreeSaga2 = class extends Saga {
20695
- sagaName = "CaptureTreeSaga";
20696
- async execute(input) {
20697
- const {
20698
- repositoryPath,
20699
- lastTreeHash,
20700
- interrupted,
20701
- apiClient,
20702
- taskId,
20703
- runId
20704
- } = input;
20705
- const tmpDir = join13(repositoryPath, ".posthog", "tmp");
20706
- if (existsSync6(join13(repositoryPath, ".gitmodules"))) {
20707
- this.log.warn(
20708
- "Repository has submodules - snapshot may not capture submodule state"
20709
- );
20710
- }
20711
- const shouldArchive = !!apiClient;
20712
- const archivePath = shouldArchive ? join13(tmpDir, `tree-${Date.now()}.tar.gz`) : void 0;
20713
- try {
20714
- const gitCaptureSaga = new CaptureTreeSaga(this.log);
20715
- const captureResult = await gitCaptureSaga.run({
20716
- baseDir: repositoryPath,
20717
- lastTreeHash,
20718
- archivePath
20719
- });
20720
- if (!captureResult.success) {
20721
- throw new Error(`Failed to capture tree: ${captureResult.error}`);
20722
- }
20723
- const {
20724
- snapshot: gitSnapshot,
20725
- archivePath: createdArchivePath,
20726
- changed
20727
- } = captureResult.data;
20728
- if (!changed || !gitSnapshot) {
20729
- this.log.debug("No changes since last capture", { lastTreeHash });
20730
- return { snapshot: null, newTreeHash: lastTreeHash };
20731
- }
20732
- let archiveUrl;
20733
- if (apiClient && createdArchivePath) {
20734
- try {
20735
- archiveUrl = await this.uploadArchive(
20736
- createdArchivePath,
20737
- gitSnapshot.treeHash,
20738
- apiClient,
20739
- taskId,
20740
- runId
20741
- );
20742
- } finally {
20743
- await rm7(createdArchivePath, { force: true }).catch(() => {
20744
- });
20745
- }
20746
- }
20747
- const snapshot = {
20748
- treeHash: gitSnapshot.treeHash,
20749
- baseCommit: gitSnapshot.baseCommit,
20750
- changes: gitSnapshot.changes,
20751
- timestamp: gitSnapshot.timestamp,
20752
- interrupted,
20753
- archiveUrl
20754
- };
20755
- this.log.info("Tree captured", {
20756
- treeHash: snapshot.treeHash,
20757
- changes: snapshot.changes.length,
20758
- interrupted,
20759
- archiveUrl
20760
- });
20761
- return { snapshot, newTreeHash: snapshot.treeHash };
20762
- } finally {
20763
- if (archivePath) {
20764
- await rm7(archivePath, { force: true }).catch(() => {
20765
- });
20766
- }
20767
- await this.removeTmpDirIfEmpty(tmpDir);
20768
- }
20769
- }
20770
- async uploadArchive(archivePath, treeHash, apiClient, taskId, runId) {
20771
- const archiveUrl = await this.step({
20772
- name: "upload_archive",
20773
- execute: async () => {
20774
- const archiveContent = await readFile6(archivePath);
20775
- const base64Content = archiveContent.toString("base64");
20776
- const snapshotBytes = archiveContent.byteLength;
20777
- const snapshotWireBytes = Buffer.byteLength(base64Content, "utf-8");
20778
- const artifacts = await apiClient.uploadTaskArtifacts(taskId, runId, [
20779
- {
20780
- name: `trees/${treeHash}.tar.gz`,
20781
- type: "tree_snapshot",
20782
- content: base64Content,
20783
- content_type: "application/gzip"
20784
- }
20785
- ]);
20786
- const uploadedArtifact = artifacts[0];
20787
- if (uploadedArtifact?.storage_path) {
20788
- this.log.info("Tree archive uploaded", {
20789
- storagePath: uploadedArtifact.storage_path,
20790
- treeHash,
20791
- snapshotBytes,
20792
- snapshotWireBytes,
20793
- totalBytes: snapshotBytes,
20794
- totalWireBytes: snapshotWireBytes
20795
- });
20796
- return uploadedArtifact.storage_path;
20797
- }
20798
- return void 0;
20799
- },
20800
- rollback: async () => {
20801
- await rm7(archivePath, { force: true }).catch(() => {
20802
- });
20803
- }
20804
- });
20805
- return archiveUrl;
20806
- }
20807
- async removeTmpDirIfEmpty(tmpDir) {
20808
- const entries = await readdir3(tmpDir).catch(() => null);
20809
- if (!entries || entries.length > 0) {
20810
- return;
20811
- }
20812
- await rmdir3(tmpDir).catch(() => {
20813
- });
20814
- }
20815
- };
20816
-
20817
- // src/tree-tracker.ts
20818
- var TreeTracker = class {
20819
- repositoryPath;
20820
- taskId;
20821
- runId;
20822
- apiClient;
20823
- logger;
20824
- lastTreeHash = null;
20825
- constructor(config) {
20826
- this.repositoryPath = config.repositoryPath;
20827
- this.taskId = config.taskId;
20828
- this.runId = config.runId;
20829
- this.apiClient = config.apiClient;
20830
- this.logger = config.logger || new Logger({ debug: false, prefix: "[TreeTracker]" });
20831
- }
20832
- /**
20833
- * Capture current working tree state as a snapshot.
20834
- * Uses a temporary index to avoid modifying user's staging area.
20835
- * Uses Saga pattern for atomic operation with automatic cleanup on failure.
20836
- */
20837
- async captureTree(options) {
20838
- const saga = new CaptureTreeSaga2(this.logger);
20839
- const result = await saga.run({
20840
- repositoryPath: this.repositoryPath,
20841
- taskId: this.taskId,
20842
- runId: this.runId,
20843
- apiClient: this.apiClient,
20844
- lastTreeHash: this.lastTreeHash,
20845
- interrupted: options?.interrupted
20846
- });
20847
- if (!result.success) {
20848
- this.logger.error("Failed to capture tree", {
20849
- error: result.error,
20850
- failedStep: result.failedStep
20851
- });
20852
- throw new Error(
20853
- `Failed to capture tree at step '${result.failedStep}': ${result.error}`
20854
- );
20855
- }
20856
- if (result.data.newTreeHash !== null) {
20857
- this.lastTreeHash = result.data.newTreeHash;
20858
- }
20859
- return result.data.snapshot;
20860
- }
20861
- /**
20862
- * Download and apply a tree snapshot.
20863
- * Uses Saga pattern for atomic operation with rollback on failure.
20864
- */
20865
- async applyTreeSnapshot(snapshot) {
20866
- if (!this.apiClient) {
20867
- throw new Error("Cannot apply snapshot: API client not configured");
20868
- }
20869
- if (!snapshot.archiveUrl) {
20870
- this.logger.warn("Cannot apply snapshot: no archive URL", {
20871
- treeHash: snapshot.treeHash,
20872
- changes: snapshot.changes.length
20873
- });
20874
- throw new Error("Cannot apply snapshot: no archive URL");
20875
- }
20876
- const saga = new ApplySnapshotSaga(this.logger);
20877
- const result = await saga.run({
20878
- snapshot,
20879
- repositoryPath: this.repositoryPath,
20880
- apiClient: this.apiClient,
20881
- taskId: this.taskId,
20882
- runId: this.runId
20883
- });
20884
- if (!result.success) {
20885
- this.logger.error("Failed to apply tree snapshot", {
20886
- error: result.error,
20887
- failedStep: result.failedStep,
20888
- treeHash: snapshot.treeHash
20889
- });
20890
- throw new Error(
20891
- `Failed to apply snapshot at step '${result.failedStep}': ${result.error}`
20892
- );
20893
- }
20894
- this.lastTreeHash = result.data.treeHash;
20895
- }
20896
- /**
20897
- * Get the last captured tree hash.
20898
- */
20899
- getLastTreeHash() {
20900
- return this.lastTreeHash;
20901
- }
20902
- /**
20903
- * Set the last tree hash (used when resuming).
20904
- */
20905
- setLastTreeHash(hash) {
20906
- this.lastTreeHash = hash;
20907
- }
20908
- };
20909
-
20910
20265
  // src/server/cloud-prompt.ts
20911
20266
  function normalizeCloudPromptContent(content) {
20912
20267
  if (typeof content === "string") {
@@ -21391,7 +20746,6 @@ var AgentServer = class {
21391
20746
  });
21392
20747
  this.logger.debug("Resume state loaded", {
21393
20748
  conversationTurns: this.resumeState.conversation.length,
21394
- hasSnapshot: !!this.resumeState.latestSnapshot,
21395
20749
  hasGitCheckpoint: !!this.resumeState.latestGitCheckpoint,
21396
20750
  gitCheckpointBranch: this.resumeState.latestGitCheckpoint?.branch ?? null,
21397
20751
  logEntries: this.resumeState.logEntryCount
@@ -21637,13 +20991,6 @@ var AgentServer = class {
21637
20991
  getApiKey: () => this.config.apiKey,
21638
20992
  userAgent: `posthog/cloud.hog.dev; version: ${this.config.version ?? package_default.version}`
21639
20993
  });
21640
- const treeTracker = this.config.repositoryPath ? new TreeTracker({
21641
- repositoryPath: this.config.repositoryPath,
21642
- taskId: payload.task_id,
21643
- runId: payload.run_id,
21644
- apiClient: posthogAPI,
21645
- logger: new Logger({ debug: true, prefix: "[TreeTracker]" })
21646
- }) : null;
21647
20994
  const logWriter = new SessionLogWriter({
21648
20995
  posthogAPI,
21649
20996
  logger: new Logger({ debug: true, prefix: "[SessionLogWriter]" })
@@ -21736,7 +21083,6 @@ var AgentServer = class {
21736
21083
  acpSessionId,
21737
21084
  acpConnection,
21738
21085
  clientConnection,
21739
- treeTracker,
21740
21086
  sseController,
21741
21087
  deviceInfo,
21742
21088
  logWriter,
@@ -21863,31 +21209,7 @@ var AgentServer = class {
21863
21209
  const conversationSummary = formatConversationForResume(
21864
21210
  this.resumeState.conversation
21865
21211
  );
21866
- let snapshotApplied = false;
21867
- if (this.resumeState.latestSnapshot?.archiveUrl && this.config.repositoryPath && this.posthogAPI) {
21868
- try {
21869
- const treeTracker = new TreeTracker({
21870
- repositoryPath: this.config.repositoryPath,
21871
- taskId: payload.task_id,
21872
- runId: payload.run_id,
21873
- apiClient: this.posthogAPI,
21874
- logger: this.logger.child("TreeTracker")
21875
- });
21876
- await treeTracker.applyTreeSnapshot(this.resumeState.latestSnapshot);
21877
- treeTracker.setLastTreeHash(this.resumeState.latestSnapshot.treeHash);
21878
- snapshotApplied = true;
21879
- this.logger.info("Tree snapshot applied", {
21880
- treeHash: this.resumeState.latestSnapshot.treeHash,
21881
- changes: this.resumeState.latestSnapshot.changes?.length ?? 0,
21882
- hasArchiveUrl: !!this.resumeState.latestSnapshot.archiveUrl
21883
- });
21884
- } catch (error) {
21885
- this.logger.warn("Failed to apply tree snapshot", {
21886
- error: error instanceof Error ? error.message : String(error),
21887
- treeHash: this.resumeState.latestSnapshot.treeHash
21888
- });
21889
- }
21890
- }
21212
+ let checkpointApplied = false;
21891
21213
  if (this.resumeState.latestGitCheckpoint && this.config.repositoryPath && this.posthogAPI) {
21892
21214
  try {
21893
21215
  const checkpointTracker = new HandoffCheckpointTracker({
@@ -21900,6 +21222,7 @@ var AgentServer = class {
21900
21222
  const metrics = await checkpointTracker.applyFromHandoff(
21901
21223
  this.resumeState.latestGitCheckpoint
21902
21224
  );
21225
+ checkpointApplied = true;
21903
21226
  this.logger.info("Git checkpoint applied", {
21904
21227
  branch: this.resumeState.latestGitCheckpoint.branch,
21905
21228
  head: this.resumeState.latestGitCheckpoint.head,
@@ -21915,7 +21238,7 @@ var AgentServer = class {
21915
21238
  }
21916
21239
  }
21917
21240
  const pendingUserPrompt = await this.getPendingUserPrompt(taskRun);
21918
- const sandboxContext = snapshotApplied ? `The workspace environment (all files, packages, and code changes) has been fully restored from where you left off.` : `The workspace files from the previous session were not restored (the file snapshot may have expired), so you are starting with a fresh environment. Your conversation history is fully preserved below.`;
21241
+ const sandboxContext = checkpointApplied ? `The workspace environment (all files, packages, and code changes) has been fully restored from the latest checkpoint.` : `The workspace from the previous session was not restored from a checkpoint, so you are starting with a fresh environment. Your conversation history is fully preserved below.`;
21919
21242
  let resumePromptBlocks;
21920
21243
  if (pendingUserPrompt?.length) {
21921
21244
  resumePromptBlocks = [
@@ -21956,7 +21279,7 @@ Continue from where you left off. The user is waiting for your response.`
21956
21279
  conversationTurns: this.resumeState.conversation.length,
21957
21280
  promptLength: promptBlocksToText(resumePromptBlocks).length,
21958
21281
  hasPendingUserMessage: !!pendingUserPrompt?.length,
21959
- snapshotApplied,
21282
+ checkpointApplied,
21960
21283
  hasGitCheckpoint: !!this.resumeState.latestGitCheckpoint,
21961
21284
  gitCheckpointBranch: this.resumeState.latestGitCheckpoint?.branch ?? null
21962
21285
  });
@@ -22102,16 +21425,16 @@ Continue from where you left off. The user is waiting for your response.`
22102
21425
  throw new Error(`Failed to download artifact ${artifact.name}`);
22103
21426
  }
22104
21427
  const safeName = this.getSafeArtifactName(artifact.name);
22105
- const artifactDir = join14(
21428
+ const artifactDir = join11(
22106
21429
  this.config.repositoryPath ?? "/tmp/workspace",
22107
21430
  ".posthog",
22108
21431
  "attachments",
22109
21432
  runId,
22110
21433
  artifact.id ?? safeName
22111
21434
  );
22112
- await mkdir8(artifactDir, { recursive: true });
22113
- const artifactPath = join14(artifactDir, safeName);
22114
- await writeFile6(artifactPath, Buffer.from(data));
21435
+ await mkdir4(artifactDir, { recursive: true });
21436
+ const artifactPath = join11(artifactDir, safeName);
21437
+ await writeFile4(artifactPath, Buffer.from(data));
22115
21438
  return resourceLink(pathToFileURL(artifactPath).toString(), artifact.name, {
22116
21439
  ...artifact.content_type ? { mimeType: artifact.content_type } : {},
22117
21440
  ...typeof artifact.size === "number" ? { size: artifact.size } : {}
@@ -22466,8 +21789,8 @@ ${attributionInstructions}
22466
21789
  const meta = params.update?._meta?.claudeCode;
22467
21790
  const toolName = meta?.toolName;
22468
21791
  const toolResponse = meta?.toolResponse;
22469
- if ((toolName === "Write" || toolName === "Edit") && toolResponse?.filePath) {
22470
- await this.captureTreeState();
21792
+ if ((toolName === "Write" || toolName === "Edit" || toolName === "MultiEdit" || toolName === "Delete" || toolName === "Move") && toolResponse?.filePath) {
21793
+ await this.captureCheckpointState();
22471
21794
  }
22472
21795
  if (toolName && (toolName.includes("Bash") || toolName.includes("bash"))) {
22473
21796
  this.detectAndAttachPrUrl(payload, params.update);
@@ -22629,14 +21952,9 @@ ${attributionInstructions}
22629
21952
  if (!this.session) return;
22630
21953
  this.logger.debug("Cleaning up session");
22631
21954
  try {
22632
- await this.captureHandoffCheckpoint();
22633
- } catch (error) {
22634
- this.logger.error("Failed to capture handoff checkpoint", error);
22635
- }
22636
- try {
22637
- await this.captureTreeState();
21955
+ await this.captureCheckpointState(this.session.pendingHandoffGitState);
22638
21956
  } catch (error) {
22639
- this.logger.error("Failed to capture final tree state", error);
21957
+ this.logger.error("Failed to capture final checkpoint state", error);
22640
21958
  }
22641
21959
  try {
22642
21960
  await this.session.logWriter.flush(this.session.payload.run_id, {
@@ -22664,41 +21982,13 @@ ${attributionInstructions}
22664
21982
  this.lastReportedBranch = null;
22665
21983
  this.session = null;
22666
21984
  }
22667
- async captureTreeState() {
22668
- if (!this.session?.treeTracker) return;
22669
- try {
22670
- const snapshot = await this.session.treeTracker.captureTree({});
22671
- if (snapshot) {
22672
- const snapshotWithDevice = {
22673
- ...snapshot,
22674
- device: this.session.deviceInfo
22675
- };
22676
- const notification = {
22677
- jsonrpc: "2.0",
22678
- method: POSTHOG_NOTIFICATIONS.TREE_SNAPSHOT,
22679
- params: snapshotWithDevice
22680
- };
22681
- this.broadcastEvent({
22682
- type: "notification",
22683
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
22684
- notification
22685
- });
22686
- this.session.logWriter.appendRawLine(
22687
- this.session.payload.run_id,
22688
- JSON.stringify(notification)
22689
- );
22690
- }
22691
- } catch (error) {
22692
- this.logger.error("Failed to capture tree state", error);
22693
- }
22694
- }
22695
- async captureHandoffCheckpoint() {
22696
- if (!this.session?.treeTracker || !this.session.pendingHandoffGitState) {
21985
+ async captureCheckpointState(localGitState) {
21986
+ if (!this.session || !this.config.repositoryPath) {
22697
21987
  return;
22698
21988
  }
22699
21989
  if (!this.posthogAPI) {
22700
21990
  this.logger.warn(
22701
- "Skipping handoff checkpoint capture: PostHog API client is not configured"
21991
+ "Skipping checkpoint capture: PostHog API client is not configured"
22702
21992
  );
22703
21993
  return;
22704
21994
  }
@@ -22709,9 +21999,7 @@ ${attributionInstructions}
22709
21999
  apiClient: this.posthogAPI,
22710
22000
  logger: this.logger.child("HandoffCheckpoint")
22711
22001
  });
22712
- const checkpoint = await tracker.captureForHandoff(
22713
- this.session.pendingHandoffGitState
22714
- );
22002
+ const checkpoint = await tracker.captureForHandoff(localGitState);
22715
22003
  if (!checkpoint) return;
22716
22004
  const checkpointWithDevice = {
22717
22005
  ...checkpoint,