@posthog/agent 2.3.398 → 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 -2
  5. package/dist/handoff-checkpoint.js +38 -53
  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 -768
  17. package/dist/server/agent-server.js.map +1 -1
  18. package/dist/server/bin.cjs +101 -766
  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 +3 -17
  25. package/src/handoff-checkpoint.ts +15 -45
  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 -6431
  35. package/dist/tree-tracker.js.map +0 -1
  36. package/src/sagas/apply-snapshot-saga.test.ts +0 -690
  37. package/src/sagas/apply-snapshot-saga.ts +0 -100
  38. package/src/sagas/capture-tree-saga.test.ts +0 -892
  39. package/src/sagas/capture-tree-saga.ts +0 -150
  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.398",
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,6 +19277,8 @@ var HandoffCheckpointTracker = class {
19283
19277
  } finally {
19284
19278
  await this.removeIfPresent(packPath);
19285
19279
  await this.removeIfPresent(indexPath);
19280
+ await (0, import_promises3.rm)(tmpDir, { recursive: true, force: true }).catch(() => {
19281
+ });
19286
19282
  }
19287
19283
  }
19288
19284
  toGitCheckpoint(checkpoint) {
@@ -19390,50 +19386,24 @@ var HandoffCheckpointTracker = class {
19390
19386
  }
19391
19387
  logCaptureMetrics(checkpoint, uploads) {
19392
19388
  this.logger.info("Captured handoff checkpoint", {
19393
- checkpointId: checkpoint.checkpointId,
19394
19389
  branch: checkpoint.branch,
19395
- head: checkpoint.head,
19396
- artifactPath: uploads.pack?.storagePath,
19397
- indexArtifactPath: uploads.index?.storagePath,
19398
- ...this.buildMetricPayload(uploads)
19390
+ head: checkpoint.head?.slice(0, 7),
19391
+ totalBytes: this.sumRawBytes(uploads.pack, uploads.index)
19399
19392
  });
19400
19393
  }
19401
- logApplyMetrics(checkpoint, downloads, totalBytes) {
19394
+ logApplyMetrics(checkpoint, _downloads, totalBytes) {
19402
19395
  this.logger.info("Applied handoff checkpoint", {
19403
- checkpointId: checkpoint.checkpointId,
19404
- commit: checkpoint.commit,
19405
19396
  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)
19397
+ head: checkpoint.head?.slice(0, 7),
19398
+ totalBytes
19413
19399
  });
19414
19400
  }
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
19401
  sumRawBytes(...artifacts) {
19426
19402
  return artifacts.reduce(
19427
19403
  (total, artifact) => total + (artifact?.rawBytes ?? 0),
19428
19404
  0
19429
19405
  );
19430
19406
  }
19431
- sumWireBytes(...artifacts) {
19432
- return artifacts.reduce(
19433
- (total, artifact) => total + (artifact?.wireBytes ?? 0),
19434
- 0
19435
- );
19436
- }
19437
19407
  async removeIfPresent(filePath) {
19438
19408
  if (!filePath) {
19439
19409
  return;
@@ -19713,21 +19683,10 @@ var ResumeSaga = class extends Saga {
19713
19683
  return this.emptyResult();
19714
19684
  }
19715
19685
  this.log.info("Fetched log entries", { count: entries.length });
19716
- const latestSnapshot = await this.readOnlyStep(
19717
- "find_snapshot",
19718
- () => Promise.resolve(this.findLatestTreeSnapshot(entries))
19719
- );
19720
19686
  const latestGitCheckpoint = await this.readOnlyStep(
19721
19687
  "find_git_checkpoint",
19722
19688
  () => Promise.resolve(this.findLatestGitCheckpoint(entries))
19723
19689
  );
19724
- if (latestSnapshot) {
19725
- this.log.info("Found tree snapshot", {
19726
- treeHash: latestSnapshot.treeHash,
19727
- hasArchiveUrl: !!latestSnapshot.archiveUrl,
19728
- changes: latestSnapshot.changes?.length ?? 0
19729
- });
19730
- }
19731
19690
  if (latestGitCheckpoint) {
19732
19691
  this.log.info("Found git checkpoint", {
19733
19692
  checkpointId: latestGitCheckpoint.checkpointId,
@@ -19744,15 +19703,13 @@ var ResumeSaga = class extends Saga {
19744
19703
  );
19745
19704
  this.log.info("Resume state rebuilt", {
19746
19705
  turns: conversation.length,
19747
- hasSnapshot: !!latestSnapshot,
19748
19706
  hasGitCheckpoint: !!latestGitCheckpoint,
19749
- interrupted: latestSnapshot?.interrupted ?? false
19707
+ interrupted: false
19750
19708
  });
19751
19709
  return {
19752
19710
  conversation,
19753
- latestSnapshot,
19754
19711
  latestGitCheckpoint,
19755
- interrupted: latestSnapshot?.interrupted ?? false,
19712
+ interrupted: false,
19756
19713
  lastDevice,
19757
19714
  logEntryCount: entries.length
19758
19715
  };
@@ -19760,27 +19717,11 @@ var ResumeSaga = class extends Saga {
19760
19717
  emptyResult() {
19761
19718
  return {
19762
19719
  conversation: [],
19763
- latestSnapshot: null,
19764
19720
  latestGitCheckpoint: null,
19765
19721
  interrupted: false,
19766
19722
  logEntryCount: 0
19767
19723
  };
19768
19724
  }
19769
- findLatestTreeSnapshot(entries) {
19770
- for (let i2 = entries.length - 1; i2 >= 0; i2--) {
19771
- const entry = entries[i2];
19772
- if (isNotification(
19773
- entry.notification?.method,
19774
- POSTHOG_NOTIFICATIONS.TREE_SNAPSHOT
19775
- )) {
19776
- const params = entry.notification.params;
19777
- if (params?.treeHash) {
19778
- return params;
19779
- }
19780
- }
19781
- }
19782
- return null;
19783
- }
19784
19725
  findLatestGitCheckpoint(entries) {
19785
19726
  const sdkPrefixedMethod = `_${POSTHOG_NOTIFICATIONS.GIT_CHECKPOINT}`;
19786
19727
  for (let i2 = entries.length - 1; i2 >= 0; i2--) {
@@ -19942,7 +19883,6 @@ async function resumeFromLog(config) {
19942
19883
  }
19943
19884
  return {
19944
19885
  conversation: result.data.conversation,
19945
- latestSnapshot: result.data.latestSnapshot,
19946
19886
  latestGitCheckpoint: result.data.latestGitCheckpoint,
19947
19887
  interrupted: result.data.interrupted,
19948
19888
  lastDevice: result.data.lastDevice,
@@ -20330,544 +20270,6 @@ var SessionLogWriter = class _SessionLogWriter {
20330
20270
  }
20331
20271
  };
20332
20272
 
20333
- // src/sagas/apply-snapshot-saga.ts
20334
- var import_promises5 = require("fs/promises");
20335
- var import_node_path8 = require("path");
20336
-
20337
- // ../git/dist/sagas/tree.js
20338
- var import_node_fs4 = require("fs");
20339
- var fs13 = __toESM(require("fs/promises"), 1);
20340
- var path16 = __toESM(require("path"), 1);
20341
- var tar = __toESM(require("tar"), 1);
20342
- var CaptureTreeSaga = class extends GitSaga {
20343
- sagaName = "CaptureTreeSaga";
20344
- tempIndexPath = null;
20345
- async executeGitOperations(input) {
20346
- const { baseDir, lastTreeHash, archivePath, signal } = input;
20347
- const tmpDir = path16.join(baseDir, ".git", "posthog-code-tmp");
20348
- await this.step({
20349
- name: "create_tmp_dir",
20350
- execute: () => fs13.mkdir(tmpDir, { recursive: true }),
20351
- rollback: async () => {
20352
- }
20353
- });
20354
- this.tempIndexPath = path16.join(tmpDir, `index-${Date.now()}`);
20355
- const tempIndexGit = this.git.env({
20356
- ...process.env,
20357
- GIT_INDEX_FILE: this.tempIndexPath
20358
- });
20359
- await this.step({
20360
- name: "init_temp_index",
20361
- execute: () => tempIndexGit.raw(["read-tree", "HEAD"]),
20362
- rollback: async () => {
20363
- if (this.tempIndexPath) {
20364
- await fs13.rm(this.tempIndexPath, { force: true }).catch(() => {
20365
- });
20366
- }
20367
- }
20368
- });
20369
- await this.readOnlyStep("stage_files", () => tempIndexGit.raw(["add", "-A"]));
20370
- const treeHash = await this.readOnlyStep("write_tree", () => tempIndexGit.raw(["write-tree"]));
20371
- if (lastTreeHash && treeHash === lastTreeHash) {
20372
- this.log.debug("No changes since last capture", { treeHash });
20373
- await fs13.rm(this.tempIndexPath, { force: true }).catch(() => {
20374
- });
20375
- return { snapshot: null, changed: false };
20376
- }
20377
- const baseCommit = await this.readOnlyStep("get_base_commit", async () => {
20378
- try {
20379
- return await getHeadSha(baseDir, { abortSignal: signal });
20380
- } catch {
20381
- return null;
20382
- }
20383
- });
20384
- const changes = await this.readOnlyStep("get_changes", () => this.getChanges(this.git, baseCommit, treeHash));
20385
- await fs13.rm(this.tempIndexPath, { force: true }).catch(() => {
20386
- });
20387
- const snapshot = {
20388
- treeHash,
20389
- baseCommit,
20390
- changes,
20391
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
20392
- };
20393
- let createdArchivePath;
20394
- if (archivePath) {
20395
- createdArchivePath = await this.createArchive(baseDir, archivePath, changes);
20396
- }
20397
- this.log.info("Tree captured", {
20398
- treeHash,
20399
- changes: changes.length,
20400
- archived: !!createdArchivePath
20401
- });
20402
- return { snapshot, archivePath: createdArchivePath, changed: true };
20403
- }
20404
- async createArchive(baseDir, archivePath, changes) {
20405
- const filesToArchive = changes.filter((c) => c.status !== "D").map((c) => c.path);
20406
- if (filesToArchive.length === 0) {
20407
- return void 0;
20408
- }
20409
- const existingFiles = filesToArchive.filter((f) => (0, import_node_fs4.existsSync)(path16.join(baseDir, f)));
20410
- if (existingFiles.length === 0) {
20411
- return void 0;
20412
- }
20413
- await this.step({
20414
- name: "create_archive",
20415
- execute: async () => {
20416
- const archiveDir = path16.dirname(archivePath);
20417
- await fs13.mkdir(archiveDir, { recursive: true });
20418
- await tar.create({
20419
- gzip: true,
20420
- file: archivePath,
20421
- cwd: baseDir
20422
- }, existingFiles);
20423
- },
20424
- rollback: async () => {
20425
- await fs13.rm(archivePath, { force: true }).catch(() => {
20426
- });
20427
- }
20428
- });
20429
- return archivePath;
20430
- }
20431
- async getChanges(git, fromRef, toRef) {
20432
- if (!fromRef) {
20433
- const stdout2 = await git.raw(["ls-tree", "-r", "--name-only", toRef]);
20434
- return stdout2.split("\n").filter((p) => p.trim()).map((p) => ({ path: p, status: "A" }));
20435
- }
20436
- const stdout = await git.raw([
20437
- "diff-tree",
20438
- "-r",
20439
- "--name-status",
20440
- fromRef,
20441
- toRef
20442
- ]);
20443
- const changes = [];
20444
- for (const line of stdout.split("\n")) {
20445
- if (!line.trim())
20446
- continue;
20447
- const [status, filePath] = line.split(" ");
20448
- if (!filePath)
20449
- continue;
20450
- let normalizedStatus;
20451
- if (status === "D") {
20452
- normalizedStatus = "D";
20453
- } else if (status === "A") {
20454
- normalizedStatus = "A";
20455
- } else {
20456
- normalizedStatus = "M";
20457
- }
20458
- changes.push({ path: filePath, status: normalizedStatus });
20459
- }
20460
- return changes;
20461
- }
20462
- };
20463
- var ApplyTreeSaga = class extends GitSaga {
20464
- sagaName = "ApplyTreeSaga";
20465
- originalHead = null;
20466
- originalBranch = null;
20467
- extractedFiles = [];
20468
- fileBackups = /* @__PURE__ */ new Map();
20469
- async executeGitOperations(input) {
20470
- const { baseDir, treeHash, baseCommit, changes, archivePath } = input;
20471
- const headInfo = await this.readOnlyStep("get_current_head", async () => {
20472
- let head = null;
20473
- let branch = null;
20474
- try {
20475
- head = await this.git.revparse(["HEAD"]);
20476
- } catch {
20477
- head = null;
20478
- }
20479
- try {
20480
- branch = await this.git.raw(["symbolic-ref", "--short", "HEAD"]);
20481
- } catch {
20482
- branch = null;
20483
- }
20484
- return { head, branch };
20485
- });
20486
- this.originalHead = headInfo.head;
20487
- this.originalBranch = headInfo.branch;
20488
- let checkoutPerformed = false;
20489
- if (baseCommit && baseCommit !== this.originalHead) {
20490
- await this.readOnlyStep("check_working_tree", async () => {
20491
- const status = await this.git.status();
20492
- if (!status.isClean()) {
20493
- const changedFiles = status.modified.length + status.staged.length + status.deleted.length;
20494
- throw new Error(`Cannot apply tree: ${changedFiles} uncommitted change(s) exist. Commit or stash your changes first.`);
20495
- }
20496
- });
20497
- await this.step({
20498
- name: "checkout_base",
20499
- execute: async () => {
20500
- await this.git.checkout(baseCommit);
20501
- checkoutPerformed = true;
20502
- this.log.warn("Applied tree from different commit - now in detached HEAD state", {
20503
- originalHead: this.originalHead,
20504
- originalBranch: this.originalBranch,
20505
- baseCommit
20506
- });
20507
- },
20508
- rollback: async () => {
20509
- try {
20510
- if (this.originalBranch) {
20511
- await this.git.checkout(this.originalBranch);
20512
- } else if (this.originalHead) {
20513
- await this.git.checkout(this.originalHead);
20514
- }
20515
- } catch (error) {
20516
- this.log.warn("Failed to rollback checkout", { error });
20517
- }
20518
- }
20519
- });
20520
- }
20521
- if (archivePath) {
20522
- const filesToExtract = changes.filter((c) => c.status !== "D").map((c) => c.path);
20523
- await this.readOnlyStep("backup_existing_files", async () => {
20524
- for (const filePath of filesToExtract) {
20525
- const fullPath = path16.join(baseDir, filePath);
20526
- try {
20527
- const content = await fs13.readFile(fullPath);
20528
- this.fileBackups.set(filePath, content);
20529
- } catch {
20530
- }
20531
- }
20532
- });
20533
- await this.step({
20534
- name: "extract_archive",
20535
- execute: async () => {
20536
- await tar.extract({
20537
- file: archivePath,
20538
- cwd: baseDir
20539
- });
20540
- this.extractedFiles = filesToExtract;
20541
- },
20542
- rollback: async () => {
20543
- for (const filePath of this.extractedFiles) {
20544
- const fullPath = path16.join(baseDir, filePath);
20545
- const backup = this.fileBackups.get(filePath);
20546
- if (backup) {
20547
- const dir = path16.dirname(fullPath);
20548
- await fs13.mkdir(dir, { recursive: true }).catch(() => {
20549
- });
20550
- await fs13.writeFile(fullPath, backup).catch(() => {
20551
- });
20552
- } else {
20553
- await fs13.rm(fullPath, { force: true }).catch(() => {
20554
- });
20555
- }
20556
- }
20557
- }
20558
- });
20559
- }
20560
- for (const change of changes.filter((c) => c.status === "D")) {
20561
- const fullPath = path16.join(baseDir, change.path);
20562
- const backupContent = await this.readOnlyStep(`backup_${change.path}`, async () => {
20563
- try {
20564
- return await fs13.readFile(fullPath);
20565
- } catch {
20566
- return null;
20567
- }
20568
- });
20569
- await this.step({
20570
- name: `delete_${change.path}`,
20571
- execute: async () => {
20572
- await fs13.rm(fullPath, { force: true });
20573
- this.log.debug(`Deleted file: ${change.path}`);
20574
- },
20575
- rollback: async () => {
20576
- if (backupContent) {
20577
- const dir = path16.dirname(fullPath);
20578
- await fs13.mkdir(dir, { recursive: true }).catch(() => {
20579
- });
20580
- await fs13.writeFile(fullPath, backupContent).catch(() => {
20581
- });
20582
- }
20583
- }
20584
- });
20585
- }
20586
- const deletedCount = changes.filter((c) => c.status === "D").length;
20587
- this.log.info("Tree applied", {
20588
- treeHash,
20589
- totalChanges: changes.length,
20590
- deletedFiles: deletedCount,
20591
- checkoutPerformed
20592
- });
20593
- return { treeHash, checkoutPerformed };
20594
- }
20595
- };
20596
-
20597
- // src/sagas/apply-snapshot-saga.ts
20598
- var ApplySnapshotSaga = class extends Saga {
20599
- sagaName = "ApplySnapshotSaga";
20600
- archivePath = null;
20601
- async execute(input) {
20602
- const { snapshot, repositoryPath, apiClient, taskId, runId } = input;
20603
- const tmpDir = (0, import_node_path8.join)(repositoryPath, ".posthog", "tmp");
20604
- if (!snapshot.archiveUrl) {
20605
- throw new Error("Cannot apply snapshot: no archive URL");
20606
- }
20607
- const archiveUrl = snapshot.archiveUrl;
20608
- await this.step({
20609
- name: "create_tmp_dir",
20610
- execute: () => (0, import_promises5.mkdir)(tmpDir, { recursive: true }),
20611
- rollback: async () => {
20612
- }
20613
- });
20614
- const archivePath = (0, import_node_path8.join)(tmpDir, `${snapshot.treeHash}.tar.gz`);
20615
- this.archivePath = archivePath;
20616
- await this.step({
20617
- name: "download_archive",
20618
- execute: async () => {
20619
- const arrayBuffer = await apiClient.downloadArtifact(
20620
- taskId,
20621
- runId,
20622
- archiveUrl
20623
- );
20624
- if (!arrayBuffer) {
20625
- throw new Error("Failed to download archive");
20626
- }
20627
- const base64Content = Buffer.from(arrayBuffer).toString("utf-8");
20628
- const binaryContent = Buffer.from(base64Content, "base64");
20629
- await (0, import_promises5.writeFile)(archivePath, binaryContent);
20630
- this.log.info("Tree archive downloaded", {
20631
- treeHash: snapshot.treeHash,
20632
- snapshotBytes: binaryContent.byteLength,
20633
- snapshotWireBytes: arrayBuffer.byteLength,
20634
- totalBytes: binaryContent.byteLength,
20635
- totalWireBytes: arrayBuffer.byteLength
20636
- });
20637
- },
20638
- rollback: async () => {
20639
- if (this.archivePath) {
20640
- await (0, import_promises5.rm)(this.archivePath, { force: true }).catch(() => {
20641
- });
20642
- }
20643
- }
20644
- });
20645
- const gitApplySaga = new ApplyTreeSaga(this.log);
20646
- const applyResult = await gitApplySaga.run({
20647
- baseDir: repositoryPath,
20648
- treeHash: snapshot.treeHash,
20649
- baseCommit: snapshot.baseCommit,
20650
- changes: snapshot.changes,
20651
- archivePath: this.archivePath
20652
- });
20653
- if (!applyResult.success) {
20654
- throw new Error(`Failed to apply tree: ${applyResult.error}`);
20655
- }
20656
- await (0, import_promises5.rm)(this.archivePath, { force: true }).catch(() => {
20657
- });
20658
- this.log.info("Tree snapshot applied", {
20659
- treeHash: snapshot.treeHash,
20660
- totalChanges: snapshot.changes.length,
20661
- deletedFiles: snapshot.changes.filter((c) => c.status === "D").length
20662
- });
20663
- return { treeHash: snapshot.treeHash };
20664
- }
20665
- };
20666
-
20667
- // src/sagas/capture-tree-saga.ts
20668
- var import_node_fs5 = require("fs");
20669
- var import_promises6 = require("fs/promises");
20670
- var import_node_path9 = require("path");
20671
- var CaptureTreeSaga2 = class extends Saga {
20672
- sagaName = "CaptureTreeSaga";
20673
- async execute(input) {
20674
- const {
20675
- repositoryPath,
20676
- lastTreeHash,
20677
- interrupted,
20678
- apiClient,
20679
- taskId,
20680
- runId
20681
- } = input;
20682
- const tmpDir = (0, import_node_path9.join)(repositoryPath, ".posthog", "tmp");
20683
- if ((0, import_node_fs5.existsSync)((0, import_node_path9.join)(repositoryPath, ".gitmodules"))) {
20684
- this.log.warn(
20685
- "Repository has submodules - snapshot may not capture submodule state"
20686
- );
20687
- }
20688
- const shouldArchive = !!apiClient;
20689
- const archivePath = shouldArchive ? (0, import_node_path9.join)(tmpDir, `tree-${Date.now()}.tar.gz`) : void 0;
20690
- const gitCaptureSaga = new CaptureTreeSaga(this.log);
20691
- const captureResult = await gitCaptureSaga.run({
20692
- baseDir: repositoryPath,
20693
- lastTreeHash,
20694
- archivePath
20695
- });
20696
- if (!captureResult.success) {
20697
- throw new Error(`Failed to capture tree: ${captureResult.error}`);
20698
- }
20699
- const {
20700
- snapshot: gitSnapshot,
20701
- archivePath: createdArchivePath,
20702
- changed
20703
- } = captureResult.data;
20704
- if (!changed || !gitSnapshot) {
20705
- this.log.debug("No changes since last capture", { lastTreeHash });
20706
- return { snapshot: null, newTreeHash: lastTreeHash };
20707
- }
20708
- let archiveUrl;
20709
- if (apiClient && createdArchivePath) {
20710
- try {
20711
- archiveUrl = await this.uploadArchive(
20712
- createdArchivePath,
20713
- gitSnapshot.treeHash,
20714
- apiClient,
20715
- taskId,
20716
- runId
20717
- );
20718
- } finally {
20719
- await (0, import_promises6.rm)(createdArchivePath, { force: true }).catch(() => {
20720
- });
20721
- }
20722
- }
20723
- const snapshot = {
20724
- treeHash: gitSnapshot.treeHash,
20725
- baseCommit: gitSnapshot.baseCommit,
20726
- changes: gitSnapshot.changes,
20727
- timestamp: gitSnapshot.timestamp,
20728
- interrupted,
20729
- archiveUrl
20730
- };
20731
- this.log.info("Tree captured", {
20732
- treeHash: snapshot.treeHash,
20733
- changes: snapshot.changes.length,
20734
- interrupted,
20735
- archiveUrl
20736
- });
20737
- return { snapshot, newTreeHash: snapshot.treeHash };
20738
- }
20739
- async uploadArchive(archivePath, treeHash, apiClient, taskId, runId) {
20740
- const archiveUrl = await this.step({
20741
- name: "upload_archive",
20742
- execute: async () => {
20743
- const archiveContent = await (0, import_promises6.readFile)(archivePath);
20744
- const base64Content = archiveContent.toString("base64");
20745
- const snapshotBytes = archiveContent.byteLength;
20746
- const snapshotWireBytes = Buffer.byteLength(base64Content, "utf-8");
20747
- const artifacts = await apiClient.uploadTaskArtifacts(taskId, runId, [
20748
- {
20749
- name: `trees/${treeHash}.tar.gz`,
20750
- type: "tree_snapshot",
20751
- content: base64Content,
20752
- content_type: "application/gzip"
20753
- }
20754
- ]);
20755
- const uploadedArtifact = artifacts[0];
20756
- if (uploadedArtifact?.storage_path) {
20757
- this.log.info("Tree archive uploaded", {
20758
- storagePath: uploadedArtifact.storage_path,
20759
- treeHash,
20760
- snapshotBytes,
20761
- snapshotWireBytes,
20762
- totalBytes: snapshotBytes,
20763
- totalWireBytes: snapshotWireBytes
20764
- });
20765
- return uploadedArtifact.storage_path;
20766
- }
20767
- return void 0;
20768
- },
20769
- rollback: async () => {
20770
- await (0, import_promises6.rm)(archivePath, { force: true }).catch(() => {
20771
- });
20772
- }
20773
- });
20774
- return archiveUrl;
20775
- }
20776
- };
20777
-
20778
- // src/tree-tracker.ts
20779
- var TreeTracker = class {
20780
- repositoryPath;
20781
- taskId;
20782
- runId;
20783
- apiClient;
20784
- logger;
20785
- lastTreeHash = null;
20786
- constructor(config) {
20787
- this.repositoryPath = config.repositoryPath;
20788
- this.taskId = config.taskId;
20789
- this.runId = config.runId;
20790
- this.apiClient = config.apiClient;
20791
- this.logger = config.logger || new Logger({ debug: false, prefix: "[TreeTracker]" });
20792
- }
20793
- /**
20794
- * Capture current working tree state as a snapshot.
20795
- * Uses a temporary index to avoid modifying user's staging area.
20796
- * Uses Saga pattern for atomic operation with automatic cleanup on failure.
20797
- */
20798
- async captureTree(options) {
20799
- const saga = new CaptureTreeSaga2(this.logger);
20800
- const result = await saga.run({
20801
- repositoryPath: this.repositoryPath,
20802
- taskId: this.taskId,
20803
- runId: this.runId,
20804
- apiClient: this.apiClient,
20805
- lastTreeHash: this.lastTreeHash,
20806
- interrupted: options?.interrupted
20807
- });
20808
- if (!result.success) {
20809
- this.logger.error("Failed to capture tree", {
20810
- error: result.error,
20811
- failedStep: result.failedStep
20812
- });
20813
- throw new Error(
20814
- `Failed to capture tree at step '${result.failedStep}': ${result.error}`
20815
- );
20816
- }
20817
- if (result.data.newTreeHash !== null) {
20818
- this.lastTreeHash = result.data.newTreeHash;
20819
- }
20820
- return result.data.snapshot;
20821
- }
20822
- /**
20823
- * Download and apply a tree snapshot.
20824
- * Uses Saga pattern for atomic operation with rollback on failure.
20825
- */
20826
- async applyTreeSnapshot(snapshot) {
20827
- if (!this.apiClient) {
20828
- throw new Error("Cannot apply snapshot: API client not configured");
20829
- }
20830
- if (!snapshot.archiveUrl) {
20831
- this.logger.warn("Cannot apply snapshot: no archive URL", {
20832
- treeHash: snapshot.treeHash,
20833
- changes: snapshot.changes.length
20834
- });
20835
- throw new Error("Cannot apply snapshot: no archive URL");
20836
- }
20837
- const saga = new ApplySnapshotSaga(this.logger);
20838
- const result = await saga.run({
20839
- snapshot,
20840
- repositoryPath: this.repositoryPath,
20841
- apiClient: this.apiClient,
20842
- taskId: this.taskId,
20843
- runId: this.runId
20844
- });
20845
- if (!result.success) {
20846
- this.logger.error("Failed to apply tree snapshot", {
20847
- error: result.error,
20848
- failedStep: result.failedStep,
20849
- treeHash: snapshot.treeHash
20850
- });
20851
- throw new Error(
20852
- `Failed to apply snapshot at step '${result.failedStep}': ${result.error}`
20853
- );
20854
- }
20855
- this.lastTreeHash = result.data.treeHash;
20856
- }
20857
- /**
20858
- * Get the last captured tree hash.
20859
- */
20860
- getLastTreeHash() {
20861
- return this.lastTreeHash;
20862
- }
20863
- /**
20864
- * Set the last tree hash (used when resuming).
20865
- */
20866
- setLastTreeHash(hash) {
20867
- this.lastTreeHash = hash;
20868
- }
20869
- };
20870
-
20871
20273
  // src/server/cloud-prompt.ts
20872
20274
  function normalizeCloudPromptContent(content) {
20873
20275
  if (typeof content === "string") {
@@ -21352,7 +20754,6 @@ var AgentServer = class {
21352
20754
  });
21353
20755
  this.logger.debug("Resume state loaded", {
21354
20756
  conversationTurns: this.resumeState.conversation.length,
21355
- hasSnapshot: !!this.resumeState.latestSnapshot,
21356
20757
  hasGitCheckpoint: !!this.resumeState.latestGitCheckpoint,
21357
20758
  gitCheckpointBranch: this.resumeState.latestGitCheckpoint?.branch ?? null,
21358
20759
  logEntries: this.resumeState.logEntryCount
@@ -21598,13 +20999,6 @@ var AgentServer = class {
21598
20999
  getApiKey: () => this.config.apiKey,
21599
21000
  userAgent: `posthog/cloud.hog.dev; version: ${this.config.version ?? package_default.version}`
21600
21001
  });
21601
- const treeTracker = this.config.repositoryPath ? new TreeTracker({
21602
- repositoryPath: this.config.repositoryPath,
21603
- taskId: payload.task_id,
21604
- runId: payload.run_id,
21605
- apiClient: posthogAPI,
21606
- logger: new Logger({ debug: true, prefix: "[TreeTracker]" })
21607
- }) : null;
21608
21002
  const logWriter = new SessionLogWriter({
21609
21003
  posthogAPI,
21610
21004
  logger: new Logger({ debug: true, prefix: "[SessionLogWriter]" })
@@ -21697,7 +21091,6 @@ var AgentServer = class {
21697
21091
  acpSessionId,
21698
21092
  acpConnection,
21699
21093
  clientConnection,
21700
- treeTracker,
21701
21094
  sseController,
21702
21095
  deviceInfo,
21703
21096
  logWriter,
@@ -21824,31 +21217,7 @@ var AgentServer = class {
21824
21217
  const conversationSummary = formatConversationForResume(
21825
21218
  this.resumeState.conversation
21826
21219
  );
21827
- let snapshotApplied = false;
21828
- if (this.resumeState.latestSnapshot?.archiveUrl && this.config.repositoryPath && this.posthogAPI) {
21829
- try {
21830
- const treeTracker = new TreeTracker({
21831
- repositoryPath: this.config.repositoryPath,
21832
- taskId: payload.task_id,
21833
- runId: payload.run_id,
21834
- apiClient: this.posthogAPI,
21835
- logger: this.logger.child("TreeTracker")
21836
- });
21837
- await treeTracker.applyTreeSnapshot(this.resumeState.latestSnapshot);
21838
- treeTracker.setLastTreeHash(this.resumeState.latestSnapshot.treeHash);
21839
- snapshotApplied = true;
21840
- this.logger.info("Tree snapshot applied", {
21841
- treeHash: this.resumeState.latestSnapshot.treeHash,
21842
- changes: this.resumeState.latestSnapshot.changes?.length ?? 0,
21843
- hasArchiveUrl: !!this.resumeState.latestSnapshot.archiveUrl
21844
- });
21845
- } catch (error) {
21846
- this.logger.warn("Failed to apply tree snapshot", {
21847
- error: error instanceof Error ? error.message : String(error),
21848
- treeHash: this.resumeState.latestSnapshot.treeHash
21849
- });
21850
- }
21851
- }
21220
+ let checkpointApplied = false;
21852
21221
  if (this.resumeState.latestGitCheckpoint && this.config.repositoryPath && this.posthogAPI) {
21853
21222
  try {
21854
21223
  const checkpointTracker = new HandoffCheckpointTracker({
@@ -21861,6 +21230,7 @@ var AgentServer = class {
21861
21230
  const metrics = await checkpointTracker.applyFromHandoff(
21862
21231
  this.resumeState.latestGitCheckpoint
21863
21232
  );
21233
+ checkpointApplied = true;
21864
21234
  this.logger.info("Git checkpoint applied", {
21865
21235
  branch: this.resumeState.latestGitCheckpoint.branch,
21866
21236
  head: this.resumeState.latestGitCheckpoint.head,
@@ -21876,7 +21246,7 @@ var AgentServer = class {
21876
21246
  }
21877
21247
  }
21878
21248
  const pendingUserPrompt = await this.getPendingUserPrompt(taskRun);
21879
- 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.`;
21880
21250
  let resumePromptBlocks;
21881
21251
  if (pendingUserPrompt?.length) {
21882
21252
  resumePromptBlocks = [
@@ -21917,7 +21287,7 @@ Continue from where you left off. The user is waiting for your response.`
21917
21287
  conversationTurns: this.resumeState.conversation.length,
21918
21288
  promptLength: promptBlocksToText(resumePromptBlocks).length,
21919
21289
  hasPendingUserMessage: !!pendingUserPrompt?.length,
21920
- snapshotApplied,
21290
+ checkpointApplied,
21921
21291
  hasGitCheckpoint: !!this.resumeState.latestGitCheckpoint,
21922
21292
  gitCheckpointBranch: this.resumeState.latestGitCheckpoint?.branch ?? null
21923
21293
  });
@@ -22063,23 +21433,23 @@ Continue from where you left off. The user is waiting for your response.`
22063
21433
  throw new Error(`Failed to download artifact ${artifact.name}`);
22064
21434
  }
22065
21435
  const safeName = this.getSafeArtifactName(artifact.name);
22066
- const artifactDir = (0, import_node_path10.join)(
21436
+ const artifactDir = (0, import_node_path8.join)(
22067
21437
  this.config.repositoryPath ?? "/tmp/workspace",
22068
21438
  ".posthog",
22069
21439
  "attachments",
22070
21440
  runId,
22071
21441
  artifact.id ?? safeName
22072
21442
  );
22073
- await (0, import_promises7.mkdir)(artifactDir, { recursive: true });
22074
- const artifactPath = (0, import_node_path10.join)(artifactDir, safeName);
22075
- 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));
22076
21446
  return resourceLink((0, import_node_url2.pathToFileURL)(artifactPath).toString(), artifact.name, {
22077
21447
  ...artifact.content_type ? { mimeType: artifact.content_type } : {},
22078
21448
  ...typeof artifact.size === "number" ? { size: artifact.size } : {}
22079
21449
  });
22080
21450
  }
22081
21451
  getSafeArtifactName(name2) {
22082
- const baseName = (0, import_node_path10.basename)(name2).trim();
21452
+ const baseName = (0, import_node_path8.basename)(name2).trim();
22083
21453
  const normalizedName = baseName.replace(/[^\w.-]/g, "_");
22084
21454
  return normalizedName.length > 0 ? normalizedName : "attachment";
22085
21455
  }
@@ -22427,8 +21797,8 @@ ${attributionInstructions}
22427
21797
  const meta = params.update?._meta?.claudeCode;
22428
21798
  const toolName = meta?.toolName;
22429
21799
  const toolResponse = meta?.toolResponse;
22430
- if ((toolName === "Write" || toolName === "Edit") && toolResponse?.filePath) {
22431
- await this.captureTreeState();
21800
+ if ((toolName === "Write" || toolName === "Edit" || toolName === "MultiEdit" || toolName === "Delete" || toolName === "Move") && toolResponse?.filePath) {
21801
+ await this.captureCheckpointState();
22432
21802
  }
22433
21803
  if (toolName && (toolName.includes("Bash") || toolName.includes("bash"))) {
22434
21804
  this.detectAndAttachPrUrl(payload, params.update);
@@ -22590,14 +21960,9 @@ ${attributionInstructions}
22590
21960
  if (!this.session) return;
22591
21961
  this.logger.debug("Cleaning up session");
22592
21962
  try {
22593
- await this.captureHandoffCheckpoint();
22594
- } catch (error) {
22595
- this.logger.error("Failed to capture handoff checkpoint", error);
22596
- }
22597
- try {
22598
- await this.captureTreeState();
21963
+ await this.captureCheckpointState(this.session.pendingHandoffGitState);
22599
21964
  } catch (error) {
22600
- this.logger.error("Failed to capture final tree state", error);
21965
+ this.logger.error("Failed to capture final checkpoint state", error);
22601
21966
  }
22602
21967
  try {
22603
21968
  await this.session.logWriter.flush(this.session.payload.run_id, {
@@ -22625,41 +21990,13 @@ ${attributionInstructions}
22625
21990
  this.lastReportedBranch = null;
22626
21991
  this.session = null;
22627
21992
  }
22628
- async captureTreeState() {
22629
- if (!this.session?.treeTracker) return;
22630
- try {
22631
- const snapshot = await this.session.treeTracker.captureTree({});
22632
- if (snapshot) {
22633
- const snapshotWithDevice = {
22634
- ...snapshot,
22635
- device: this.session.deviceInfo
22636
- };
22637
- const notification = {
22638
- jsonrpc: "2.0",
22639
- method: POSTHOG_NOTIFICATIONS.TREE_SNAPSHOT,
22640
- params: snapshotWithDevice
22641
- };
22642
- this.broadcastEvent({
22643
- type: "notification",
22644
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
22645
- notification
22646
- });
22647
- this.session.logWriter.appendRawLine(
22648
- this.session.payload.run_id,
22649
- JSON.stringify(notification)
22650
- );
22651
- }
22652
- } catch (error) {
22653
- this.logger.error("Failed to capture tree state", error);
22654
- }
22655
- }
22656
- async captureHandoffCheckpoint() {
22657
- if (!this.session?.treeTracker || !this.session.pendingHandoffGitState) {
21993
+ async captureCheckpointState(localGitState) {
21994
+ if (!this.session || !this.config.repositoryPath) {
22658
21995
  return;
22659
21996
  }
22660
21997
  if (!this.posthogAPI) {
22661
21998
  this.logger.warn(
22662
- "Skipping handoff checkpoint capture: PostHog API client is not configured"
21999
+ "Skipping checkpoint capture: PostHog API client is not configured"
22663
22000
  );
22664
22001
  return;
22665
22002
  }
@@ -22670,9 +22007,7 @@ ${attributionInstructions}
22670
22007
  apiClient: this.posthogAPI,
22671
22008
  logger: this.logger.child("HandoffCheckpoint")
22672
22009
  });
22673
- const checkpoint = await tracker.captureForHandoff(
22674
- this.session.pendingHandoffGitState
22675
- );
22010
+ const checkpoint = await tracker.captureForHandoff(localGitState);
22676
22011
  if (!checkpoint) return;
22677
22012
  const checkpointWithDevice = {
22678
22013
  ...checkpoint,