@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
@@ -805,10 +805,10 @@ var require_src2 = __commonJS({
805
805
  var fs_1 = require("fs");
806
806
  var debug_1 = __importDefault(require_src());
807
807
  var log = debug_1.default("@kwsites/file-exists");
808
- function check(path17, isFile2, isDirectory) {
809
- log(`checking %s`, path17);
808
+ function check(path16, isFile2, isDirectory) {
809
+ log(`checking %s`, path16);
810
810
  try {
811
- const stat4 = fs_1.statSync(path17);
811
+ const stat4 = fs_1.statSync(path16);
812
812
  if (stat4.isFile() && isFile2) {
813
813
  log(`[OK] path represents a file`);
814
814
  return true;
@@ -828,8 +828,8 @@ var require_src2 = __commonJS({
828
828
  throw e;
829
829
  }
830
830
  }
831
- function exists2(path17, type = exports2.READABLE) {
832
- return check(path17, (type & exports2.FILE) > 0, (type & exports2.FOLDER) > 0);
831
+ function exists2(path16, type = exports2.READABLE) {
832
+ return check(path16, (type & exports2.FILE) > 0, (type & exports2.FOLDER) > 0);
833
833
  }
834
834
  exports2.exists = exists2;
835
835
  exports2.FILE = 1;
@@ -925,11 +925,11 @@ var require_tree_sitter = __commonJS({
925
925
  throw toThrow;
926
926
  };
927
927
  var scriptDirectory = "";
928
- function locateFile(path17) {
928
+ function locateFile(path16) {
929
929
  if (Module["locateFile"]) {
930
- return Module["locateFile"](path17, scriptDirectory);
930
+ return Module["locateFile"](path16, scriptDirectory);
931
931
  }
932
- return scriptDirectory + path17;
932
+ return scriptDirectory + path16;
933
933
  }
934
934
  var readAsync, readBinary;
935
935
  if (ENVIRONMENT_IS_NODE) {
@@ -3450,8 +3450,8 @@ var require_tree_sitter = __commonJS({
3450
3450
  } else {
3451
3451
  const url = input;
3452
3452
  if (typeof process !== "undefined" && process.versions && process.versions.node) {
3453
- const fs14 = require("fs");
3454
- bytes = Promise.resolve(fs14.readFileSync(url));
3453
+ const fs13 = require("fs");
3454
+ bytes = Promise.resolve(fs13.readFileSync(url));
3455
3455
  } else {
3456
3456
  bytes = fetch(url).then((response) => response.arrayBuffer().then((buffer) => {
3457
3457
  if (response.ok) {
@@ -3912,8 +3912,8 @@ function isSupportedReasoningEffort(adapter, modelId, value) {
3912
3912
  }
3913
3913
 
3914
3914
  // src/server/agent-server.ts
3915
- var import_promises7 = require("fs/promises");
3916
- var import_node_path10 = require("path");
3915
+ var import_promises5 = require("fs/promises");
3916
+ var import_node_path8 = require("path");
3917
3917
  var import_node_url2 = require("url");
3918
3918
  var import_sdk5 = require("@agentclientprotocol/sdk");
3919
3919
  var import_node_server = require("@hono/node-server");
@@ -3959,8 +3959,8 @@ function pathspec(...paths) {
3959
3959
  cache.set(key, paths);
3960
3960
  return key;
3961
3961
  }
3962
- function isPathSpec(path17) {
3963
- return path17 instanceof String && cache.has(path17);
3962
+ function isPathSpec(path16) {
3963
+ return path16 instanceof String && cache.has(path16);
3964
3964
  }
3965
3965
  function toPaths(pathSpec) {
3966
3966
  return cache.get(pathSpec) || [];
@@ -4049,8 +4049,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
4049
4049
  function forEachLineWithContent(input, callback) {
4050
4050
  return toLinesWithContent(input, true).map((line) => callback(line));
4051
4051
  }
4052
- function folderExists(path17) {
4053
- return (0, import_file_exists.exists)(path17, import_file_exists.FOLDER);
4052
+ function folderExists(path16) {
4053
+ return (0, import_file_exists.exists)(path16, import_file_exists.FOLDER);
4054
4054
  }
4055
4055
  function append(target, item) {
4056
4056
  if (Array.isArray(target)) {
@@ -4454,8 +4454,8 @@ function checkIsRepoRootTask() {
4454
4454
  commands,
4455
4455
  format: "utf-8",
4456
4456
  onError,
4457
- parser(path17) {
4458
- return /^\.(git)?$/.test(path17.trim());
4457
+ parser(path16) {
4458
+ return /^\.(git)?$/.test(path16.trim());
4459
4459
  }
4460
4460
  };
4461
4461
  }
@@ -4889,11 +4889,11 @@ function parseGrep(grep) {
4889
4889
  const paths = /* @__PURE__ */ new Set();
4890
4890
  const results = {};
4891
4891
  forEachLineWithContent(grep, (input) => {
4892
- const [path17, line, preview] = input.split(NULL);
4893
- paths.add(path17);
4894
- (results[path17] = results[path17] || []).push({
4892
+ const [path16, line, preview] = input.split(NULL);
4893
+ paths.add(path16);
4894
+ (results[path16] = results[path16] || []).push({
4895
4895
  line: asNumber(line),
4896
- path: path17,
4896
+ path: path16,
4897
4897
  preview
4898
4898
  });
4899
4899
  });
@@ -5658,14 +5658,14 @@ var init_hash_object = __esm({
5658
5658
  init_task();
5659
5659
  }
5660
5660
  });
5661
- function parseInit(bare, path17, text2) {
5661
+ function parseInit(bare, path16, text2) {
5662
5662
  const response = String(text2).trim();
5663
5663
  let result;
5664
5664
  if (result = initResponseRegex.exec(response)) {
5665
- return new InitSummary(bare, path17, false, result[1]);
5665
+ return new InitSummary(bare, path16, false, result[1]);
5666
5666
  }
5667
5667
  if (result = reInitResponseRegex.exec(response)) {
5668
- return new InitSummary(bare, path17, true, result[1]);
5668
+ return new InitSummary(bare, path16, true, result[1]);
5669
5669
  }
5670
5670
  let gitDir = "";
5671
5671
  const tokens = response.split(" ");
@@ -5676,7 +5676,7 @@ function parseInit(bare, path17, text2) {
5676
5676
  break;
5677
5677
  }
5678
5678
  }
5679
- return new InitSummary(bare, path17, /^re/i.test(response), gitDir);
5679
+ return new InitSummary(bare, path16, /^re/i.test(response), gitDir);
5680
5680
  }
5681
5681
  var InitSummary;
5682
5682
  var initResponseRegex;
@@ -5685,9 +5685,9 @@ var init_InitSummary = __esm({
5685
5685
  "src/lib/responses/InitSummary.ts"() {
5686
5686
  "use strict";
5687
5687
  InitSummary = class {
5688
- constructor(bare, path17, existing, gitDir) {
5688
+ constructor(bare, path16, existing, gitDir) {
5689
5689
  this.bare = bare;
5690
- this.path = path17;
5690
+ this.path = path16;
5691
5691
  this.existing = existing;
5692
5692
  this.gitDir = gitDir;
5693
5693
  }
@@ -5699,7 +5699,7 @@ var init_InitSummary = __esm({
5699
5699
  function hasBareCommand(command) {
5700
5700
  return command.includes(bareCommand);
5701
5701
  }
5702
- function initTask(bare = false, path17, customArgs) {
5702
+ function initTask(bare = false, path16, customArgs) {
5703
5703
  const commands = ["init", ...customArgs];
5704
5704
  if (bare && !hasBareCommand(commands)) {
5705
5705
  commands.splice(1, 0, bareCommand);
@@ -5708,7 +5708,7 @@ function initTask(bare = false, path17, customArgs) {
5708
5708
  commands,
5709
5709
  format: "utf-8",
5710
5710
  parser(text2) {
5711
- return parseInit(commands.includes("--bare"), path17, text2);
5711
+ return parseInit(commands.includes("--bare"), path16, text2);
5712
5712
  }
5713
5713
  };
5714
5714
  }
@@ -6524,12 +6524,12 @@ var init_FileStatusSummary = __esm({
6524
6524
  "use strict";
6525
6525
  fromPathRegex = /^(.+)\0(.+)$/;
6526
6526
  FileStatusSummary = class {
6527
- constructor(path17, index, working_dir) {
6528
- this.path = path17;
6527
+ constructor(path16, index, working_dir) {
6528
+ this.path = path16;
6529
6529
  this.index = index;
6530
6530
  this.working_dir = working_dir;
6531
6531
  if (index === "R" || working_dir === "R") {
6532
- const detail = fromPathRegex.exec(path17) || [null, path17, path17];
6532
+ const detail = fromPathRegex.exec(path16) || [null, path16, path16];
6533
6533
  this.from = detail[2] || "";
6534
6534
  this.path = detail[1] || "";
6535
6535
  }
@@ -6560,14 +6560,14 @@ function splitLine(result, lineStr) {
6560
6560
  default:
6561
6561
  return;
6562
6562
  }
6563
- function data(index, workingDir, path17) {
6563
+ function data(index, workingDir, path16) {
6564
6564
  const raw = `${index}${workingDir}`;
6565
6565
  const handler = parsers6.get(raw);
6566
6566
  if (handler) {
6567
- handler(result, path17);
6567
+ handler(result, path16);
6568
6568
  }
6569
6569
  if (raw !== "##" && raw !== "!!") {
6570
- result.files.push(new FileStatusSummary(path17, index, workingDir));
6570
+ result.files.push(new FileStatusSummary(path16, index, workingDir));
6571
6571
  }
6572
6572
  }
6573
6573
  }
@@ -6880,9 +6880,9 @@ var init_simple_git_api = __esm({
6880
6880
  next
6881
6881
  );
6882
6882
  }
6883
- hashObject(path17, write) {
6883
+ hashObject(path16, write) {
6884
6884
  return this._runTask(
6885
- hashObjectTask(path17, write === true),
6885
+ hashObjectTask(path16, write === true),
6886
6886
  trailingFunctionArgument(arguments)
6887
6887
  );
6888
6888
  }
@@ -7235,8 +7235,8 @@ var init_branch = __esm({
7235
7235
  }
7236
7236
  });
7237
7237
  function toPath(input) {
7238
- const path17 = input.trim().replace(/^["']|["']$/g, "");
7239
- return path17 && (0, import_node_path.normalize)(path17);
7238
+ const path16 = input.trim().replace(/^["']|["']$/g, "");
7239
+ return path16 && (0, import_node_path.normalize)(path16);
7240
7240
  }
7241
7241
  var parseCheckIgnore;
7242
7242
  var init_CheckIgnore = __esm({
@@ -7550,8 +7550,8 @@ __export(sub_module_exports, {
7550
7550
  subModuleTask: () => subModuleTask,
7551
7551
  updateSubModuleTask: () => updateSubModuleTask
7552
7552
  });
7553
- function addSubModuleTask(repo, path17) {
7554
- return subModuleTask(["add", repo, path17]);
7553
+ function addSubModuleTask(repo, path16) {
7554
+ return subModuleTask(["add", repo, path16]);
7555
7555
  }
7556
7556
  function initSubModuleTask(customArgs) {
7557
7557
  return subModuleTask(["init", ...customArgs]);
@@ -7881,8 +7881,8 @@ var require_git = __commonJS2({
7881
7881
  }
7882
7882
  return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
7883
7883
  };
7884
- Git2.prototype.submoduleAdd = function(repo, path17, then) {
7885
- return this._runTask(addSubModuleTask2(repo, path17), trailingFunctionArgument2(arguments));
7884
+ Git2.prototype.submoduleAdd = function(repo, path16, then) {
7885
+ return this._runTask(addSubModuleTask2(repo, path16), trailingFunctionArgument2(arguments));
7886
7886
  };
7887
7887
  Git2.prototype.submoduleUpdate = function(args2, then) {
7888
7888
  return this._runTask(
@@ -8715,12 +8715,6 @@ async function listWorktrees(baseDir, options) {
8715
8715
  return worktrees;
8716
8716
  }, { signal: options?.abortSignal });
8717
8717
  }
8718
- async function getHeadSha(baseDir, options) {
8719
- const manager = getGitOperationManager();
8720
- return manager.executeRead(baseDir, (git) => git.revparse(["HEAD"]), {
8721
- signal: options?.abortSignal
8722
- });
8723
- }
8724
8718
 
8725
8719
  // src/server/agent-server.ts
8726
8720
  var import_hono = require("hono");
@@ -8729,7 +8723,7 @@ var import_zod3 = require("zod");
8729
8723
  // package.json
8730
8724
  var package_default = {
8731
8725
  name: "@posthog/agent",
8732
- version: "2.3.401",
8726
+ version: "2.3.403",
8733
8727
  repository: "https://github.com/PostHog/code",
8734
8728
  description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
8735
8729
  exports: {
@@ -8797,10 +8791,6 @@ var package_default = {
8797
8791
  types: "./dist/handoff-checkpoint.d.ts",
8798
8792
  import: "./dist/handoff-checkpoint.js"
8799
8793
  },
8800
- "./tree-tracker": {
8801
- types: "./dist/tree-tracker.d.ts",
8802
- import: "./dist/tree-tracker.js"
8803
- },
8804
8794
  "./server": {
8805
8795
  types: "./dist/server/agent-server.d.ts",
8806
8796
  import: "./dist/server/agent-server.js"
@@ -8896,8 +8886,6 @@ var POSTHOG_NOTIFICATIONS = {
8896
8886
  CONSOLE: "_posthog/console",
8897
8887
  /** Maps taskRunId to agent's sessionId and adapter type (for resumption) */
8898
8888
  SDK_SESSION: "_posthog/sdk_session",
8899
- /** Tree state snapshot captured (git tree hash + file archive) */
8900
- TREE_SNAPSHOT: "_posthog/tree_snapshot",
8901
8889
  /** Git checkpoint captured for handoff */
8902
8890
  GIT_CHECKPOINT: "_posthog/git_checkpoint",
8903
8891
  /** Agent mode changed (interactive/background) */
@@ -8936,9 +8924,6 @@ function matchesExt(method, expected) {
8936
8924
  if (!method) return false;
8937
8925
  return method === expected || method === `_${expected}`;
8938
8926
  }
8939
- function isNotification(method, expected) {
8940
- return matchesExt(method, expected);
8941
- }
8942
8927
  function isMethod(method, expected) {
8943
8928
  return matchesExt(method, expected);
8944
8929
  }
@@ -13569,8 +13554,8 @@ var ToolContentBuilder = class {
13569
13554
  this.items.push({ type: "content", content: image(data, mimeType, uri) });
13570
13555
  return this;
13571
13556
  }
13572
- diff(path17, oldText, newText) {
13573
- this.items.push({ type: "diff", path: path17, oldText, newText });
13557
+ diff(path16, oldText, newText) {
13558
+ this.items.push({ type: "diff", path: path16, oldText, newText });
13574
13559
  return this;
13575
13560
  }
13576
13561
  build() {
@@ -18453,11 +18438,13 @@ function createCodexConnection(config) {
18453
18438
 
18454
18439
  // src/handoff-checkpoint.ts
18455
18440
  var import_promises3 = require("fs/promises");
18441
+ var import_node_os2 = require("os");
18456
18442
  var import_node_path6 = require("path");
18457
18443
 
18458
18444
  // ../git/dist/handoff.js
18459
18445
  var import_node_child_process4 = require("child_process");
18460
18446
  var import_promises2 = require("fs/promises");
18447
+ var import_node_os = require("os");
18461
18448
  var import_node_path5 = __toESM(require("path"), 1);
18462
18449
 
18463
18450
  // ../git/dist/sagas/checkpoint.js
@@ -18865,7 +18852,7 @@ var GitHandoffTracker = class {
18865
18852
  this.repositoryPath = config.repositoryPath;
18866
18853
  this.logger = config.logger;
18867
18854
  }
18868
- async captureForHandoff(localGitState) {
18855
+ async captureForHandoff(_localGitState) {
18869
18856
  const captureSaga = new CaptureCheckpointSaga(this.logger);
18870
18857
  const result = await captureSaga.run({ baseDir: this.repositoryPath });
18871
18858
  if (!result.success) {
@@ -18873,15 +18860,19 @@ var GitHandoffTracker = class {
18873
18860
  }
18874
18861
  const checkpoint = result.data;
18875
18862
  const git = createGitClient(this.repositoryPath);
18876
- const tempDir = await this.getTempDir(git);
18863
+ const tempDir = await this.createTempDir(checkpoint.checkpointId);
18877
18864
  const checkpointRef = `${CHECKPOINT_REF_PREFIX2}${checkpoint.checkpointId}`;
18878
- const shouldIncludeHead = !!checkpoint.head && checkpoint.head !== localGitState?.head;
18879
- const headRef = shouldIncludeHead ? `${HANDOFF_HEAD_REF_PREFIX}${checkpoint.checkpointId}` : void 0;
18865
+ const packRefs = [
18866
+ checkpoint.head,
18867
+ checkpoint.indexTree,
18868
+ checkpoint.worktreeTree
18869
+ ].filter((ref) => !!ref);
18870
+ const headRef = checkpoint.head ? `${HANDOFF_HEAD_REF_PREFIX}${checkpoint.checkpointId}` : void 0;
18880
18871
  const packPrefix = import_node_path5.default.join(tempDir, checkpoint.checkpointId);
18881
18872
  try {
18882
18873
  const [headPack, indexFile, tracking] = await Promise.all([
18883
- shouldIncludeHead && checkpoint.head ? this.captureHeadPack(packPrefix, checkpoint.head) : Promise.resolve(void 0),
18884
- this.copyIndexFile(git, checkpoint.checkpointId),
18874
+ this.captureObjectPack(packPrefix, packRefs),
18875
+ this.copyIndexFile(git, checkpoint.checkpointId, tempDir),
18885
18876
  getTrackingMetadata(git, checkpoint.branch)
18886
18877
  ]);
18887
18878
  return {
@@ -18928,6 +18919,8 @@ var GitHandoffTracker = class {
18928
18919
  } else if (checkpoint.head) {
18929
18920
  await git.checkout(checkpoint.head);
18930
18921
  }
18922
+ await git.clean(["f", "d"]);
18923
+ await git.raw(["read-tree", "--reset", "-u", checkpoint.worktreeTree]);
18931
18924
  if (indexPath) {
18932
18925
  await this.restoreIndexFile(git, indexPath);
18933
18926
  }
@@ -18939,8 +18932,8 @@ var GitHandoffTracker = class {
18939
18932
  totalBytes: packBytes + indexBytes
18940
18933
  };
18941
18934
  }
18942
- async captureHeadPack(packPrefix, headCommit) {
18943
- const hash = await this.runGitWithInput(["pack-objects", packPrefix, "--revs"], `${headCommit}
18935
+ async captureObjectPack(packPrefix, refs) {
18936
+ const hash = await this.runGitWithInput(["pack-objects", packPrefix, "--revs"], `${refs.join("\n")}
18944
18937
  `);
18945
18938
  const packPath = `${packPrefix}-${hash.trim()}.pack`;
18946
18939
  const rawBytes = await this.getFileSize(packPath);
@@ -18948,9 +18941,8 @@ var GitHandoffTracker = class {
18948
18941
  });
18949
18942
  return { path: packPath, rawBytes };
18950
18943
  }
18951
- async copyIndexFile(git, checkpointId) {
18944
+ async copyIndexFile(git, checkpointId, tempDir) {
18952
18945
  const indexPath = await this.getGitPath(git, "index");
18953
- const tempDir = await this.getTempDir(git);
18954
18946
  const copiedIndexPath = import_node_path5.default.join(tempDir, `${checkpointId}.index`);
18955
18947
  await (0, import_promises2.copyFile)(indexPath, copiedIndexPath);
18956
18948
  return {
@@ -19078,13 +19070,8 @@ var GitHandoffTracker = class {
19078
19070
  ]);
19079
19071
  return exitCode === 0;
19080
19072
  }
19081
- async getTempDir(git) {
19082
- const raw = await git.raw(["rev-parse", "--git-common-dir"]);
19083
- const commonDir = raw.trim() || ".git";
19084
- const resolved = import_node_path5.default.isAbsolute(commonDir) ? commonDir : import_node_path5.default.resolve(this.repositoryPath, commonDir);
19085
- const tempDir = import_node_path5.default.join(resolved, "posthog-code-tmp");
19086
- await (0, import_promises2.mkdir)(tempDir, { recursive: true });
19087
- return tempDir;
19073
+ async createTempDir(checkpointId) {
19074
+ return (0, import_promises2.mkdtemp)(joinTempPrefix(checkpointId));
19088
19075
  }
19089
19076
  async getGitPath(git, gitPath) {
19090
19077
  const raw = await git.raw(["rev-parse", "--git-path", gitPath]);
@@ -19151,6 +19138,9 @@ var GitHandoffTracker = class {
19151
19138
  });
19152
19139
  }
19153
19140
  };
19141
+ function joinTempPrefix(checkpointId) {
19142
+ return import_node_path5.default.join((0, import_node_os.tmpdir)(), `posthog-code-handoff-${checkpointId}-`);
19143
+ }
19154
19144
  async function getCurrentBranchName(git) {
19155
19145
  try {
19156
19146
  const raw = await git.revparse(["--abbrev-ref", "HEAD"]);
@@ -19237,8 +19227,11 @@ var HandoffCheckpointTracker = class {
19237
19227
  indexArtifactPath: uploads.index?.storagePath
19238
19228
  };
19239
19229
  } finally {
19230
+ const tempDir = capture.headPack?.path ? (0, import_node_path6.dirname)(capture.headPack.path) : (0, import_node_path6.dirname)(capture.indexFile.path);
19240
19231
  await this.removeIfPresent(capture.headPack?.path);
19241
19232
  await this.removeIfPresent(capture.indexFile.path);
19233
+ await (0, import_promises3.rm)(tempDir, { recursive: true, force: true }).catch(() => {
19234
+ });
19242
19235
  }
19243
19236
  }
19244
19237
  async applyFromHandoff(checkpoint, options) {
@@ -19248,8 +19241,9 @@ var HandoffCheckpointTracker = class {
19248
19241
  );
19249
19242
  }
19250
19243
  const gitTracker = this.createGitTracker();
19251
- const tmpDir = (0, import_node_path6.join)(this.repositoryPath, ".posthog", "tmp");
19252
- await (0, import_promises3.mkdir)(tmpDir, { recursive: true });
19244
+ const tmpDir = await (0, import_promises3.mkdtemp)(
19245
+ (0, import_node_path6.join)((0, import_node_os2.tmpdir)(), `posthog-code-handoff-${checkpoint.checkpointId}-`)
19246
+ );
19253
19247
  const packPath = (0, import_node_path6.join)(tmpDir, `${checkpoint.checkpointId}.pack`);
19254
19248
  const indexPath = (0, import_node_path6.join)(tmpDir, `${checkpoint.checkpointId}.index`);
19255
19249
  try {
@@ -19283,7 +19277,8 @@ var HandoffCheckpointTracker = class {
19283
19277
  } finally {
19284
19278
  await this.removeIfPresent(packPath);
19285
19279
  await this.removeIfPresent(indexPath);
19286
- await this.removeTmpDirIfEmpty(tmpDir);
19280
+ await (0, import_promises3.rm)(tmpDir, { recursive: true, force: true }).catch(() => {
19281
+ });
19287
19282
  }
19288
19283
  }
19289
19284
  toGitCheckpoint(checkpoint) {
@@ -19391,50 +19386,24 @@ var HandoffCheckpointTracker = class {
19391
19386
  }
19392
19387
  logCaptureMetrics(checkpoint, uploads) {
19393
19388
  this.logger.info("Captured handoff checkpoint", {
19394
- checkpointId: checkpoint.checkpointId,
19395
19389
  branch: checkpoint.branch,
19396
- head: checkpoint.head,
19397
- artifactPath: uploads.pack?.storagePath,
19398
- indexArtifactPath: uploads.index?.storagePath,
19399
- ...this.buildMetricPayload(uploads)
19390
+ head: checkpoint.head?.slice(0, 7),
19391
+ totalBytes: this.sumRawBytes(uploads.pack, uploads.index)
19400
19392
  });
19401
19393
  }
19402
- logApplyMetrics(checkpoint, downloads, totalBytes) {
19394
+ logApplyMetrics(checkpoint, _downloads, totalBytes) {
19403
19395
  this.logger.info("Applied handoff checkpoint", {
19404
- checkpointId: checkpoint.checkpointId,
19405
- commit: checkpoint.commit,
19406
19396
  branch: checkpoint.branch,
19407
- head: checkpoint.head,
19408
- packBytes: downloads.pack?.rawBytes ?? 0,
19409
- packWireBytes: downloads.pack?.wireBytes ?? 0,
19410
- indexBytes: downloads.index?.rawBytes ?? 0,
19411
- indexWireBytes: downloads.index?.wireBytes ?? 0,
19412
- totalBytes,
19413
- totalWireBytes: this.sumWireBytes(downloads.pack, downloads.index)
19397
+ head: checkpoint.head?.slice(0, 7),
19398
+ totalBytes
19414
19399
  });
19415
19400
  }
19416
- buildMetricPayload(metrics) {
19417
- return {
19418
- packBytes: metrics.pack?.rawBytes ?? 0,
19419
- packWireBytes: metrics.pack?.wireBytes ?? 0,
19420
- indexBytes: metrics.index?.rawBytes ?? 0,
19421
- indexWireBytes: metrics.index?.wireBytes ?? 0,
19422
- totalBytes: this.sumRawBytes(metrics.pack, metrics.index),
19423
- totalWireBytes: this.sumWireBytes(metrics.pack, metrics.index)
19424
- };
19425
- }
19426
19401
  sumRawBytes(...artifacts) {
19427
19402
  return artifacts.reduce(
19428
19403
  (total, artifact) => total + (artifact?.rawBytes ?? 0),
19429
19404
  0
19430
19405
  );
19431
19406
  }
19432
- sumWireBytes(...artifacts) {
19433
- return artifacts.reduce(
19434
- (total, artifact) => total + (artifact?.wireBytes ?? 0),
19435
- 0
19436
- );
19437
- }
19438
19407
  async removeIfPresent(filePath) {
19439
19408
  if (!filePath) {
19440
19409
  return;
@@ -19442,14 +19411,6 @@ var HandoffCheckpointTracker = class {
19442
19411
  await (0, import_promises3.rm)(filePath, { force: true }).catch(() => {
19443
19412
  });
19444
19413
  }
19445
- async removeTmpDirIfEmpty(tmpDir) {
19446
- const entries = await (0, import_promises3.readdir)(tmpDir).catch(() => null);
19447
- if (!entries || entries.length > 0) {
19448
- return;
19449
- }
19450
- await (0, import_promises3.rmdir)(tmpDir).catch(() => {
19451
- });
19452
- }
19453
19414
  };
19454
19415
 
19455
19416
  // src/utils/gateway.ts
@@ -19722,21 +19683,10 @@ var ResumeSaga = class extends Saga {
19722
19683
  return this.emptyResult();
19723
19684
  }
19724
19685
  this.log.info("Fetched log entries", { count: entries.length });
19725
- const latestSnapshot = await this.readOnlyStep(
19726
- "find_snapshot",
19727
- () => Promise.resolve(this.findLatestTreeSnapshot(entries))
19728
- );
19729
19686
  const latestGitCheckpoint = await this.readOnlyStep(
19730
19687
  "find_git_checkpoint",
19731
19688
  () => Promise.resolve(this.findLatestGitCheckpoint(entries))
19732
19689
  );
19733
- if (latestSnapshot) {
19734
- this.log.info("Found tree snapshot", {
19735
- treeHash: latestSnapshot.treeHash,
19736
- hasArchiveUrl: !!latestSnapshot.archiveUrl,
19737
- changes: latestSnapshot.changes?.length ?? 0
19738
- });
19739
- }
19740
19690
  if (latestGitCheckpoint) {
19741
19691
  this.log.info("Found git checkpoint", {
19742
19692
  checkpointId: latestGitCheckpoint.checkpointId,
@@ -19753,15 +19703,13 @@ var ResumeSaga = class extends Saga {
19753
19703
  );
19754
19704
  this.log.info("Resume state rebuilt", {
19755
19705
  turns: conversation.length,
19756
- hasSnapshot: !!latestSnapshot,
19757
19706
  hasGitCheckpoint: !!latestGitCheckpoint,
19758
- interrupted: latestSnapshot?.interrupted ?? false
19707
+ interrupted: false
19759
19708
  });
19760
19709
  return {
19761
19710
  conversation,
19762
- latestSnapshot,
19763
19711
  latestGitCheckpoint,
19764
- interrupted: latestSnapshot?.interrupted ?? false,
19712
+ interrupted: false,
19765
19713
  lastDevice,
19766
19714
  logEntryCount: entries.length
19767
19715
  };
@@ -19769,27 +19717,11 @@ var ResumeSaga = class extends Saga {
19769
19717
  emptyResult() {
19770
19718
  return {
19771
19719
  conversation: [],
19772
- latestSnapshot: null,
19773
19720
  latestGitCheckpoint: null,
19774
19721
  interrupted: false,
19775
19722
  logEntryCount: 0
19776
19723
  };
19777
19724
  }
19778
- findLatestTreeSnapshot(entries) {
19779
- for (let i2 = entries.length - 1; i2 >= 0; i2--) {
19780
- const entry = entries[i2];
19781
- if (isNotification(
19782
- entry.notification?.method,
19783
- POSTHOG_NOTIFICATIONS.TREE_SNAPSHOT
19784
- )) {
19785
- const params = entry.notification.params;
19786
- if (params?.treeHash) {
19787
- return params;
19788
- }
19789
- }
19790
- }
19791
- return null;
19792
- }
19793
19725
  findLatestGitCheckpoint(entries) {
19794
19726
  const sdkPrefixedMethod = `_${POSTHOG_NOTIFICATIONS.GIT_CHECKPOINT}`;
19795
19727
  for (let i2 = entries.length - 1; i2 >= 0; i2--) {
@@ -19951,7 +19883,6 @@ async function resumeFromLog(config) {
19951
19883
  }
19952
19884
  return {
19953
19885
  conversation: result.data.conversation,
19954
- latestSnapshot: result.data.latestSnapshot,
19955
19886
  latestGitCheckpoint: result.data.latestGitCheckpoint,
19956
19887
  interrupted: result.data.interrupted,
19957
19888
  lastDevice: result.data.lastDevice,
@@ -20339,575 +20270,6 @@ var SessionLogWriter = class _SessionLogWriter {
20339
20270
  }
20340
20271
  };
20341
20272
 
20342
- // src/sagas/apply-snapshot-saga.ts
20343
- var import_promises5 = require("fs/promises");
20344
- var import_node_path8 = require("path");
20345
-
20346
- // ../git/dist/sagas/tree.js
20347
- var import_node_fs4 = require("fs");
20348
- var fs13 = __toESM(require("fs/promises"), 1);
20349
- var path16 = __toESM(require("path"), 1);
20350
- var tar = __toESM(require("tar"), 1);
20351
- var CaptureTreeSaga = class extends GitSaga {
20352
- sagaName = "CaptureTreeSaga";
20353
- tempIndexPath = null;
20354
- async executeGitOperations(input) {
20355
- const { baseDir, lastTreeHash, archivePath, signal } = input;
20356
- const tmpDir = path16.join(baseDir, ".git", "posthog-code-tmp");
20357
- await this.step({
20358
- name: "create_tmp_dir",
20359
- execute: () => fs13.mkdir(tmpDir, { recursive: true }),
20360
- rollback: async () => {
20361
- }
20362
- });
20363
- this.tempIndexPath = path16.join(tmpDir, `index-${Date.now()}`);
20364
- const tempIndexGit = this.git.env({
20365
- ...process.env,
20366
- GIT_INDEX_FILE: this.tempIndexPath
20367
- });
20368
- await this.step({
20369
- name: "init_temp_index",
20370
- execute: () => tempIndexGit.raw(["read-tree", "HEAD"]),
20371
- rollback: async () => {
20372
- if (this.tempIndexPath) {
20373
- await fs13.rm(this.tempIndexPath, { force: true }).catch(() => {
20374
- });
20375
- }
20376
- }
20377
- });
20378
- await this.readOnlyStep("stage_files", () => tempIndexGit.raw(["add", "-A"]));
20379
- const treeHash = await this.readOnlyStep("write_tree", () => tempIndexGit.raw(["write-tree"]));
20380
- if (lastTreeHash && treeHash === lastTreeHash) {
20381
- this.log.debug("No changes since last capture", { treeHash });
20382
- await fs13.rm(this.tempIndexPath, { force: true }).catch(() => {
20383
- });
20384
- return { snapshot: null, changed: false };
20385
- }
20386
- const baseCommit = await this.readOnlyStep("get_base_commit", async () => {
20387
- try {
20388
- return await getHeadSha(baseDir, { abortSignal: signal });
20389
- } catch {
20390
- return null;
20391
- }
20392
- });
20393
- const changes = await this.readOnlyStep("get_changes", () => this.getChanges(this.git, baseCommit, treeHash));
20394
- await fs13.rm(this.tempIndexPath, { force: true }).catch(() => {
20395
- });
20396
- const snapshot = {
20397
- treeHash,
20398
- baseCommit,
20399
- changes,
20400
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
20401
- };
20402
- let createdArchivePath;
20403
- if (archivePath) {
20404
- createdArchivePath = await this.createArchive(baseDir, archivePath, changes);
20405
- }
20406
- this.log.info("Tree captured", {
20407
- treeHash,
20408
- changes: changes.length,
20409
- archived: !!createdArchivePath
20410
- });
20411
- return { snapshot, archivePath: createdArchivePath, changed: true };
20412
- }
20413
- async createArchive(baseDir, archivePath, changes) {
20414
- const filesToArchive = changes.filter((c) => c.status !== "D").map((c) => c.path);
20415
- if (filesToArchive.length === 0) {
20416
- return void 0;
20417
- }
20418
- const existingFiles = filesToArchive.filter((f) => (0, import_node_fs4.existsSync)(path16.join(baseDir, f)));
20419
- if (existingFiles.length === 0) {
20420
- return void 0;
20421
- }
20422
- await this.step({
20423
- name: "create_archive",
20424
- execute: async () => {
20425
- const archiveDir = path16.dirname(archivePath);
20426
- await fs13.mkdir(archiveDir, { recursive: true });
20427
- await tar.create({
20428
- gzip: true,
20429
- file: archivePath,
20430
- cwd: baseDir
20431
- }, existingFiles);
20432
- },
20433
- rollback: async () => {
20434
- await fs13.rm(archivePath, { force: true }).catch(() => {
20435
- });
20436
- }
20437
- });
20438
- return archivePath;
20439
- }
20440
- async getChanges(git, fromRef, toRef) {
20441
- if (!fromRef) {
20442
- const stdout2 = await git.raw(["ls-tree", "-r", "--name-only", toRef]);
20443
- return stdout2.split("\n").filter((p) => p.trim()).map((p) => ({ path: p, status: "A" }));
20444
- }
20445
- const stdout = await git.raw([
20446
- "diff-tree",
20447
- "-r",
20448
- "--name-status",
20449
- fromRef,
20450
- toRef
20451
- ]);
20452
- const changes = [];
20453
- for (const line of stdout.split("\n")) {
20454
- if (!line.trim())
20455
- continue;
20456
- const [status, filePath] = line.split(" ");
20457
- if (!filePath)
20458
- continue;
20459
- let normalizedStatus;
20460
- if (status === "D") {
20461
- normalizedStatus = "D";
20462
- } else if (status === "A") {
20463
- normalizedStatus = "A";
20464
- } else {
20465
- normalizedStatus = "M";
20466
- }
20467
- changes.push({ path: filePath, status: normalizedStatus });
20468
- }
20469
- return changes;
20470
- }
20471
- };
20472
- var ApplyTreeSaga = class extends GitSaga {
20473
- sagaName = "ApplyTreeSaga";
20474
- originalHead = null;
20475
- originalBranch = null;
20476
- extractedFiles = [];
20477
- fileBackups = /* @__PURE__ */ new Map();
20478
- async executeGitOperations(input) {
20479
- const { baseDir, treeHash, baseCommit, changes, archivePath } = input;
20480
- const headInfo = await this.readOnlyStep("get_current_head", async () => {
20481
- let head = null;
20482
- let branch = null;
20483
- try {
20484
- head = await this.git.revparse(["HEAD"]);
20485
- } catch {
20486
- head = null;
20487
- }
20488
- try {
20489
- branch = await this.git.raw(["symbolic-ref", "--short", "HEAD"]);
20490
- } catch {
20491
- branch = null;
20492
- }
20493
- return { head, branch };
20494
- });
20495
- this.originalHead = headInfo.head;
20496
- this.originalBranch = headInfo.branch;
20497
- let checkoutPerformed = false;
20498
- if (baseCommit && baseCommit !== this.originalHead) {
20499
- await this.readOnlyStep("check_working_tree", async () => {
20500
- const status = await this.git.status();
20501
- if (!status.isClean()) {
20502
- const changedFiles = status.modified.length + status.staged.length + status.deleted.length;
20503
- throw new Error(`Cannot apply tree: ${changedFiles} uncommitted change(s) exist. Commit or stash your changes first.`);
20504
- }
20505
- });
20506
- await this.step({
20507
- name: "checkout_base",
20508
- execute: async () => {
20509
- await this.git.checkout(baseCommit);
20510
- checkoutPerformed = true;
20511
- this.log.warn("Applied tree from different commit - now in detached HEAD state", {
20512
- originalHead: this.originalHead,
20513
- originalBranch: this.originalBranch,
20514
- baseCommit
20515
- });
20516
- },
20517
- rollback: async () => {
20518
- try {
20519
- if (this.originalBranch) {
20520
- await this.git.checkout(this.originalBranch);
20521
- } else if (this.originalHead) {
20522
- await this.git.checkout(this.originalHead);
20523
- }
20524
- } catch (error) {
20525
- this.log.warn("Failed to rollback checkout", { error });
20526
- }
20527
- }
20528
- });
20529
- }
20530
- if (archivePath) {
20531
- const filesToExtract = changes.filter((c) => c.status !== "D").map((c) => c.path);
20532
- await this.readOnlyStep("backup_existing_files", async () => {
20533
- for (const filePath of filesToExtract) {
20534
- const fullPath = path16.join(baseDir, filePath);
20535
- try {
20536
- const content = await fs13.readFile(fullPath);
20537
- this.fileBackups.set(filePath, content);
20538
- } catch {
20539
- }
20540
- }
20541
- });
20542
- await this.step({
20543
- name: "extract_archive",
20544
- execute: async () => {
20545
- await tar.extract({
20546
- file: archivePath,
20547
- cwd: baseDir
20548
- });
20549
- this.extractedFiles = filesToExtract;
20550
- },
20551
- rollback: async () => {
20552
- for (const filePath of this.extractedFiles) {
20553
- const fullPath = path16.join(baseDir, filePath);
20554
- const backup = this.fileBackups.get(filePath);
20555
- if (backup) {
20556
- const dir = path16.dirname(fullPath);
20557
- await fs13.mkdir(dir, { recursive: true }).catch(() => {
20558
- });
20559
- await fs13.writeFile(fullPath, backup).catch(() => {
20560
- });
20561
- } else {
20562
- await fs13.rm(fullPath, { force: true }).catch(() => {
20563
- });
20564
- }
20565
- }
20566
- }
20567
- });
20568
- }
20569
- for (const change of changes.filter((c) => c.status === "D")) {
20570
- const fullPath = path16.join(baseDir, change.path);
20571
- const backupContent = await this.readOnlyStep(`backup_${change.path}`, async () => {
20572
- try {
20573
- return await fs13.readFile(fullPath);
20574
- } catch {
20575
- return null;
20576
- }
20577
- });
20578
- await this.step({
20579
- name: `delete_${change.path}`,
20580
- execute: async () => {
20581
- await fs13.rm(fullPath, { force: true });
20582
- this.log.debug(`Deleted file: ${change.path}`);
20583
- },
20584
- rollback: async () => {
20585
- if (backupContent) {
20586
- const dir = path16.dirname(fullPath);
20587
- await fs13.mkdir(dir, { recursive: true }).catch(() => {
20588
- });
20589
- await fs13.writeFile(fullPath, backupContent).catch(() => {
20590
- });
20591
- }
20592
- }
20593
- });
20594
- }
20595
- const deletedCount = changes.filter((c) => c.status === "D").length;
20596
- this.log.info("Tree applied", {
20597
- treeHash,
20598
- totalChanges: changes.length,
20599
- deletedFiles: deletedCount,
20600
- checkoutPerformed
20601
- });
20602
- return { treeHash, checkoutPerformed };
20603
- }
20604
- };
20605
-
20606
- // src/sagas/apply-snapshot-saga.ts
20607
- var ApplySnapshotSaga = class extends Saga {
20608
- sagaName = "ApplySnapshotSaga";
20609
- archivePath = null;
20610
- async execute(input) {
20611
- const { snapshot, repositoryPath, apiClient, taskId, runId } = input;
20612
- const tmpDir = (0, import_node_path8.join)(repositoryPath, ".posthog", "tmp");
20613
- if (!snapshot.archiveUrl) {
20614
- throw new Error("Cannot apply snapshot: no archive URL");
20615
- }
20616
- const archiveUrl = snapshot.archiveUrl;
20617
- try {
20618
- await this.step({
20619
- name: "create_tmp_dir",
20620
- execute: () => (0, import_promises5.mkdir)(tmpDir, { recursive: true }),
20621
- rollback: async () => {
20622
- }
20623
- });
20624
- const archivePath = (0, import_node_path8.join)(tmpDir, `${snapshot.treeHash}.tar.gz`);
20625
- this.archivePath = archivePath;
20626
- await this.step({
20627
- name: "download_archive",
20628
- execute: async () => {
20629
- const arrayBuffer = await apiClient.downloadArtifact(
20630
- taskId,
20631
- runId,
20632
- archiveUrl
20633
- );
20634
- if (!arrayBuffer) {
20635
- throw new Error("Failed to download archive");
20636
- }
20637
- const base64Content = Buffer.from(arrayBuffer).toString("utf-8");
20638
- const binaryContent = Buffer.from(base64Content, "base64");
20639
- await (0, import_promises5.writeFile)(archivePath, binaryContent);
20640
- this.log.info("Tree archive downloaded", {
20641
- treeHash: snapshot.treeHash,
20642
- snapshotBytes: binaryContent.byteLength,
20643
- snapshotWireBytes: arrayBuffer.byteLength,
20644
- totalBytes: binaryContent.byteLength,
20645
- totalWireBytes: arrayBuffer.byteLength
20646
- });
20647
- },
20648
- rollback: async () => {
20649
- if (this.archivePath) {
20650
- await (0, import_promises5.rm)(this.archivePath, { force: true }).catch(() => {
20651
- });
20652
- }
20653
- }
20654
- });
20655
- const gitApplySaga = new ApplyTreeSaga(this.log);
20656
- const applyResult = await gitApplySaga.run({
20657
- baseDir: repositoryPath,
20658
- treeHash: snapshot.treeHash,
20659
- baseCommit: snapshot.baseCommit,
20660
- changes: snapshot.changes,
20661
- archivePath: this.archivePath
20662
- });
20663
- if (!applyResult.success) {
20664
- throw new Error(`Failed to apply tree: ${applyResult.error}`);
20665
- }
20666
- this.log.info("Tree snapshot applied", {
20667
- treeHash: snapshot.treeHash,
20668
- totalChanges: snapshot.changes.length,
20669
- deletedFiles: snapshot.changes.filter((c) => c.status === "D").length
20670
- });
20671
- return { treeHash: snapshot.treeHash };
20672
- } finally {
20673
- if (this.archivePath) {
20674
- await (0, import_promises5.rm)(this.archivePath, { force: true }).catch(() => {
20675
- });
20676
- }
20677
- await this.removeTmpDirIfEmpty(tmpDir);
20678
- this.archivePath = null;
20679
- }
20680
- }
20681
- async removeTmpDirIfEmpty(tmpDir) {
20682
- const entries = await (0, import_promises5.readdir)(tmpDir).catch(() => null);
20683
- if (!entries || entries.length > 0) {
20684
- return;
20685
- }
20686
- await (0, import_promises5.rmdir)(tmpDir).catch(() => {
20687
- });
20688
- }
20689
- };
20690
-
20691
- // src/sagas/capture-tree-saga.ts
20692
- var import_node_fs5 = require("fs");
20693
- var import_promises6 = require("fs/promises");
20694
- var import_node_path9 = require("path");
20695
- var CaptureTreeSaga2 = class extends Saga {
20696
- sagaName = "CaptureTreeSaga";
20697
- async execute(input) {
20698
- const {
20699
- repositoryPath,
20700
- lastTreeHash,
20701
- interrupted,
20702
- apiClient,
20703
- taskId,
20704
- runId
20705
- } = input;
20706
- const tmpDir = (0, import_node_path9.join)(repositoryPath, ".posthog", "tmp");
20707
- if ((0, import_node_fs5.existsSync)((0, import_node_path9.join)(repositoryPath, ".gitmodules"))) {
20708
- this.log.warn(
20709
- "Repository has submodules - snapshot may not capture submodule state"
20710
- );
20711
- }
20712
- const shouldArchive = !!apiClient;
20713
- const archivePath = shouldArchive ? (0, import_node_path9.join)(tmpDir, `tree-${Date.now()}.tar.gz`) : void 0;
20714
- try {
20715
- const gitCaptureSaga = new CaptureTreeSaga(this.log);
20716
- const captureResult = await gitCaptureSaga.run({
20717
- baseDir: repositoryPath,
20718
- lastTreeHash,
20719
- archivePath
20720
- });
20721
- if (!captureResult.success) {
20722
- throw new Error(`Failed to capture tree: ${captureResult.error}`);
20723
- }
20724
- const {
20725
- snapshot: gitSnapshot,
20726
- archivePath: createdArchivePath,
20727
- changed
20728
- } = captureResult.data;
20729
- if (!changed || !gitSnapshot) {
20730
- this.log.debug("No changes since last capture", { lastTreeHash });
20731
- return { snapshot: null, newTreeHash: lastTreeHash };
20732
- }
20733
- let archiveUrl;
20734
- if (apiClient && createdArchivePath) {
20735
- try {
20736
- archiveUrl = await this.uploadArchive(
20737
- createdArchivePath,
20738
- gitSnapshot.treeHash,
20739
- apiClient,
20740
- taskId,
20741
- runId
20742
- );
20743
- } finally {
20744
- await (0, import_promises6.rm)(createdArchivePath, { force: true }).catch(() => {
20745
- });
20746
- }
20747
- }
20748
- const snapshot = {
20749
- treeHash: gitSnapshot.treeHash,
20750
- baseCommit: gitSnapshot.baseCommit,
20751
- changes: gitSnapshot.changes,
20752
- timestamp: gitSnapshot.timestamp,
20753
- interrupted,
20754
- archiveUrl
20755
- };
20756
- this.log.info("Tree captured", {
20757
- treeHash: snapshot.treeHash,
20758
- changes: snapshot.changes.length,
20759
- interrupted,
20760
- archiveUrl
20761
- });
20762
- return { snapshot, newTreeHash: snapshot.treeHash };
20763
- } finally {
20764
- if (archivePath) {
20765
- await (0, import_promises6.rm)(archivePath, { force: true }).catch(() => {
20766
- });
20767
- }
20768
- await this.removeTmpDirIfEmpty(tmpDir);
20769
- }
20770
- }
20771
- async uploadArchive(archivePath, treeHash, apiClient, taskId, runId) {
20772
- const archiveUrl = await this.step({
20773
- name: "upload_archive",
20774
- execute: async () => {
20775
- const archiveContent = await (0, import_promises6.readFile)(archivePath);
20776
- const base64Content = archiveContent.toString("base64");
20777
- const snapshotBytes = archiveContent.byteLength;
20778
- const snapshotWireBytes = Buffer.byteLength(base64Content, "utf-8");
20779
- const artifacts = await apiClient.uploadTaskArtifacts(taskId, runId, [
20780
- {
20781
- name: `trees/${treeHash}.tar.gz`,
20782
- type: "tree_snapshot",
20783
- content: base64Content,
20784
- content_type: "application/gzip"
20785
- }
20786
- ]);
20787
- const uploadedArtifact = artifacts[0];
20788
- if (uploadedArtifact?.storage_path) {
20789
- this.log.info("Tree archive uploaded", {
20790
- storagePath: uploadedArtifact.storage_path,
20791
- treeHash,
20792
- snapshotBytes,
20793
- snapshotWireBytes,
20794
- totalBytes: snapshotBytes,
20795
- totalWireBytes: snapshotWireBytes
20796
- });
20797
- return uploadedArtifact.storage_path;
20798
- }
20799
- return void 0;
20800
- },
20801
- rollback: async () => {
20802
- await (0, import_promises6.rm)(archivePath, { force: true }).catch(() => {
20803
- });
20804
- }
20805
- });
20806
- return archiveUrl;
20807
- }
20808
- async removeTmpDirIfEmpty(tmpDir) {
20809
- const entries = await (0, import_promises6.readdir)(tmpDir).catch(() => null);
20810
- if (!entries || entries.length > 0) {
20811
- return;
20812
- }
20813
- await (0, import_promises6.rmdir)(tmpDir).catch(() => {
20814
- });
20815
- }
20816
- };
20817
-
20818
- // src/tree-tracker.ts
20819
- var TreeTracker = class {
20820
- repositoryPath;
20821
- taskId;
20822
- runId;
20823
- apiClient;
20824
- logger;
20825
- lastTreeHash = null;
20826
- constructor(config) {
20827
- this.repositoryPath = config.repositoryPath;
20828
- this.taskId = config.taskId;
20829
- this.runId = config.runId;
20830
- this.apiClient = config.apiClient;
20831
- this.logger = config.logger || new Logger({ debug: false, prefix: "[TreeTracker]" });
20832
- }
20833
- /**
20834
- * Capture current working tree state as a snapshot.
20835
- * Uses a temporary index to avoid modifying user's staging area.
20836
- * Uses Saga pattern for atomic operation with automatic cleanup on failure.
20837
- */
20838
- async captureTree(options) {
20839
- const saga = new CaptureTreeSaga2(this.logger);
20840
- const result = await saga.run({
20841
- repositoryPath: this.repositoryPath,
20842
- taskId: this.taskId,
20843
- runId: this.runId,
20844
- apiClient: this.apiClient,
20845
- lastTreeHash: this.lastTreeHash,
20846
- interrupted: options?.interrupted
20847
- });
20848
- if (!result.success) {
20849
- this.logger.error("Failed to capture tree", {
20850
- error: result.error,
20851
- failedStep: result.failedStep
20852
- });
20853
- throw new Error(
20854
- `Failed to capture tree at step '${result.failedStep}': ${result.error}`
20855
- );
20856
- }
20857
- if (result.data.newTreeHash !== null) {
20858
- this.lastTreeHash = result.data.newTreeHash;
20859
- }
20860
- return result.data.snapshot;
20861
- }
20862
- /**
20863
- * Download and apply a tree snapshot.
20864
- * Uses Saga pattern for atomic operation with rollback on failure.
20865
- */
20866
- async applyTreeSnapshot(snapshot) {
20867
- if (!this.apiClient) {
20868
- throw new Error("Cannot apply snapshot: API client not configured");
20869
- }
20870
- if (!snapshot.archiveUrl) {
20871
- this.logger.warn("Cannot apply snapshot: no archive URL", {
20872
- treeHash: snapshot.treeHash,
20873
- changes: snapshot.changes.length
20874
- });
20875
- throw new Error("Cannot apply snapshot: no archive URL");
20876
- }
20877
- const saga = new ApplySnapshotSaga(this.logger);
20878
- const result = await saga.run({
20879
- snapshot,
20880
- repositoryPath: this.repositoryPath,
20881
- apiClient: this.apiClient,
20882
- taskId: this.taskId,
20883
- runId: this.runId
20884
- });
20885
- if (!result.success) {
20886
- this.logger.error("Failed to apply tree snapshot", {
20887
- error: result.error,
20888
- failedStep: result.failedStep,
20889
- treeHash: snapshot.treeHash
20890
- });
20891
- throw new Error(
20892
- `Failed to apply snapshot at step '${result.failedStep}': ${result.error}`
20893
- );
20894
- }
20895
- this.lastTreeHash = result.data.treeHash;
20896
- }
20897
- /**
20898
- * Get the last captured tree hash.
20899
- */
20900
- getLastTreeHash() {
20901
- return this.lastTreeHash;
20902
- }
20903
- /**
20904
- * Set the last tree hash (used when resuming).
20905
- */
20906
- setLastTreeHash(hash) {
20907
- this.lastTreeHash = hash;
20908
- }
20909
- };
20910
-
20911
20273
  // src/server/cloud-prompt.ts
20912
20274
  function normalizeCloudPromptContent(content) {
20913
20275
  if (typeof content === "string") {
@@ -21392,7 +20754,6 @@ var AgentServer = class {
21392
20754
  });
21393
20755
  this.logger.debug("Resume state loaded", {
21394
20756
  conversationTurns: this.resumeState.conversation.length,
21395
- hasSnapshot: !!this.resumeState.latestSnapshot,
21396
20757
  hasGitCheckpoint: !!this.resumeState.latestGitCheckpoint,
21397
20758
  gitCheckpointBranch: this.resumeState.latestGitCheckpoint?.branch ?? null,
21398
20759
  logEntries: this.resumeState.logEntryCount
@@ -21638,13 +20999,6 @@ var AgentServer = class {
21638
20999
  getApiKey: () => this.config.apiKey,
21639
21000
  userAgent: `posthog/cloud.hog.dev; version: ${this.config.version ?? package_default.version}`
21640
21001
  });
21641
- const treeTracker = this.config.repositoryPath ? new TreeTracker({
21642
- repositoryPath: this.config.repositoryPath,
21643
- taskId: payload.task_id,
21644
- runId: payload.run_id,
21645
- apiClient: posthogAPI,
21646
- logger: new Logger({ debug: true, prefix: "[TreeTracker]" })
21647
- }) : null;
21648
21002
  const logWriter = new SessionLogWriter({
21649
21003
  posthogAPI,
21650
21004
  logger: new Logger({ debug: true, prefix: "[SessionLogWriter]" })
@@ -21737,7 +21091,6 @@ var AgentServer = class {
21737
21091
  acpSessionId,
21738
21092
  acpConnection,
21739
21093
  clientConnection,
21740
- treeTracker,
21741
21094
  sseController,
21742
21095
  deviceInfo,
21743
21096
  logWriter,
@@ -21864,31 +21217,7 @@ var AgentServer = class {
21864
21217
  const conversationSummary = formatConversationForResume(
21865
21218
  this.resumeState.conversation
21866
21219
  );
21867
- let snapshotApplied = false;
21868
- if (this.resumeState.latestSnapshot?.archiveUrl && this.config.repositoryPath && this.posthogAPI) {
21869
- try {
21870
- const treeTracker = new TreeTracker({
21871
- repositoryPath: this.config.repositoryPath,
21872
- taskId: payload.task_id,
21873
- runId: payload.run_id,
21874
- apiClient: this.posthogAPI,
21875
- logger: this.logger.child("TreeTracker")
21876
- });
21877
- await treeTracker.applyTreeSnapshot(this.resumeState.latestSnapshot);
21878
- treeTracker.setLastTreeHash(this.resumeState.latestSnapshot.treeHash);
21879
- snapshotApplied = true;
21880
- this.logger.info("Tree snapshot applied", {
21881
- treeHash: this.resumeState.latestSnapshot.treeHash,
21882
- changes: this.resumeState.latestSnapshot.changes?.length ?? 0,
21883
- hasArchiveUrl: !!this.resumeState.latestSnapshot.archiveUrl
21884
- });
21885
- } catch (error) {
21886
- this.logger.warn("Failed to apply tree snapshot", {
21887
- error: error instanceof Error ? error.message : String(error),
21888
- treeHash: this.resumeState.latestSnapshot.treeHash
21889
- });
21890
- }
21891
- }
21220
+ let checkpointApplied = false;
21892
21221
  if (this.resumeState.latestGitCheckpoint && this.config.repositoryPath && this.posthogAPI) {
21893
21222
  try {
21894
21223
  const checkpointTracker = new HandoffCheckpointTracker({
@@ -21901,6 +21230,7 @@ var AgentServer = class {
21901
21230
  const metrics = await checkpointTracker.applyFromHandoff(
21902
21231
  this.resumeState.latestGitCheckpoint
21903
21232
  );
21233
+ checkpointApplied = true;
21904
21234
  this.logger.info("Git checkpoint applied", {
21905
21235
  branch: this.resumeState.latestGitCheckpoint.branch,
21906
21236
  head: this.resumeState.latestGitCheckpoint.head,
@@ -21916,7 +21246,7 @@ var AgentServer = class {
21916
21246
  }
21917
21247
  }
21918
21248
  const pendingUserPrompt = await this.getPendingUserPrompt(taskRun);
21919
- 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.`;
21249
+ 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.`;
21920
21250
  let resumePromptBlocks;
21921
21251
  if (pendingUserPrompt?.length) {
21922
21252
  resumePromptBlocks = [
@@ -21957,7 +21287,7 @@ Continue from where you left off. The user is waiting for your response.`
21957
21287
  conversationTurns: this.resumeState.conversation.length,
21958
21288
  promptLength: promptBlocksToText(resumePromptBlocks).length,
21959
21289
  hasPendingUserMessage: !!pendingUserPrompt?.length,
21960
- snapshotApplied,
21290
+ checkpointApplied,
21961
21291
  hasGitCheckpoint: !!this.resumeState.latestGitCheckpoint,
21962
21292
  gitCheckpointBranch: this.resumeState.latestGitCheckpoint?.branch ?? null
21963
21293
  });
@@ -22103,23 +21433,23 @@ Continue from where you left off. The user is waiting for your response.`
22103
21433
  throw new Error(`Failed to download artifact ${artifact.name}`);
22104
21434
  }
22105
21435
  const safeName = this.getSafeArtifactName(artifact.name);
22106
- const artifactDir = (0, import_node_path10.join)(
21436
+ const artifactDir = (0, import_node_path8.join)(
22107
21437
  this.config.repositoryPath ?? "/tmp/workspace",
22108
21438
  ".posthog",
22109
21439
  "attachments",
22110
21440
  runId,
22111
21441
  artifact.id ?? safeName
22112
21442
  );
22113
- await (0, import_promises7.mkdir)(artifactDir, { recursive: true });
22114
- const artifactPath = (0, import_node_path10.join)(artifactDir, safeName);
22115
- await (0, import_promises7.writeFile)(artifactPath, Buffer.from(data));
21443
+ await (0, import_promises5.mkdir)(artifactDir, { recursive: true });
21444
+ const artifactPath = (0, import_node_path8.join)(artifactDir, safeName);
21445
+ await (0, import_promises5.writeFile)(artifactPath, Buffer.from(data));
22116
21446
  return resourceLink((0, import_node_url2.pathToFileURL)(artifactPath).toString(), artifact.name, {
22117
21447
  ...artifact.content_type ? { mimeType: artifact.content_type } : {},
22118
21448
  ...typeof artifact.size === "number" ? { size: artifact.size } : {}
22119
21449
  });
22120
21450
  }
22121
21451
  getSafeArtifactName(name2) {
22122
- const baseName = (0, import_node_path10.basename)(name2).trim();
21452
+ const baseName = (0, import_node_path8.basename)(name2).trim();
22123
21453
  const normalizedName = baseName.replace(/[^\w.-]/g, "_");
22124
21454
  return normalizedName.length > 0 ? normalizedName : "attachment";
22125
21455
  }
@@ -22467,8 +21797,8 @@ ${attributionInstructions}
22467
21797
  const meta = params.update?._meta?.claudeCode;
22468
21798
  const toolName = meta?.toolName;
22469
21799
  const toolResponse = meta?.toolResponse;
22470
- if ((toolName === "Write" || toolName === "Edit") && toolResponse?.filePath) {
22471
- await this.captureTreeState();
21800
+ if ((toolName === "Write" || toolName === "Edit" || toolName === "MultiEdit" || toolName === "Delete" || toolName === "Move") && toolResponse?.filePath) {
21801
+ await this.captureCheckpointState();
22472
21802
  }
22473
21803
  if (toolName && (toolName.includes("Bash") || toolName.includes("bash"))) {
22474
21804
  this.detectAndAttachPrUrl(payload, params.update);
@@ -22630,14 +21960,9 @@ ${attributionInstructions}
22630
21960
  if (!this.session) return;
22631
21961
  this.logger.debug("Cleaning up session");
22632
21962
  try {
22633
- await this.captureHandoffCheckpoint();
22634
- } catch (error) {
22635
- this.logger.error("Failed to capture handoff checkpoint", error);
22636
- }
22637
- try {
22638
- await this.captureTreeState();
21963
+ await this.captureCheckpointState(this.session.pendingHandoffGitState);
22639
21964
  } catch (error) {
22640
- this.logger.error("Failed to capture final tree state", error);
21965
+ this.logger.error("Failed to capture final checkpoint state", error);
22641
21966
  }
22642
21967
  try {
22643
21968
  await this.session.logWriter.flush(this.session.payload.run_id, {
@@ -22665,41 +21990,13 @@ ${attributionInstructions}
22665
21990
  this.lastReportedBranch = null;
22666
21991
  this.session = null;
22667
21992
  }
22668
- async captureTreeState() {
22669
- if (!this.session?.treeTracker) return;
22670
- try {
22671
- const snapshot = await this.session.treeTracker.captureTree({});
22672
- if (snapshot) {
22673
- const snapshotWithDevice = {
22674
- ...snapshot,
22675
- device: this.session.deviceInfo
22676
- };
22677
- const notification = {
22678
- jsonrpc: "2.0",
22679
- method: POSTHOG_NOTIFICATIONS.TREE_SNAPSHOT,
22680
- params: snapshotWithDevice
22681
- };
22682
- this.broadcastEvent({
22683
- type: "notification",
22684
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
22685
- notification
22686
- });
22687
- this.session.logWriter.appendRawLine(
22688
- this.session.payload.run_id,
22689
- JSON.stringify(notification)
22690
- );
22691
- }
22692
- } catch (error) {
22693
- this.logger.error("Failed to capture tree state", error);
22694
- }
22695
- }
22696
- async captureHandoffCheckpoint() {
22697
- if (!this.session?.treeTracker || !this.session.pendingHandoffGitState) {
21993
+ async captureCheckpointState(localGitState) {
21994
+ if (!this.session || !this.config.repositoryPath) {
22698
21995
  return;
22699
21996
  }
22700
21997
  if (!this.posthogAPI) {
22701
21998
  this.logger.warn(
22702
- "Skipping handoff checkpoint capture: PostHog API client is not configured"
21999
+ "Skipping checkpoint capture: PostHog API client is not configured"
22703
22000
  );
22704
22001
  return;
22705
22002
  }
@@ -22710,9 +22007,7 @@ ${attributionInstructions}
22710
22007
  apiClient: this.posthogAPI,
22711
22008
  logger: this.logger.child("HandoffCheckpoint")
22712
22009
  });
22713
- const checkpoint = await tracker.captureForHandoff(
22714
- this.session.pendingHandoffGitState
22715
- );
22010
+ const checkpoint = await tracker.captureForHandoff(localGitState);
22716
22011
  if (!checkpoint) return;
22717
22012
  const checkpointWithDevice = {
22718
22013
  ...checkpoint,