@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
@@ -809,10 +809,10 @@ var require_src2 = __commonJS({
809
809
  var fs_1 = __require("fs");
810
810
  var debug_1 = __importDefault(require_src());
811
811
  var log = debug_1.default("@kwsites/file-exists");
812
- function check(path17, isFile2, isDirectory) {
813
- log(`checking %s`, path17);
812
+ function check(path16, isFile2, isDirectory) {
813
+ log(`checking %s`, path16);
814
814
  try {
815
- const stat4 = fs_1.statSync(path17);
815
+ const stat4 = fs_1.statSync(path16);
816
816
  if (stat4.isFile() && isFile2) {
817
817
  log(`[OK] path represents a file`);
818
818
  return true;
@@ -832,8 +832,8 @@ var require_src2 = __commonJS({
832
832
  throw e;
833
833
  }
834
834
  }
835
- function exists2(path17, type = exports2.READABLE) {
836
- return check(path17, (type & exports2.FILE) > 0, (type & exports2.FOLDER) > 0);
835
+ function exists2(path16, type = exports2.READABLE) {
836
+ return check(path16, (type & exports2.FILE) > 0, (type & exports2.FOLDER) > 0);
837
837
  }
838
838
  exports2.exists = exists2;
839
839
  exports2.FILE = 1;
@@ -929,11 +929,11 @@ var require_tree_sitter = __commonJS({
929
929
  throw toThrow;
930
930
  };
931
931
  var scriptDirectory = "";
932
- function locateFile(path17) {
932
+ function locateFile(path16) {
933
933
  if (Module["locateFile"]) {
934
- return Module["locateFile"](path17, scriptDirectory);
934
+ return Module["locateFile"](path16, scriptDirectory);
935
935
  }
936
- return scriptDirectory + path17;
936
+ return scriptDirectory + path16;
937
937
  }
938
938
  var readAsync, readBinary;
939
939
  if (ENVIRONMENT_IS_NODE) {
@@ -3454,8 +3454,8 @@ var require_tree_sitter = __commonJS({
3454
3454
  } else {
3455
3455
  const url = input;
3456
3456
  if (typeof process !== "undefined" && process.versions && process.versions.node) {
3457
- const fs14 = __require("fs");
3458
- bytes = Promise.resolve(fs14.readFileSync(url));
3457
+ const fs13 = __require("fs");
3458
+ bytes = Promise.resolve(fs13.readFileSync(url));
3459
3459
  } else {
3460
3460
  bytes = fetch(url).then((response) => response.arrayBuffer().then((buffer) => {
3461
3461
  if (response.ok) {
@@ -3784,8 +3784,8 @@ ${JSON.stringify(symbolNames, null, 2)}`);
3784
3784
  });
3785
3785
 
3786
3786
  // src/server/agent-server.ts
3787
- import { mkdir as mkdir8, writeFile as writeFile6 } from "fs/promises";
3788
- import { basename as basename2, join as join14 } from "path";
3787
+ import { mkdir as mkdir4, writeFile as writeFile4 } from "fs/promises";
3788
+ import { basename as basename2, join as join11 } from "path";
3789
3789
  import { pathToFileURL } from "url";
3790
3790
  import {
3791
3791
  ClientSideConnection as ClientSideConnection2,
@@ -3835,8 +3835,8 @@ function pathspec(...paths) {
3835
3835
  cache.set(key, paths);
3836
3836
  return key;
3837
3837
  }
3838
- function isPathSpec(path17) {
3839
- return path17 instanceof String && cache.has(path17);
3838
+ function isPathSpec(path16) {
3839
+ return path16 instanceof String && cache.has(path16);
3840
3840
  }
3841
3841
  function toPaths(pathSpec) {
3842
3842
  return cache.get(pathSpec) || [];
@@ -3925,8 +3925,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
3925
3925
  function forEachLineWithContent(input, callback) {
3926
3926
  return toLinesWithContent(input, true).map((line) => callback(line));
3927
3927
  }
3928
- function folderExists(path17) {
3929
- return (0, import_file_exists.exists)(path17, import_file_exists.FOLDER);
3928
+ function folderExists(path16) {
3929
+ return (0, import_file_exists.exists)(path16, import_file_exists.FOLDER);
3930
3930
  }
3931
3931
  function append(target, item) {
3932
3932
  if (Array.isArray(target)) {
@@ -4330,8 +4330,8 @@ function checkIsRepoRootTask() {
4330
4330
  commands,
4331
4331
  format: "utf-8",
4332
4332
  onError,
4333
- parser(path17) {
4334
- return /^\.(git)?$/.test(path17.trim());
4333
+ parser(path16) {
4334
+ return /^\.(git)?$/.test(path16.trim());
4335
4335
  }
4336
4336
  };
4337
4337
  }
@@ -4765,11 +4765,11 @@ function parseGrep(grep) {
4765
4765
  const paths = /* @__PURE__ */ new Set();
4766
4766
  const results = {};
4767
4767
  forEachLineWithContent(grep, (input) => {
4768
- const [path17, line, preview] = input.split(NULL);
4769
- paths.add(path17);
4770
- (results[path17] = results[path17] || []).push({
4768
+ const [path16, line, preview] = input.split(NULL);
4769
+ paths.add(path16);
4770
+ (results[path16] = results[path16] || []).push({
4771
4771
  line: asNumber(line),
4772
- path: path17,
4772
+ path: path16,
4773
4773
  preview
4774
4774
  });
4775
4775
  });
@@ -5534,14 +5534,14 @@ var init_hash_object = __esm({
5534
5534
  init_task();
5535
5535
  }
5536
5536
  });
5537
- function parseInit(bare, path17, text2) {
5537
+ function parseInit(bare, path16, text2) {
5538
5538
  const response = String(text2).trim();
5539
5539
  let result;
5540
5540
  if (result = initResponseRegex.exec(response)) {
5541
- return new InitSummary(bare, path17, false, result[1]);
5541
+ return new InitSummary(bare, path16, false, result[1]);
5542
5542
  }
5543
5543
  if (result = reInitResponseRegex.exec(response)) {
5544
- return new InitSummary(bare, path17, true, result[1]);
5544
+ return new InitSummary(bare, path16, true, result[1]);
5545
5545
  }
5546
5546
  let gitDir = "";
5547
5547
  const tokens = response.split(" ");
@@ -5552,7 +5552,7 @@ function parseInit(bare, path17, text2) {
5552
5552
  break;
5553
5553
  }
5554
5554
  }
5555
- return new InitSummary(bare, path17, /^re/i.test(response), gitDir);
5555
+ return new InitSummary(bare, path16, /^re/i.test(response), gitDir);
5556
5556
  }
5557
5557
  var InitSummary;
5558
5558
  var initResponseRegex;
@@ -5561,9 +5561,9 @@ var init_InitSummary = __esm({
5561
5561
  "src/lib/responses/InitSummary.ts"() {
5562
5562
  "use strict";
5563
5563
  InitSummary = class {
5564
- constructor(bare, path17, existing, gitDir) {
5564
+ constructor(bare, path16, existing, gitDir) {
5565
5565
  this.bare = bare;
5566
- this.path = path17;
5566
+ this.path = path16;
5567
5567
  this.existing = existing;
5568
5568
  this.gitDir = gitDir;
5569
5569
  }
@@ -5575,7 +5575,7 @@ var init_InitSummary = __esm({
5575
5575
  function hasBareCommand(command) {
5576
5576
  return command.includes(bareCommand);
5577
5577
  }
5578
- function initTask(bare = false, path17, customArgs) {
5578
+ function initTask(bare = false, path16, customArgs) {
5579
5579
  const commands = ["init", ...customArgs];
5580
5580
  if (bare && !hasBareCommand(commands)) {
5581
5581
  commands.splice(1, 0, bareCommand);
@@ -5584,7 +5584,7 @@ function initTask(bare = false, path17, customArgs) {
5584
5584
  commands,
5585
5585
  format: "utf-8",
5586
5586
  parser(text2) {
5587
- return parseInit(commands.includes("--bare"), path17, text2);
5587
+ return parseInit(commands.includes("--bare"), path16, text2);
5588
5588
  }
5589
5589
  };
5590
5590
  }
@@ -6400,12 +6400,12 @@ var init_FileStatusSummary = __esm({
6400
6400
  "use strict";
6401
6401
  fromPathRegex = /^(.+)\0(.+)$/;
6402
6402
  FileStatusSummary = class {
6403
- constructor(path17, index, working_dir) {
6404
- this.path = path17;
6403
+ constructor(path16, index, working_dir) {
6404
+ this.path = path16;
6405
6405
  this.index = index;
6406
6406
  this.working_dir = working_dir;
6407
6407
  if (index === "R" || working_dir === "R") {
6408
- const detail = fromPathRegex.exec(path17) || [null, path17, path17];
6408
+ const detail = fromPathRegex.exec(path16) || [null, path16, path16];
6409
6409
  this.from = detail[2] || "";
6410
6410
  this.path = detail[1] || "";
6411
6411
  }
@@ -6436,14 +6436,14 @@ function splitLine(result, lineStr) {
6436
6436
  default:
6437
6437
  return;
6438
6438
  }
6439
- function data(index, workingDir, path17) {
6439
+ function data(index, workingDir, path16) {
6440
6440
  const raw = `${index}${workingDir}`;
6441
6441
  const handler = parsers6.get(raw);
6442
6442
  if (handler) {
6443
- handler(result, path17);
6443
+ handler(result, path16);
6444
6444
  }
6445
6445
  if (raw !== "##" && raw !== "!!") {
6446
- result.files.push(new FileStatusSummary(path17, index, workingDir));
6446
+ result.files.push(new FileStatusSummary(path16, index, workingDir));
6447
6447
  }
6448
6448
  }
6449
6449
  }
@@ -6756,9 +6756,9 @@ var init_simple_git_api = __esm({
6756
6756
  next
6757
6757
  );
6758
6758
  }
6759
- hashObject(path17, write) {
6759
+ hashObject(path16, write) {
6760
6760
  return this._runTask(
6761
- hashObjectTask(path17, write === true),
6761
+ hashObjectTask(path16, write === true),
6762
6762
  trailingFunctionArgument(arguments)
6763
6763
  );
6764
6764
  }
@@ -7111,8 +7111,8 @@ var init_branch = __esm({
7111
7111
  }
7112
7112
  });
7113
7113
  function toPath(input) {
7114
- const path17 = input.trim().replace(/^["']|["']$/g, "");
7115
- return path17 && normalize(path17);
7114
+ const path16 = input.trim().replace(/^["']|["']$/g, "");
7115
+ return path16 && normalize(path16);
7116
7116
  }
7117
7117
  var parseCheckIgnore;
7118
7118
  var init_CheckIgnore = __esm({
@@ -7426,8 +7426,8 @@ __export(sub_module_exports, {
7426
7426
  subModuleTask: () => subModuleTask,
7427
7427
  updateSubModuleTask: () => updateSubModuleTask
7428
7428
  });
7429
- function addSubModuleTask(repo, path17) {
7430
- return subModuleTask(["add", repo, path17]);
7429
+ function addSubModuleTask(repo, path16) {
7430
+ return subModuleTask(["add", repo, path16]);
7431
7431
  }
7432
7432
  function initSubModuleTask(customArgs) {
7433
7433
  return subModuleTask(["init", ...customArgs]);
@@ -7757,8 +7757,8 @@ var require_git = __commonJS2({
7757
7757
  }
7758
7758
  return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
7759
7759
  };
7760
- Git2.prototype.submoduleAdd = function(repo, path17, then) {
7761
- return this._runTask(addSubModuleTask2(repo, path17), trailingFunctionArgument2(arguments));
7760
+ Git2.prototype.submoduleAdd = function(repo, path16, then) {
7761
+ return this._runTask(addSubModuleTask2(repo, path16), trailingFunctionArgument2(arguments));
7762
7762
  };
7763
7763
  Git2.prototype.submoduleUpdate = function(args2, then) {
7764
7764
  return this._runTask(
@@ -8591,12 +8591,6 @@ async function listWorktrees(baseDir, options) {
8591
8591
  return worktrees;
8592
8592
  }, { signal: options?.abortSignal });
8593
8593
  }
8594
- async function getHeadSha(baseDir, options) {
8595
- const manager = getGitOperationManager();
8596
- return manager.executeRead(baseDir, (git) => git.revparse(["HEAD"]), {
8597
- signal: options?.abortSignal
8598
- });
8599
- }
8600
8594
 
8601
8595
  // src/server/agent-server.ts
8602
8596
  import { Hono } from "hono";
@@ -8605,7 +8599,7 @@ import { z as z4 } from "zod";
8605
8599
  // package.json
8606
8600
  var package_default = {
8607
8601
  name: "@posthog/agent",
8608
- version: "2.3.398",
8602
+ version: "2.3.403",
8609
8603
  repository: "https://github.com/PostHog/code",
8610
8604
  description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
8611
8605
  exports: {
@@ -8673,10 +8667,6 @@ var package_default = {
8673
8667
  types: "./dist/handoff-checkpoint.d.ts",
8674
8668
  import: "./dist/handoff-checkpoint.js"
8675
8669
  },
8676
- "./tree-tracker": {
8677
- types: "./dist/tree-tracker.d.ts",
8678
- import: "./dist/tree-tracker.js"
8679
- },
8680
8670
  "./server": {
8681
8671
  types: "./dist/server/agent-server.d.ts",
8682
8672
  import: "./dist/server/agent-server.js"
@@ -8772,8 +8762,6 @@ var POSTHOG_NOTIFICATIONS = {
8772
8762
  CONSOLE: "_posthog/console",
8773
8763
  /** Maps taskRunId to agent's sessionId and adapter type (for resumption) */
8774
8764
  SDK_SESSION: "_posthog/sdk_session",
8775
- /** Tree state snapshot captured (git tree hash + file archive) */
8776
- TREE_SNAPSHOT: "_posthog/tree_snapshot",
8777
8765
  /** Git checkpoint captured for handoff */
8778
8766
  GIT_CHECKPOINT: "_posthog/git_checkpoint",
8779
8767
  /** Agent mode changed (interactive/background) */
@@ -8812,9 +8800,6 @@ function matchesExt(method, expected) {
8812
8800
  if (!method) return false;
8813
8801
  return method === expected || method === `_${expected}`;
8814
8802
  }
8815
- function isNotification(method, expected) {
8816
- return matchesExt(method, expected);
8817
- }
8818
8803
  function isMethod(method, expected) {
8819
8804
  return matchesExt(method, expected);
8820
8805
  }
@@ -13450,8 +13435,8 @@ var ToolContentBuilder = class {
13450
13435
  this.items.push({ type: "content", content: image(data, mimeType, uri) });
13451
13436
  return this;
13452
13437
  }
13453
- diff(path17, oldText, newText) {
13454
- this.items.push({ type: "diff", path: path17, oldText, newText });
13438
+ diff(path16, oldText, newText) {
13439
+ this.items.push({ type: "diff", path: path16, oldText, newText });
13455
13440
  return this;
13456
13441
  }
13457
13442
  build() {
@@ -18444,12 +18429,14 @@ function createCodexConnection(config) {
18444
18429
  }
18445
18430
 
18446
18431
  // src/handoff-checkpoint.ts
18447
- import { mkdir as mkdir4, readFile as readFile4, rm as rm4, writeFile as writeFile2 } from "fs/promises";
18448
- import { join as join9 } from "path";
18432
+ import { mkdtemp as mkdtemp2, readFile as readFile4, rm as rm4, writeFile as writeFile2 } from "fs/promises";
18433
+ import { tmpdir as tmpdir2 } from "os";
18434
+ import { dirname as dirname6, join as join9 } from "path";
18449
18435
 
18450
18436
  // ../git/dist/handoff.js
18451
18437
  import { spawn as spawn4 } from "child_process";
18452
- import { copyFile, mkdir as mkdir3, readFile as readFile3, rm as rm3, stat as stat3 } from "fs/promises";
18438
+ import { copyFile, mkdtemp, readFile as readFile3, rm as rm3, stat as stat3 } from "fs/promises";
18439
+ import { tmpdir } from "os";
18453
18440
  import path13 from "path";
18454
18441
 
18455
18442
  // ../git/dist/sagas/checkpoint.js
@@ -18857,7 +18844,7 @@ var GitHandoffTracker = class {
18857
18844
  this.repositoryPath = config.repositoryPath;
18858
18845
  this.logger = config.logger;
18859
18846
  }
18860
- async captureForHandoff(localGitState) {
18847
+ async captureForHandoff(_localGitState) {
18861
18848
  const captureSaga = new CaptureCheckpointSaga(this.logger);
18862
18849
  const result = await captureSaga.run({ baseDir: this.repositoryPath });
18863
18850
  if (!result.success) {
@@ -18865,15 +18852,19 @@ var GitHandoffTracker = class {
18865
18852
  }
18866
18853
  const checkpoint = result.data;
18867
18854
  const git = createGitClient(this.repositoryPath);
18868
- const tempDir = await this.getTempDir(git);
18855
+ const tempDir = await this.createTempDir(checkpoint.checkpointId);
18869
18856
  const checkpointRef = `${CHECKPOINT_REF_PREFIX2}${checkpoint.checkpointId}`;
18870
- const shouldIncludeHead = !!checkpoint.head && checkpoint.head !== localGitState?.head;
18871
- const headRef = shouldIncludeHead ? `${HANDOFF_HEAD_REF_PREFIX}${checkpoint.checkpointId}` : void 0;
18857
+ const packRefs = [
18858
+ checkpoint.head,
18859
+ checkpoint.indexTree,
18860
+ checkpoint.worktreeTree
18861
+ ].filter((ref) => !!ref);
18862
+ const headRef = checkpoint.head ? `${HANDOFF_HEAD_REF_PREFIX}${checkpoint.checkpointId}` : void 0;
18872
18863
  const packPrefix = path13.join(tempDir, checkpoint.checkpointId);
18873
18864
  try {
18874
18865
  const [headPack, indexFile, tracking] = await Promise.all([
18875
- shouldIncludeHead && checkpoint.head ? this.captureHeadPack(packPrefix, checkpoint.head) : Promise.resolve(void 0),
18876
- this.copyIndexFile(git, checkpoint.checkpointId),
18866
+ this.captureObjectPack(packPrefix, packRefs),
18867
+ this.copyIndexFile(git, checkpoint.checkpointId, tempDir),
18877
18868
  getTrackingMetadata(git, checkpoint.branch)
18878
18869
  ]);
18879
18870
  return {
@@ -18920,6 +18911,8 @@ var GitHandoffTracker = class {
18920
18911
  } else if (checkpoint.head) {
18921
18912
  await git.checkout(checkpoint.head);
18922
18913
  }
18914
+ await git.clean(["f", "d"]);
18915
+ await git.raw(["read-tree", "--reset", "-u", checkpoint.worktreeTree]);
18923
18916
  if (indexPath) {
18924
18917
  await this.restoreIndexFile(git, indexPath);
18925
18918
  }
@@ -18931,8 +18924,8 @@ var GitHandoffTracker = class {
18931
18924
  totalBytes: packBytes + indexBytes
18932
18925
  };
18933
18926
  }
18934
- async captureHeadPack(packPrefix, headCommit) {
18935
- const hash = await this.runGitWithInput(["pack-objects", packPrefix, "--revs"], `${headCommit}
18927
+ async captureObjectPack(packPrefix, refs) {
18928
+ const hash = await this.runGitWithInput(["pack-objects", packPrefix, "--revs"], `${refs.join("\n")}
18936
18929
  `);
18937
18930
  const packPath = `${packPrefix}-${hash.trim()}.pack`;
18938
18931
  const rawBytes = await this.getFileSize(packPath);
@@ -18940,9 +18933,8 @@ var GitHandoffTracker = class {
18940
18933
  });
18941
18934
  return { path: packPath, rawBytes };
18942
18935
  }
18943
- async copyIndexFile(git, checkpointId) {
18936
+ async copyIndexFile(git, checkpointId, tempDir) {
18944
18937
  const indexPath = await this.getGitPath(git, "index");
18945
- const tempDir = await this.getTempDir(git);
18946
18938
  const copiedIndexPath = path13.join(tempDir, `${checkpointId}.index`);
18947
18939
  await copyFile(indexPath, copiedIndexPath);
18948
18940
  return {
@@ -19070,13 +19062,8 @@ var GitHandoffTracker = class {
19070
19062
  ]);
19071
19063
  return exitCode === 0;
19072
19064
  }
19073
- async getTempDir(git) {
19074
- const raw = await git.raw(["rev-parse", "--git-common-dir"]);
19075
- const commonDir = raw.trim() || ".git";
19076
- const resolved = path13.isAbsolute(commonDir) ? commonDir : path13.resolve(this.repositoryPath, commonDir);
19077
- const tempDir = path13.join(resolved, "posthog-code-tmp");
19078
- await mkdir3(tempDir, { recursive: true });
19079
- return tempDir;
19065
+ async createTempDir(checkpointId) {
19066
+ return mkdtemp(joinTempPrefix(checkpointId));
19080
19067
  }
19081
19068
  async getGitPath(git, gitPath) {
19082
19069
  const raw = await git.raw(["rev-parse", "--git-path", gitPath]);
@@ -19143,6 +19130,9 @@ var GitHandoffTracker = class {
19143
19130
  });
19144
19131
  }
19145
19132
  };
19133
+ function joinTempPrefix(checkpointId) {
19134
+ return path13.join(tmpdir(), `posthog-code-handoff-${checkpointId}-`);
19135
+ }
19146
19136
  async function getCurrentBranchName(git) {
19147
19137
  try {
19148
19138
  const raw = await git.revparse(["--abbrev-ref", "HEAD"]);
@@ -19229,8 +19219,11 @@ var HandoffCheckpointTracker = class {
19229
19219
  indexArtifactPath: uploads.index?.storagePath
19230
19220
  };
19231
19221
  } finally {
19222
+ const tempDir = capture.headPack?.path ? dirname6(capture.headPack.path) : dirname6(capture.indexFile.path);
19232
19223
  await this.removeIfPresent(capture.headPack?.path);
19233
19224
  await this.removeIfPresent(capture.indexFile.path);
19225
+ await rm4(tempDir, { recursive: true, force: true }).catch(() => {
19226
+ });
19234
19227
  }
19235
19228
  }
19236
19229
  async applyFromHandoff(checkpoint, options) {
@@ -19240,8 +19233,9 @@ var HandoffCheckpointTracker = class {
19240
19233
  );
19241
19234
  }
19242
19235
  const gitTracker = this.createGitTracker();
19243
- const tmpDir = join9(this.repositoryPath, ".posthog", "tmp");
19244
- await mkdir4(tmpDir, { recursive: true });
19236
+ const tmpDir = await mkdtemp2(
19237
+ join9(tmpdir2(), `posthog-code-handoff-${checkpoint.checkpointId}-`)
19238
+ );
19245
19239
  const packPath = join9(tmpDir, `${checkpoint.checkpointId}.pack`);
19246
19240
  const indexPath = join9(tmpDir, `${checkpoint.checkpointId}.index`);
19247
19241
  try {
@@ -19275,6 +19269,8 @@ var HandoffCheckpointTracker = class {
19275
19269
  } finally {
19276
19270
  await this.removeIfPresent(packPath);
19277
19271
  await this.removeIfPresent(indexPath);
19272
+ await rm4(tmpDir, { recursive: true, force: true }).catch(() => {
19273
+ });
19278
19274
  }
19279
19275
  }
19280
19276
  toGitCheckpoint(checkpoint) {
@@ -19382,50 +19378,24 @@ var HandoffCheckpointTracker = class {
19382
19378
  }
19383
19379
  logCaptureMetrics(checkpoint, uploads) {
19384
19380
  this.logger.info("Captured handoff checkpoint", {
19385
- checkpointId: checkpoint.checkpointId,
19386
19381
  branch: checkpoint.branch,
19387
- head: checkpoint.head,
19388
- artifactPath: uploads.pack?.storagePath,
19389
- indexArtifactPath: uploads.index?.storagePath,
19390
- ...this.buildMetricPayload(uploads)
19382
+ head: checkpoint.head?.slice(0, 7),
19383
+ totalBytes: this.sumRawBytes(uploads.pack, uploads.index)
19391
19384
  });
19392
19385
  }
19393
- logApplyMetrics(checkpoint, downloads, totalBytes) {
19386
+ logApplyMetrics(checkpoint, _downloads, totalBytes) {
19394
19387
  this.logger.info("Applied handoff checkpoint", {
19395
- checkpointId: checkpoint.checkpointId,
19396
- commit: checkpoint.commit,
19397
19388
  branch: checkpoint.branch,
19398
- head: checkpoint.head,
19399
- packBytes: downloads.pack?.rawBytes ?? 0,
19400
- packWireBytes: downloads.pack?.wireBytes ?? 0,
19401
- indexBytes: downloads.index?.rawBytes ?? 0,
19402
- indexWireBytes: downloads.index?.wireBytes ?? 0,
19403
- totalBytes,
19404
- totalWireBytes: this.sumWireBytes(downloads.pack, downloads.index)
19389
+ head: checkpoint.head?.slice(0, 7),
19390
+ totalBytes
19405
19391
  });
19406
19392
  }
19407
- buildMetricPayload(metrics) {
19408
- return {
19409
- packBytes: metrics.pack?.rawBytes ?? 0,
19410
- packWireBytes: metrics.pack?.wireBytes ?? 0,
19411
- indexBytes: metrics.index?.rawBytes ?? 0,
19412
- indexWireBytes: metrics.index?.wireBytes ?? 0,
19413
- totalBytes: this.sumRawBytes(metrics.pack, metrics.index),
19414
- totalWireBytes: this.sumWireBytes(metrics.pack, metrics.index)
19415
- };
19416
- }
19417
19393
  sumRawBytes(...artifacts) {
19418
19394
  return artifacts.reduce(
19419
19395
  (total, artifact) => total + (artifact?.rawBytes ?? 0),
19420
19396
  0
19421
19397
  );
19422
19398
  }
19423
- sumWireBytes(...artifacts) {
19424
- return artifacts.reduce(
19425
- (total, artifact) => total + (artifact?.wireBytes ?? 0),
19426
- 0
19427
- );
19428
- }
19429
19399
  async removeIfPresent(filePath) {
19430
19400
  if (!filePath) {
19431
19401
  return;
@@ -19705,21 +19675,10 @@ var ResumeSaga = class extends Saga {
19705
19675
  return this.emptyResult();
19706
19676
  }
19707
19677
  this.log.info("Fetched log entries", { count: entries.length });
19708
- const latestSnapshot = await this.readOnlyStep(
19709
- "find_snapshot",
19710
- () => Promise.resolve(this.findLatestTreeSnapshot(entries))
19711
- );
19712
19678
  const latestGitCheckpoint = await this.readOnlyStep(
19713
19679
  "find_git_checkpoint",
19714
19680
  () => Promise.resolve(this.findLatestGitCheckpoint(entries))
19715
19681
  );
19716
- if (latestSnapshot) {
19717
- this.log.info("Found tree snapshot", {
19718
- treeHash: latestSnapshot.treeHash,
19719
- hasArchiveUrl: !!latestSnapshot.archiveUrl,
19720
- changes: latestSnapshot.changes?.length ?? 0
19721
- });
19722
- }
19723
19682
  if (latestGitCheckpoint) {
19724
19683
  this.log.info("Found git checkpoint", {
19725
19684
  checkpointId: latestGitCheckpoint.checkpointId,
@@ -19736,15 +19695,13 @@ var ResumeSaga = class extends Saga {
19736
19695
  );
19737
19696
  this.log.info("Resume state rebuilt", {
19738
19697
  turns: conversation.length,
19739
- hasSnapshot: !!latestSnapshot,
19740
19698
  hasGitCheckpoint: !!latestGitCheckpoint,
19741
- interrupted: latestSnapshot?.interrupted ?? false
19699
+ interrupted: false
19742
19700
  });
19743
19701
  return {
19744
19702
  conversation,
19745
- latestSnapshot,
19746
19703
  latestGitCheckpoint,
19747
- interrupted: latestSnapshot?.interrupted ?? false,
19704
+ interrupted: false,
19748
19705
  lastDevice,
19749
19706
  logEntryCount: entries.length
19750
19707
  };
@@ -19752,27 +19709,11 @@ var ResumeSaga = class extends Saga {
19752
19709
  emptyResult() {
19753
19710
  return {
19754
19711
  conversation: [],
19755
- latestSnapshot: null,
19756
19712
  latestGitCheckpoint: null,
19757
19713
  interrupted: false,
19758
19714
  logEntryCount: 0
19759
19715
  };
19760
19716
  }
19761
- findLatestTreeSnapshot(entries) {
19762
- for (let i2 = entries.length - 1; i2 >= 0; i2--) {
19763
- const entry = entries[i2];
19764
- if (isNotification(
19765
- entry.notification?.method,
19766
- POSTHOG_NOTIFICATIONS.TREE_SNAPSHOT
19767
- )) {
19768
- const params = entry.notification.params;
19769
- if (params?.treeHash) {
19770
- return params;
19771
- }
19772
- }
19773
- }
19774
- return null;
19775
- }
19776
19717
  findLatestGitCheckpoint(entries) {
19777
19718
  const sdkPrefixedMethod = `_${POSTHOG_NOTIFICATIONS.GIT_CHECKPOINT}`;
19778
19719
  for (let i2 = entries.length - 1; i2 >= 0; i2--) {
@@ -19934,7 +19875,6 @@ async function resumeFromLog(config) {
19934
19875
  }
19935
19876
  return {
19936
19877
  conversation: result.data.conversation,
19937
- latestSnapshot: result.data.latestSnapshot,
19938
19878
  latestGitCheckpoint: result.data.latestGitCheckpoint,
19939
19879
  interrupted: result.data.interrupted,
19940
19880
  lastDevice: result.data.lastDevice,
@@ -20322,544 +20262,6 @@ var SessionLogWriter = class _SessionLogWriter {
20322
20262
  }
20323
20263
  };
20324
20264
 
20325
- // src/sagas/apply-snapshot-saga.ts
20326
- import { mkdir as mkdir7, rm as rm6, writeFile as writeFile5 } from "fs/promises";
20327
- import { join as join12 } from "path";
20328
-
20329
- // ../git/dist/sagas/tree.js
20330
- import { existsSync as existsSync5 } from "fs";
20331
- import * as fs13 from "fs/promises";
20332
- import * as path16 from "path";
20333
- import * as tar from "tar";
20334
- var CaptureTreeSaga = class extends GitSaga {
20335
- sagaName = "CaptureTreeSaga";
20336
- tempIndexPath = null;
20337
- async executeGitOperations(input) {
20338
- const { baseDir, lastTreeHash, archivePath, signal } = input;
20339
- const tmpDir = path16.join(baseDir, ".git", "posthog-code-tmp");
20340
- await this.step({
20341
- name: "create_tmp_dir",
20342
- execute: () => fs13.mkdir(tmpDir, { recursive: true }),
20343
- rollback: async () => {
20344
- }
20345
- });
20346
- this.tempIndexPath = path16.join(tmpDir, `index-${Date.now()}`);
20347
- const tempIndexGit = this.git.env({
20348
- ...process.env,
20349
- GIT_INDEX_FILE: this.tempIndexPath
20350
- });
20351
- await this.step({
20352
- name: "init_temp_index",
20353
- execute: () => tempIndexGit.raw(["read-tree", "HEAD"]),
20354
- rollback: async () => {
20355
- if (this.tempIndexPath) {
20356
- await fs13.rm(this.tempIndexPath, { force: true }).catch(() => {
20357
- });
20358
- }
20359
- }
20360
- });
20361
- await this.readOnlyStep("stage_files", () => tempIndexGit.raw(["add", "-A"]));
20362
- const treeHash = await this.readOnlyStep("write_tree", () => tempIndexGit.raw(["write-tree"]));
20363
- if (lastTreeHash && treeHash === lastTreeHash) {
20364
- this.log.debug("No changes since last capture", { treeHash });
20365
- await fs13.rm(this.tempIndexPath, { force: true }).catch(() => {
20366
- });
20367
- return { snapshot: null, changed: false };
20368
- }
20369
- const baseCommit = await this.readOnlyStep("get_base_commit", async () => {
20370
- try {
20371
- return await getHeadSha(baseDir, { abortSignal: signal });
20372
- } catch {
20373
- return null;
20374
- }
20375
- });
20376
- const changes = await this.readOnlyStep("get_changes", () => this.getChanges(this.git, baseCommit, treeHash));
20377
- await fs13.rm(this.tempIndexPath, { force: true }).catch(() => {
20378
- });
20379
- const snapshot = {
20380
- treeHash,
20381
- baseCommit,
20382
- changes,
20383
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
20384
- };
20385
- let createdArchivePath;
20386
- if (archivePath) {
20387
- createdArchivePath = await this.createArchive(baseDir, archivePath, changes);
20388
- }
20389
- this.log.info("Tree captured", {
20390
- treeHash,
20391
- changes: changes.length,
20392
- archived: !!createdArchivePath
20393
- });
20394
- return { snapshot, archivePath: createdArchivePath, changed: true };
20395
- }
20396
- async createArchive(baseDir, archivePath, changes) {
20397
- const filesToArchive = changes.filter((c) => c.status !== "D").map((c) => c.path);
20398
- if (filesToArchive.length === 0) {
20399
- return void 0;
20400
- }
20401
- const existingFiles = filesToArchive.filter((f) => existsSync5(path16.join(baseDir, f)));
20402
- if (existingFiles.length === 0) {
20403
- return void 0;
20404
- }
20405
- await this.step({
20406
- name: "create_archive",
20407
- execute: async () => {
20408
- const archiveDir = path16.dirname(archivePath);
20409
- await fs13.mkdir(archiveDir, { recursive: true });
20410
- await tar.create({
20411
- gzip: true,
20412
- file: archivePath,
20413
- cwd: baseDir
20414
- }, existingFiles);
20415
- },
20416
- rollback: async () => {
20417
- await fs13.rm(archivePath, { force: true }).catch(() => {
20418
- });
20419
- }
20420
- });
20421
- return archivePath;
20422
- }
20423
- async getChanges(git, fromRef, toRef) {
20424
- if (!fromRef) {
20425
- const stdout2 = await git.raw(["ls-tree", "-r", "--name-only", toRef]);
20426
- return stdout2.split("\n").filter((p) => p.trim()).map((p) => ({ path: p, status: "A" }));
20427
- }
20428
- const stdout = await git.raw([
20429
- "diff-tree",
20430
- "-r",
20431
- "--name-status",
20432
- fromRef,
20433
- toRef
20434
- ]);
20435
- const changes = [];
20436
- for (const line of stdout.split("\n")) {
20437
- if (!line.trim())
20438
- continue;
20439
- const [status, filePath] = line.split(" ");
20440
- if (!filePath)
20441
- continue;
20442
- let normalizedStatus;
20443
- if (status === "D") {
20444
- normalizedStatus = "D";
20445
- } else if (status === "A") {
20446
- normalizedStatus = "A";
20447
- } else {
20448
- normalizedStatus = "M";
20449
- }
20450
- changes.push({ path: filePath, status: normalizedStatus });
20451
- }
20452
- return changes;
20453
- }
20454
- };
20455
- var ApplyTreeSaga = class extends GitSaga {
20456
- sagaName = "ApplyTreeSaga";
20457
- originalHead = null;
20458
- originalBranch = null;
20459
- extractedFiles = [];
20460
- fileBackups = /* @__PURE__ */ new Map();
20461
- async executeGitOperations(input) {
20462
- const { baseDir, treeHash, baseCommit, changes, archivePath } = input;
20463
- const headInfo = await this.readOnlyStep("get_current_head", async () => {
20464
- let head = null;
20465
- let branch = null;
20466
- try {
20467
- head = await this.git.revparse(["HEAD"]);
20468
- } catch {
20469
- head = null;
20470
- }
20471
- try {
20472
- branch = await this.git.raw(["symbolic-ref", "--short", "HEAD"]);
20473
- } catch {
20474
- branch = null;
20475
- }
20476
- return { head, branch };
20477
- });
20478
- this.originalHead = headInfo.head;
20479
- this.originalBranch = headInfo.branch;
20480
- let checkoutPerformed = false;
20481
- if (baseCommit && baseCommit !== this.originalHead) {
20482
- await this.readOnlyStep("check_working_tree", async () => {
20483
- const status = await this.git.status();
20484
- if (!status.isClean()) {
20485
- const changedFiles = status.modified.length + status.staged.length + status.deleted.length;
20486
- throw new Error(`Cannot apply tree: ${changedFiles} uncommitted change(s) exist. Commit or stash your changes first.`);
20487
- }
20488
- });
20489
- await this.step({
20490
- name: "checkout_base",
20491
- execute: async () => {
20492
- await this.git.checkout(baseCommit);
20493
- checkoutPerformed = true;
20494
- this.log.warn("Applied tree from different commit - now in detached HEAD state", {
20495
- originalHead: this.originalHead,
20496
- originalBranch: this.originalBranch,
20497
- baseCommit
20498
- });
20499
- },
20500
- rollback: async () => {
20501
- try {
20502
- if (this.originalBranch) {
20503
- await this.git.checkout(this.originalBranch);
20504
- } else if (this.originalHead) {
20505
- await this.git.checkout(this.originalHead);
20506
- }
20507
- } catch (error) {
20508
- this.log.warn("Failed to rollback checkout", { error });
20509
- }
20510
- }
20511
- });
20512
- }
20513
- if (archivePath) {
20514
- const filesToExtract = changes.filter((c) => c.status !== "D").map((c) => c.path);
20515
- await this.readOnlyStep("backup_existing_files", async () => {
20516
- for (const filePath of filesToExtract) {
20517
- const fullPath = path16.join(baseDir, filePath);
20518
- try {
20519
- const content = await fs13.readFile(fullPath);
20520
- this.fileBackups.set(filePath, content);
20521
- } catch {
20522
- }
20523
- }
20524
- });
20525
- await this.step({
20526
- name: "extract_archive",
20527
- execute: async () => {
20528
- await tar.extract({
20529
- file: archivePath,
20530
- cwd: baseDir
20531
- });
20532
- this.extractedFiles = filesToExtract;
20533
- },
20534
- rollback: async () => {
20535
- for (const filePath of this.extractedFiles) {
20536
- const fullPath = path16.join(baseDir, filePath);
20537
- const backup = this.fileBackups.get(filePath);
20538
- if (backup) {
20539
- const dir = path16.dirname(fullPath);
20540
- await fs13.mkdir(dir, { recursive: true }).catch(() => {
20541
- });
20542
- await fs13.writeFile(fullPath, backup).catch(() => {
20543
- });
20544
- } else {
20545
- await fs13.rm(fullPath, { force: true }).catch(() => {
20546
- });
20547
- }
20548
- }
20549
- }
20550
- });
20551
- }
20552
- for (const change of changes.filter((c) => c.status === "D")) {
20553
- const fullPath = path16.join(baseDir, change.path);
20554
- const backupContent = await this.readOnlyStep(`backup_${change.path}`, async () => {
20555
- try {
20556
- return await fs13.readFile(fullPath);
20557
- } catch {
20558
- return null;
20559
- }
20560
- });
20561
- await this.step({
20562
- name: `delete_${change.path}`,
20563
- execute: async () => {
20564
- await fs13.rm(fullPath, { force: true });
20565
- this.log.debug(`Deleted file: ${change.path}`);
20566
- },
20567
- rollback: async () => {
20568
- if (backupContent) {
20569
- const dir = path16.dirname(fullPath);
20570
- await fs13.mkdir(dir, { recursive: true }).catch(() => {
20571
- });
20572
- await fs13.writeFile(fullPath, backupContent).catch(() => {
20573
- });
20574
- }
20575
- }
20576
- });
20577
- }
20578
- const deletedCount = changes.filter((c) => c.status === "D").length;
20579
- this.log.info("Tree applied", {
20580
- treeHash,
20581
- totalChanges: changes.length,
20582
- deletedFiles: deletedCount,
20583
- checkoutPerformed
20584
- });
20585
- return { treeHash, checkoutPerformed };
20586
- }
20587
- };
20588
-
20589
- // src/sagas/apply-snapshot-saga.ts
20590
- var ApplySnapshotSaga = class extends Saga {
20591
- sagaName = "ApplySnapshotSaga";
20592
- archivePath = null;
20593
- async execute(input) {
20594
- const { snapshot, repositoryPath, apiClient, taskId, runId } = input;
20595
- const tmpDir = join12(repositoryPath, ".posthog", "tmp");
20596
- if (!snapshot.archiveUrl) {
20597
- throw new Error("Cannot apply snapshot: no archive URL");
20598
- }
20599
- const archiveUrl = snapshot.archiveUrl;
20600
- await this.step({
20601
- name: "create_tmp_dir",
20602
- execute: () => mkdir7(tmpDir, { recursive: true }),
20603
- rollback: async () => {
20604
- }
20605
- });
20606
- const archivePath = join12(tmpDir, `${snapshot.treeHash}.tar.gz`);
20607
- this.archivePath = archivePath;
20608
- await this.step({
20609
- name: "download_archive",
20610
- execute: async () => {
20611
- const arrayBuffer = await apiClient.downloadArtifact(
20612
- taskId,
20613
- runId,
20614
- archiveUrl
20615
- );
20616
- if (!arrayBuffer) {
20617
- throw new Error("Failed to download archive");
20618
- }
20619
- const base64Content = Buffer.from(arrayBuffer).toString("utf-8");
20620
- const binaryContent = Buffer.from(base64Content, "base64");
20621
- await writeFile5(archivePath, binaryContent);
20622
- this.log.info("Tree archive downloaded", {
20623
- treeHash: snapshot.treeHash,
20624
- snapshotBytes: binaryContent.byteLength,
20625
- snapshotWireBytes: arrayBuffer.byteLength,
20626
- totalBytes: binaryContent.byteLength,
20627
- totalWireBytes: arrayBuffer.byteLength
20628
- });
20629
- },
20630
- rollback: async () => {
20631
- if (this.archivePath) {
20632
- await rm6(this.archivePath, { force: true }).catch(() => {
20633
- });
20634
- }
20635
- }
20636
- });
20637
- const gitApplySaga = new ApplyTreeSaga(this.log);
20638
- const applyResult = await gitApplySaga.run({
20639
- baseDir: repositoryPath,
20640
- treeHash: snapshot.treeHash,
20641
- baseCommit: snapshot.baseCommit,
20642
- changes: snapshot.changes,
20643
- archivePath: this.archivePath
20644
- });
20645
- if (!applyResult.success) {
20646
- throw new Error(`Failed to apply tree: ${applyResult.error}`);
20647
- }
20648
- await rm6(this.archivePath, { force: true }).catch(() => {
20649
- });
20650
- this.log.info("Tree snapshot applied", {
20651
- treeHash: snapshot.treeHash,
20652
- totalChanges: snapshot.changes.length,
20653
- deletedFiles: snapshot.changes.filter((c) => c.status === "D").length
20654
- });
20655
- return { treeHash: snapshot.treeHash };
20656
- }
20657
- };
20658
-
20659
- // src/sagas/capture-tree-saga.ts
20660
- import { existsSync as existsSync6 } from "fs";
20661
- import { readFile as readFile6, rm as rm7 } from "fs/promises";
20662
- import { join as join13 } from "path";
20663
- var CaptureTreeSaga2 = class extends Saga {
20664
- sagaName = "CaptureTreeSaga";
20665
- async execute(input) {
20666
- const {
20667
- repositoryPath,
20668
- lastTreeHash,
20669
- interrupted,
20670
- apiClient,
20671
- taskId,
20672
- runId
20673
- } = input;
20674
- const tmpDir = join13(repositoryPath, ".posthog", "tmp");
20675
- if (existsSync6(join13(repositoryPath, ".gitmodules"))) {
20676
- this.log.warn(
20677
- "Repository has submodules - snapshot may not capture submodule state"
20678
- );
20679
- }
20680
- const shouldArchive = !!apiClient;
20681
- const archivePath = shouldArchive ? join13(tmpDir, `tree-${Date.now()}.tar.gz`) : void 0;
20682
- const gitCaptureSaga = new CaptureTreeSaga(this.log);
20683
- const captureResult = await gitCaptureSaga.run({
20684
- baseDir: repositoryPath,
20685
- lastTreeHash,
20686
- archivePath
20687
- });
20688
- if (!captureResult.success) {
20689
- throw new Error(`Failed to capture tree: ${captureResult.error}`);
20690
- }
20691
- const {
20692
- snapshot: gitSnapshot,
20693
- archivePath: createdArchivePath,
20694
- changed
20695
- } = captureResult.data;
20696
- if (!changed || !gitSnapshot) {
20697
- this.log.debug("No changes since last capture", { lastTreeHash });
20698
- return { snapshot: null, newTreeHash: lastTreeHash };
20699
- }
20700
- let archiveUrl;
20701
- if (apiClient && createdArchivePath) {
20702
- try {
20703
- archiveUrl = await this.uploadArchive(
20704
- createdArchivePath,
20705
- gitSnapshot.treeHash,
20706
- apiClient,
20707
- taskId,
20708
- runId
20709
- );
20710
- } finally {
20711
- await rm7(createdArchivePath, { force: true }).catch(() => {
20712
- });
20713
- }
20714
- }
20715
- const snapshot = {
20716
- treeHash: gitSnapshot.treeHash,
20717
- baseCommit: gitSnapshot.baseCommit,
20718
- changes: gitSnapshot.changes,
20719
- timestamp: gitSnapshot.timestamp,
20720
- interrupted,
20721
- archiveUrl
20722
- };
20723
- this.log.info("Tree captured", {
20724
- treeHash: snapshot.treeHash,
20725
- changes: snapshot.changes.length,
20726
- interrupted,
20727
- archiveUrl
20728
- });
20729
- return { snapshot, newTreeHash: snapshot.treeHash };
20730
- }
20731
- async uploadArchive(archivePath, treeHash, apiClient, taskId, runId) {
20732
- const archiveUrl = await this.step({
20733
- name: "upload_archive",
20734
- execute: async () => {
20735
- const archiveContent = await readFile6(archivePath);
20736
- const base64Content = archiveContent.toString("base64");
20737
- const snapshotBytes = archiveContent.byteLength;
20738
- const snapshotWireBytes = Buffer.byteLength(base64Content, "utf-8");
20739
- const artifacts = await apiClient.uploadTaskArtifacts(taskId, runId, [
20740
- {
20741
- name: `trees/${treeHash}.tar.gz`,
20742
- type: "tree_snapshot",
20743
- content: base64Content,
20744
- content_type: "application/gzip"
20745
- }
20746
- ]);
20747
- const uploadedArtifact = artifacts[0];
20748
- if (uploadedArtifact?.storage_path) {
20749
- this.log.info("Tree archive uploaded", {
20750
- storagePath: uploadedArtifact.storage_path,
20751
- treeHash,
20752
- snapshotBytes,
20753
- snapshotWireBytes,
20754
- totalBytes: snapshotBytes,
20755
- totalWireBytes: snapshotWireBytes
20756
- });
20757
- return uploadedArtifact.storage_path;
20758
- }
20759
- return void 0;
20760
- },
20761
- rollback: async () => {
20762
- await rm7(archivePath, { force: true }).catch(() => {
20763
- });
20764
- }
20765
- });
20766
- return archiveUrl;
20767
- }
20768
- };
20769
-
20770
- // src/tree-tracker.ts
20771
- var TreeTracker = class {
20772
- repositoryPath;
20773
- taskId;
20774
- runId;
20775
- apiClient;
20776
- logger;
20777
- lastTreeHash = null;
20778
- constructor(config) {
20779
- this.repositoryPath = config.repositoryPath;
20780
- this.taskId = config.taskId;
20781
- this.runId = config.runId;
20782
- this.apiClient = config.apiClient;
20783
- this.logger = config.logger || new Logger({ debug: false, prefix: "[TreeTracker]" });
20784
- }
20785
- /**
20786
- * Capture current working tree state as a snapshot.
20787
- * Uses a temporary index to avoid modifying user's staging area.
20788
- * Uses Saga pattern for atomic operation with automatic cleanup on failure.
20789
- */
20790
- async captureTree(options) {
20791
- const saga = new CaptureTreeSaga2(this.logger);
20792
- const result = await saga.run({
20793
- repositoryPath: this.repositoryPath,
20794
- taskId: this.taskId,
20795
- runId: this.runId,
20796
- apiClient: this.apiClient,
20797
- lastTreeHash: this.lastTreeHash,
20798
- interrupted: options?.interrupted
20799
- });
20800
- if (!result.success) {
20801
- this.logger.error("Failed to capture tree", {
20802
- error: result.error,
20803
- failedStep: result.failedStep
20804
- });
20805
- throw new Error(
20806
- `Failed to capture tree at step '${result.failedStep}': ${result.error}`
20807
- );
20808
- }
20809
- if (result.data.newTreeHash !== null) {
20810
- this.lastTreeHash = result.data.newTreeHash;
20811
- }
20812
- return result.data.snapshot;
20813
- }
20814
- /**
20815
- * Download and apply a tree snapshot.
20816
- * Uses Saga pattern for atomic operation with rollback on failure.
20817
- */
20818
- async applyTreeSnapshot(snapshot) {
20819
- if (!this.apiClient) {
20820
- throw new Error("Cannot apply snapshot: API client not configured");
20821
- }
20822
- if (!snapshot.archiveUrl) {
20823
- this.logger.warn("Cannot apply snapshot: no archive URL", {
20824
- treeHash: snapshot.treeHash,
20825
- changes: snapshot.changes.length
20826
- });
20827
- throw new Error("Cannot apply snapshot: no archive URL");
20828
- }
20829
- const saga = new ApplySnapshotSaga(this.logger);
20830
- const result = await saga.run({
20831
- snapshot,
20832
- repositoryPath: this.repositoryPath,
20833
- apiClient: this.apiClient,
20834
- taskId: this.taskId,
20835
- runId: this.runId
20836
- });
20837
- if (!result.success) {
20838
- this.logger.error("Failed to apply tree snapshot", {
20839
- error: result.error,
20840
- failedStep: result.failedStep,
20841
- treeHash: snapshot.treeHash
20842
- });
20843
- throw new Error(
20844
- `Failed to apply snapshot at step '${result.failedStep}': ${result.error}`
20845
- );
20846
- }
20847
- this.lastTreeHash = result.data.treeHash;
20848
- }
20849
- /**
20850
- * Get the last captured tree hash.
20851
- */
20852
- getLastTreeHash() {
20853
- return this.lastTreeHash;
20854
- }
20855
- /**
20856
- * Set the last tree hash (used when resuming).
20857
- */
20858
- setLastTreeHash(hash) {
20859
- this.lastTreeHash = hash;
20860
- }
20861
- };
20862
-
20863
20265
  // src/server/cloud-prompt.ts
20864
20266
  function normalizeCloudPromptContent(content) {
20865
20267
  if (typeof content === "string") {
@@ -21344,7 +20746,6 @@ var AgentServer = class {
21344
20746
  });
21345
20747
  this.logger.debug("Resume state loaded", {
21346
20748
  conversationTurns: this.resumeState.conversation.length,
21347
- hasSnapshot: !!this.resumeState.latestSnapshot,
21348
20749
  hasGitCheckpoint: !!this.resumeState.latestGitCheckpoint,
21349
20750
  gitCheckpointBranch: this.resumeState.latestGitCheckpoint?.branch ?? null,
21350
20751
  logEntries: this.resumeState.logEntryCount
@@ -21590,13 +20991,6 @@ var AgentServer = class {
21590
20991
  getApiKey: () => this.config.apiKey,
21591
20992
  userAgent: `posthog/cloud.hog.dev; version: ${this.config.version ?? package_default.version}`
21592
20993
  });
21593
- const treeTracker = this.config.repositoryPath ? new TreeTracker({
21594
- repositoryPath: this.config.repositoryPath,
21595
- taskId: payload.task_id,
21596
- runId: payload.run_id,
21597
- apiClient: posthogAPI,
21598
- logger: new Logger({ debug: true, prefix: "[TreeTracker]" })
21599
- }) : null;
21600
20994
  const logWriter = new SessionLogWriter({
21601
20995
  posthogAPI,
21602
20996
  logger: new Logger({ debug: true, prefix: "[SessionLogWriter]" })
@@ -21689,7 +21083,6 @@ var AgentServer = class {
21689
21083
  acpSessionId,
21690
21084
  acpConnection,
21691
21085
  clientConnection,
21692
- treeTracker,
21693
21086
  sseController,
21694
21087
  deviceInfo,
21695
21088
  logWriter,
@@ -21816,31 +21209,7 @@ var AgentServer = class {
21816
21209
  const conversationSummary = formatConversationForResume(
21817
21210
  this.resumeState.conversation
21818
21211
  );
21819
- let snapshotApplied = false;
21820
- if (this.resumeState.latestSnapshot?.archiveUrl && this.config.repositoryPath && this.posthogAPI) {
21821
- try {
21822
- const treeTracker = new TreeTracker({
21823
- repositoryPath: this.config.repositoryPath,
21824
- taskId: payload.task_id,
21825
- runId: payload.run_id,
21826
- apiClient: this.posthogAPI,
21827
- logger: this.logger.child("TreeTracker")
21828
- });
21829
- await treeTracker.applyTreeSnapshot(this.resumeState.latestSnapshot);
21830
- treeTracker.setLastTreeHash(this.resumeState.latestSnapshot.treeHash);
21831
- snapshotApplied = true;
21832
- this.logger.info("Tree snapshot applied", {
21833
- treeHash: this.resumeState.latestSnapshot.treeHash,
21834
- changes: this.resumeState.latestSnapshot.changes?.length ?? 0,
21835
- hasArchiveUrl: !!this.resumeState.latestSnapshot.archiveUrl
21836
- });
21837
- } catch (error) {
21838
- this.logger.warn("Failed to apply tree snapshot", {
21839
- error: error instanceof Error ? error.message : String(error),
21840
- treeHash: this.resumeState.latestSnapshot.treeHash
21841
- });
21842
- }
21843
- }
21212
+ let checkpointApplied = false;
21844
21213
  if (this.resumeState.latestGitCheckpoint && this.config.repositoryPath && this.posthogAPI) {
21845
21214
  try {
21846
21215
  const checkpointTracker = new HandoffCheckpointTracker({
@@ -21853,6 +21222,7 @@ var AgentServer = class {
21853
21222
  const metrics = await checkpointTracker.applyFromHandoff(
21854
21223
  this.resumeState.latestGitCheckpoint
21855
21224
  );
21225
+ checkpointApplied = true;
21856
21226
  this.logger.info("Git checkpoint applied", {
21857
21227
  branch: this.resumeState.latestGitCheckpoint.branch,
21858
21228
  head: this.resumeState.latestGitCheckpoint.head,
@@ -21868,7 +21238,7 @@ var AgentServer = class {
21868
21238
  }
21869
21239
  }
21870
21240
  const pendingUserPrompt = await this.getPendingUserPrompt(taskRun);
21871
- const sandboxContext = snapshotApplied ? `The workspace environment (all files, packages, and code changes) has been fully restored from where you left off.` : `The workspace files from the previous session were not restored (the file snapshot may have expired), so you are starting with a fresh environment. Your conversation history is fully preserved below.`;
21241
+ const sandboxContext = checkpointApplied ? `The workspace environment (all files, packages, and code changes) has been fully restored from the latest checkpoint.` : `The workspace from the previous session was not restored from a checkpoint, so you are starting with a fresh environment. Your conversation history is fully preserved below.`;
21872
21242
  let resumePromptBlocks;
21873
21243
  if (pendingUserPrompt?.length) {
21874
21244
  resumePromptBlocks = [
@@ -21909,7 +21279,7 @@ Continue from where you left off. The user is waiting for your response.`
21909
21279
  conversationTurns: this.resumeState.conversation.length,
21910
21280
  promptLength: promptBlocksToText(resumePromptBlocks).length,
21911
21281
  hasPendingUserMessage: !!pendingUserPrompt?.length,
21912
- snapshotApplied,
21282
+ checkpointApplied,
21913
21283
  hasGitCheckpoint: !!this.resumeState.latestGitCheckpoint,
21914
21284
  gitCheckpointBranch: this.resumeState.latestGitCheckpoint?.branch ?? null
21915
21285
  });
@@ -22055,16 +21425,16 @@ Continue from where you left off. The user is waiting for your response.`
22055
21425
  throw new Error(`Failed to download artifact ${artifact.name}`);
22056
21426
  }
22057
21427
  const safeName = this.getSafeArtifactName(artifact.name);
22058
- const artifactDir = join14(
21428
+ const artifactDir = join11(
22059
21429
  this.config.repositoryPath ?? "/tmp/workspace",
22060
21430
  ".posthog",
22061
21431
  "attachments",
22062
21432
  runId,
22063
21433
  artifact.id ?? safeName
22064
21434
  );
22065
- await mkdir8(artifactDir, { recursive: true });
22066
- const artifactPath = join14(artifactDir, safeName);
22067
- await writeFile6(artifactPath, Buffer.from(data));
21435
+ await mkdir4(artifactDir, { recursive: true });
21436
+ const artifactPath = join11(artifactDir, safeName);
21437
+ await writeFile4(artifactPath, Buffer.from(data));
22068
21438
  return resourceLink(pathToFileURL(artifactPath).toString(), artifact.name, {
22069
21439
  ...artifact.content_type ? { mimeType: artifact.content_type } : {},
22070
21440
  ...typeof artifact.size === "number" ? { size: artifact.size } : {}
@@ -22419,8 +21789,8 @@ ${attributionInstructions}
22419
21789
  const meta = params.update?._meta?.claudeCode;
22420
21790
  const toolName = meta?.toolName;
22421
21791
  const toolResponse = meta?.toolResponse;
22422
- if ((toolName === "Write" || toolName === "Edit") && toolResponse?.filePath) {
22423
- await this.captureTreeState();
21792
+ if ((toolName === "Write" || toolName === "Edit" || toolName === "MultiEdit" || toolName === "Delete" || toolName === "Move") && toolResponse?.filePath) {
21793
+ await this.captureCheckpointState();
22424
21794
  }
22425
21795
  if (toolName && (toolName.includes("Bash") || toolName.includes("bash"))) {
22426
21796
  this.detectAndAttachPrUrl(payload, params.update);
@@ -22582,14 +21952,9 @@ ${attributionInstructions}
22582
21952
  if (!this.session) return;
22583
21953
  this.logger.debug("Cleaning up session");
22584
21954
  try {
22585
- await this.captureHandoffCheckpoint();
22586
- } catch (error) {
22587
- this.logger.error("Failed to capture handoff checkpoint", error);
22588
- }
22589
- try {
22590
- await this.captureTreeState();
21955
+ await this.captureCheckpointState(this.session.pendingHandoffGitState);
22591
21956
  } catch (error) {
22592
- this.logger.error("Failed to capture final tree state", error);
21957
+ this.logger.error("Failed to capture final checkpoint state", error);
22593
21958
  }
22594
21959
  try {
22595
21960
  await this.session.logWriter.flush(this.session.payload.run_id, {
@@ -22617,41 +21982,13 @@ ${attributionInstructions}
22617
21982
  this.lastReportedBranch = null;
22618
21983
  this.session = null;
22619
21984
  }
22620
- async captureTreeState() {
22621
- if (!this.session?.treeTracker) return;
22622
- try {
22623
- const snapshot = await this.session.treeTracker.captureTree({});
22624
- if (snapshot) {
22625
- const snapshotWithDevice = {
22626
- ...snapshot,
22627
- device: this.session.deviceInfo
22628
- };
22629
- const notification = {
22630
- jsonrpc: "2.0",
22631
- method: POSTHOG_NOTIFICATIONS.TREE_SNAPSHOT,
22632
- params: snapshotWithDevice
22633
- };
22634
- this.broadcastEvent({
22635
- type: "notification",
22636
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
22637
- notification
22638
- });
22639
- this.session.logWriter.appendRawLine(
22640
- this.session.payload.run_id,
22641
- JSON.stringify(notification)
22642
- );
22643
- }
22644
- } catch (error) {
22645
- this.logger.error("Failed to capture tree state", error);
22646
- }
22647
- }
22648
- async captureHandoffCheckpoint() {
22649
- if (!this.session?.treeTracker || !this.session.pendingHandoffGitState) {
21985
+ async captureCheckpointState(localGitState) {
21986
+ if (!this.session || !this.config.repositoryPath) {
22650
21987
  return;
22651
21988
  }
22652
21989
  if (!this.posthogAPI) {
22653
21990
  this.logger.warn(
22654
- "Skipping handoff checkpoint capture: PostHog API client is not configured"
21991
+ "Skipping checkpoint capture: PostHog API client is not configured"
22655
21992
  );
22656
21993
  return;
22657
21994
  }
@@ -22662,9 +21999,7 @@ ${attributionInstructions}
22662
21999
  apiClient: this.posthogAPI,
22663
22000
  logger: this.logger.child("HandoffCheckpoint")
22664
22001
  });
22665
- const checkpoint = await tracker.captureForHandoff(
22666
- this.session.pendingHandoffGitState
22667
- );
22002
+ const checkpoint = await tracker.captureForHandoff(localGitState);
22668
22003
  if (!checkpoint) return;
22669
22004
  const checkpointWithDevice = {
22670
22005
  ...checkpoint,