@posthog/agent 2.3.386 → 2.3.387

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 (44) hide show
  1. package/dist/adapters/claude/session/jsonl-hydration.d.ts +1 -0
  2. package/dist/agent.d.ts +1 -0
  3. package/dist/agent.js +13 -2
  4. package/dist/agent.js.map +1 -1
  5. package/dist/handoff-checkpoint.d.ts +39 -0
  6. package/dist/handoff-checkpoint.js +6679 -0
  7. package/dist/handoff-checkpoint.js.map +1 -0
  8. package/dist/index.d.ts +2 -0
  9. package/dist/index.js +2 -0
  10. package/dist/index.js.map +1 -1
  11. package/dist/posthog-api.d.ts +1 -0
  12. package/dist/posthog-api.js +11 -2
  13. package/dist/posthog-api.js.map +1 -1
  14. package/dist/resume.d.ts +3 -1
  15. package/dist/resume.js +41 -4
  16. package/dist/resume.js.map +1 -1
  17. package/dist/server/agent-server.d.ts +5 -15
  18. package/dist/server/agent-server.js +1330 -394
  19. package/dist/server/agent-server.js.map +1 -1
  20. package/dist/server/bin.cjs +1332 -396
  21. package/dist/server/bin.cjs.map +1 -1
  22. package/dist/server/schemas.d.ts +191 -0
  23. package/dist/server/schemas.js +108 -0
  24. package/dist/server/schemas.js.map +1 -0
  25. package/dist/tree-tracker.d.ts +1 -0
  26. package/dist/tree-tracker.js +18 -4
  27. package/dist/tree-tracker.js.map +1 -1
  28. package/dist/types.d.ts +18 -1
  29. package/dist/types.js +5 -0
  30. package/dist/types.js.map +1 -1
  31. package/package.json +9 -1
  32. package/src/acp-extensions.ts +3 -0
  33. package/src/handoff-checkpoint.test.ts +183 -0
  34. package/src/handoff-checkpoint.ts +361 -0
  35. package/src/posthog-api.test.ts +29 -0
  36. package/src/posthog-api.ts +5 -1
  37. package/src/resume.ts +7 -1
  38. package/src/sagas/apply-snapshot-saga.ts +7 -0
  39. package/src/sagas/capture-tree-saga.ts +10 -3
  40. package/src/sagas/resume-saga.ts +32 -0
  41. package/src/sagas/test-fixtures.ts +46 -0
  42. package/src/server/agent-server.ts +74 -1
  43. package/src/server/schemas.ts +21 -2
  44. package/src/types.ts +24 -0
@@ -809,15 +809,15 @@ 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(path15, isFile2, isDirectory) {
813
- log(`checking %s`, path15);
812
+ function check(path17, isFile2, isDirectory) {
813
+ log(`checking %s`, path17);
814
814
  try {
815
- const stat2 = fs_1.statSync(path15);
816
- if (stat2.isFile() && isFile2) {
815
+ const stat4 = fs_1.statSync(path17);
816
+ if (stat4.isFile() && isFile2) {
817
817
  log(`[OK] path represents a file`);
818
818
  return true;
819
819
  }
820
- if (stat2.isDirectory() && isDirectory) {
820
+ if (stat4.isDirectory() && isDirectory) {
821
821
  log(`[OK] path represents a directory`);
822
822
  return true;
823
823
  }
@@ -832,8 +832,8 @@ var require_src2 = __commonJS({
832
832
  throw e;
833
833
  }
834
834
  }
835
- function exists2(path15, type = exports2.READABLE) {
836
- return check(path15, (type & exports2.FILE) > 0, (type & exports2.FOLDER) > 0);
835
+ function exists2(path17, type = exports2.READABLE) {
836
+ return check(path17, (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(path15) {
932
+ function locateFile(path17) {
933
933
  if (Module["locateFile"]) {
934
- return Module["locateFile"](path15, scriptDirectory);
934
+ return Module["locateFile"](path17, scriptDirectory);
935
935
  }
936
- return scriptDirectory + path15;
936
+ return scriptDirectory + path17;
937
937
  }
938
938
  var readAsync, readBinary;
939
939
  if (ENVIRONMENT_IS_NODE) {
@@ -947,10 +947,10 @@ var require_tree_sitter = __commonJS({
947
947
  };
948
948
  readAsync = (filename, binary2 = true) => {
949
949
  filename = isFileURI(filename) ? new URL(filename) : nodePath.normalize(filename);
950
- return new Promise((resolve6, reject) => {
950
+ return new Promise((resolve7, reject) => {
951
951
  fs.readFile(filename, binary2 ? void 0 : "utf8", (err2, data) => {
952
952
  if (err2) reject(err2);
953
- else resolve6(binary2 ? data.buffer : data);
953
+ else resolve7(binary2 ? data.buffer : data);
954
954
  });
955
955
  });
956
956
  };
@@ -991,13 +991,13 @@ var require_tree_sitter = __commonJS({
991
991
  }
992
992
  readAsync = (url) => {
993
993
  if (isFileURI(url)) {
994
- return new Promise((reject, resolve6) => {
994
+ return new Promise((reject, resolve7) => {
995
995
  var xhr = new XMLHttpRequest();
996
996
  xhr.open("GET", url, true);
997
997
  xhr.responseType = "arraybuffer";
998
998
  xhr.onload = () => {
999
999
  if (xhr.status == 200 || xhr.status == 0 && xhr.response) {
1000
- resolve6(xhr.response);
1000
+ resolve7(xhr.response);
1001
1001
  }
1002
1002
  reject(xhr.status);
1003
1003
  };
@@ -1957,8 +1957,8 @@ var require_tree_sitter = __commonJS({
1957
1957
  }
1958
1958
  var libFile = locateFile(libName2);
1959
1959
  if (flags2.loadAsync) {
1960
- return new Promise(function(resolve6, reject) {
1961
- asyncLoad(libFile, resolve6, reject);
1960
+ return new Promise(function(resolve7, reject) {
1961
+ asyncLoad(libFile, resolve7, reject);
1962
1962
  });
1963
1963
  }
1964
1964
  if (!readBinary) {
@@ -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 fs13 = __require("fs");
3458
- bytes = Promise.resolve(fs13.readFileSync(url));
3457
+ const fs14 = __require("fs");
3458
+ bytes = Promise.resolve(fs14.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 mkdir5, writeFile as writeFile5 } from "fs/promises";
3788
- import { basename as basename2, join as join12 } from "path";
3787
+ import { mkdir as mkdir8, writeFile as writeFile6 } from "fs/promises";
3788
+ import { basename as basename2, join as join14 } 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(path15) {
3839
- return path15 instanceof String && cache.has(path15);
3838
+ function isPathSpec(path17) {
3839
+ return path17 instanceof String && cache.has(path17);
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(path15) {
3929
- return (0, import_file_exists.exists)(path15, import_file_exists.FOLDER);
3928
+ function folderExists(path17) {
3929
+ return (0, import_file_exists.exists)(path17, 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(path15) {
4334
- return /^\.(git)?$/.test(path15.trim());
4333
+ parser(path17) {
4334
+ return /^\.(git)?$/.test(path17.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 [path15, line, preview] = input.split(NULL);
4769
- paths.add(path15);
4770
- (results[path15] = results[path15] || []).push({
4768
+ const [path17, line, preview] = input.split(NULL);
4769
+ paths.add(path17);
4770
+ (results[path17] = results[path17] || []).push({
4771
4771
  line: asNumber(line),
4772
- path: path15,
4772
+ path: path17,
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, path15, text2) {
5537
+ function parseInit(bare, path17, text2) {
5538
5538
  const response = String(text2).trim();
5539
5539
  let result;
5540
5540
  if (result = initResponseRegex.exec(response)) {
5541
- return new InitSummary(bare, path15, false, result[1]);
5541
+ return new InitSummary(bare, path17, false, result[1]);
5542
5542
  }
5543
5543
  if (result = reInitResponseRegex.exec(response)) {
5544
- return new InitSummary(bare, path15, true, result[1]);
5544
+ return new InitSummary(bare, path17, true, result[1]);
5545
5545
  }
5546
5546
  let gitDir = "";
5547
5547
  const tokens = response.split(" ");
@@ -5552,7 +5552,7 @@ function parseInit(bare, path15, text2) {
5552
5552
  break;
5553
5553
  }
5554
5554
  }
5555
- return new InitSummary(bare, path15, /^re/i.test(response), gitDir);
5555
+ return new InitSummary(bare, path17, /^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, path15, existing, gitDir) {
5564
+ constructor(bare, path17, existing, gitDir) {
5565
5565
  this.bare = bare;
5566
- this.path = path15;
5566
+ this.path = path17;
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, path15, customArgs) {
5578
+ function initTask(bare = false, path17, 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, path15, customArgs) {
5584
5584
  commands,
5585
5585
  format: "utf-8",
5586
5586
  parser(text2) {
5587
- return parseInit(commands.includes("--bare"), path15, text2);
5587
+ return parseInit(commands.includes("--bare"), path17, 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(path15, index, working_dir) {
6404
- this.path = path15;
6403
+ constructor(path17, index, working_dir) {
6404
+ this.path = path17;
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(path15) || [null, path15, path15];
6408
+ const detail = fromPathRegex.exec(path17) || [null, path17, path17];
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, path15) {
6439
+ function data(index, workingDir, path17) {
6440
6440
  const raw = `${index}${workingDir}`;
6441
6441
  const handler = parsers6.get(raw);
6442
6442
  if (handler) {
6443
- handler(result, path15);
6443
+ handler(result, path17);
6444
6444
  }
6445
6445
  if (raw !== "##" && raw !== "!!") {
6446
- result.files.push(new FileStatusSummary(path15, index, workingDir));
6446
+ result.files.push(new FileStatusSummary(path17, 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(path15, write) {
6759
+ hashObject(path17, write) {
6760
6760
  return this._runTask(
6761
- hashObjectTask(path15, write === true),
6761
+ hashObjectTask(path17, 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 path15 = input.trim().replace(/^["']|["']$/g, "");
7115
- return path15 && normalize(path15);
7114
+ const path17 = input.trim().replace(/^["']|["']$/g, "");
7115
+ return path17 && normalize(path17);
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, path15) {
7430
- return subModuleTask(["add", repo, path15]);
7429
+ function addSubModuleTask(repo, path17) {
7430
+ return subModuleTask(["add", repo, path17]);
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, path15, then) {
7761
- return this._runTask(addSubModuleTask2(repo, path15), trailingFunctionArgument2(arguments));
7760
+ Git2.prototype.submoduleAdd = function(repo, path17, then) {
7761
+ return this._runTask(addSubModuleTask2(repo, path17), trailingFunctionArgument2(arguments));
7762
7762
  };
7763
7763
  Git2.prototype.submoduleUpdate = function(args2, then) {
7764
7764
  return this._runTask(
@@ -8374,10 +8374,10 @@ async function getIndexLockPath(repoPath) {
8374
8374
  async function getLockInfo(repoPath) {
8375
8375
  const lockPath = await getIndexLockPath(repoPath);
8376
8376
  try {
8377
- const stat2 = await fs2.stat(lockPath);
8377
+ const stat4 = await fs2.stat(lockPath);
8378
8378
  return {
8379
8379
  path: lockPath,
8380
- ageMs: Date.now() - stat2.mtimeMs
8380
+ ageMs: Date.now() - stat4.mtimeMs
8381
8381
  };
8382
8382
  } catch {
8383
8383
  return null;
@@ -8412,10 +8412,10 @@ var AsyncReaderWriterLock = class {
8412
8412
  this.readers++;
8413
8413
  return;
8414
8414
  }
8415
- return new Promise((resolve6) => {
8415
+ return new Promise((resolve7) => {
8416
8416
  this.readQueue.push(() => {
8417
8417
  this.readers++;
8418
- resolve6();
8418
+ resolve7();
8419
8419
  });
8420
8420
  });
8421
8421
  }
@@ -8429,11 +8429,11 @@ var AsyncReaderWriterLock = class {
8429
8429
  return;
8430
8430
  }
8431
8431
  this.writerWaiting = true;
8432
- return new Promise((resolve6) => {
8432
+ return new Promise((resolve7) => {
8433
8433
  this.writeQueue.push(() => {
8434
8434
  this.writerWaiting = this.writeQueue.length > 0;
8435
8435
  this.writer = true;
8436
- resolve6();
8436
+ resolve7();
8437
8437
  });
8438
8438
  });
8439
8439
  }
@@ -8605,7 +8605,7 @@ import { z as z4 } from "zod";
8605
8605
  // package.json
8606
8606
  var package_default = {
8607
8607
  name: "@posthog/agent",
8608
- version: "2.3.386",
8608
+ version: "2.3.387",
8609
8609
  repository: "https://github.com/PostHog/code",
8610
8610
  description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
8611
8611
  exports: {
@@ -8665,6 +8665,10 @@ var package_default = {
8665
8665
  types: "./dist/resume.d.ts",
8666
8666
  import: "./dist/resume.js"
8667
8667
  },
8668
+ "./handoff-checkpoint": {
8669
+ types: "./dist/handoff-checkpoint.d.ts",
8670
+ import: "./dist/handoff-checkpoint.js"
8671
+ },
8668
8672
  "./tree-tracker": {
8669
8673
  types: "./dist/tree-tracker.d.ts",
8670
8674
  import: "./dist/tree-tracker.js"
@@ -8672,6 +8676,10 @@ var package_default = {
8672
8676
  "./server": {
8673
8677
  types: "./dist/server/agent-server.d.ts",
8674
8678
  import: "./dist/server/agent-server.js"
8679
+ },
8680
+ "./server/schemas": {
8681
+ types: "./dist/server/schemas.d.ts",
8682
+ import: "./dist/server/schemas.js"
8675
8683
  }
8676
8684
  },
8677
8685
  bin: {
@@ -8762,6 +8770,8 @@ var POSTHOG_NOTIFICATIONS = {
8762
8770
  SDK_SESSION: "_posthog/sdk_session",
8763
8771
  /** Tree state snapshot captured (git tree hash + file archive) */
8764
8772
  TREE_SNAPSHOT: "_posthog/tree_snapshot",
8773
+ /** Git checkpoint captured for handoff */
8774
+ GIT_CHECKPOINT: "_posthog/git_checkpoint",
8765
8775
  /** Agent mode changed (interactive/background) */
8766
8776
  MODE_CHANGE: "_posthog/mode_change",
8767
8777
  /** Request to resume a session from previous state */
@@ -8868,17 +8878,17 @@ var Pushable = class {
8868
8878
  resolvers = [];
8869
8879
  done = false;
8870
8880
  push(item) {
8871
- const resolve6 = this.resolvers.shift();
8872
- if (resolve6) {
8873
- resolve6({ value: item, done: false });
8881
+ const resolve7 = this.resolvers.shift();
8882
+ if (resolve7) {
8883
+ resolve7({ value: item, done: false });
8874
8884
  } else {
8875
8885
  this.queue.push(item);
8876
8886
  }
8877
8887
  }
8878
8888
  end() {
8879
8889
  this.done = true;
8880
- for (const resolve6 of this.resolvers) {
8881
- resolve6({ value: void 0, done: true });
8890
+ for (const resolve7 of this.resolvers) {
8891
+ resolve7({ value: void 0, done: true });
8882
8892
  }
8883
8893
  this.resolvers = [];
8884
8894
  }
@@ -8895,8 +8905,8 @@ var Pushable = class {
8895
8905
  done: true
8896
8906
  });
8897
8907
  }
8898
- return new Promise((resolve6) => {
8899
- this.resolvers.push(resolve6);
8908
+ return new Promise((resolve7) => {
8909
+ this.resolvers.push(resolve7);
8900
8910
  });
8901
8911
  }
8902
8912
  };
@@ -9010,20 +9020,20 @@ function nodeReadableToWebReadable(nodeStream) {
9010
9020
  function nodeWritableToWebWritable(nodeStream) {
9011
9021
  return new WritableStream2({
9012
9022
  write(chunk) {
9013
- return new Promise((resolve6, reject) => {
9023
+ return new Promise((resolve7, reject) => {
9014
9024
  const ok = nodeStream.write(Buffer.from(chunk), (err2) => {
9015
9025
  if (err2) reject(err2);
9016
9026
  });
9017
9027
  if (ok) {
9018
- resolve6();
9028
+ resolve7();
9019
9029
  } else {
9020
- nodeStream.once("drain", resolve6);
9030
+ nodeStream.once("drain", resolve7);
9021
9031
  }
9022
9032
  });
9023
9033
  },
9024
9034
  close() {
9025
- return new Promise((resolve6) => {
9026
- nodeStream.end(resolve6);
9035
+ return new Promise((resolve7) => {
9036
+ nodeStream.end(resolve7);
9027
9037
  });
9028
9038
  },
9029
9039
  abort(reason) {
@@ -12970,9 +12980,9 @@ var PostHogEnricher = class {
12970
12980
  }
12971
12981
  let mtimeMs = 0;
12972
12982
  try {
12973
- const stat2 = await fs4.stat(absPath);
12974
- mtimeMs = stat2.mtimeMs;
12975
- if (stat2.size > MAX_WRAPPER_SOURCE_BYTES) {
12983
+ const stat22 = await fs4.stat(absPath);
12984
+ mtimeMs = stat22.mtimeMs;
12985
+ if (stat22.size > MAX_WRAPPER_SOURCE_BYTES) {
12976
12986
  return this.setWrapperCache(absPath, mtimeMs, []);
12977
12987
  }
12978
12988
  } catch {
@@ -13138,7 +13148,7 @@ async function buildWrapperContext(deps, content, langId, absPath) {
13138
13148
  // src/utils/common.ts
13139
13149
  async function withTimeout(operation, timeoutMs) {
13140
13150
  const timeoutPromise = new Promise(
13141
- (resolve6) => setTimeout(() => resolve6({ result: "timeout" }), timeoutMs)
13151
+ (resolve7) => setTimeout(() => resolve7({ result: "timeout" }), timeoutMs)
13142
13152
  );
13143
13153
  const operationPromise = operation.then((value) => ({
13144
13154
  result: "success",
@@ -13436,8 +13446,8 @@ var ToolContentBuilder = class {
13436
13446
  this.items.push({ type: "content", content: image(data, mimeType, uri) });
13437
13447
  return this;
13438
13448
  }
13439
- diff(path15, oldText, newText) {
13440
- this.items.push({ type: "diff", path: path15, oldText, newText });
13449
+ diff(path17, oldText, newText) {
13450
+ this.items.push({ type: "diff", path: path17, oldText, newText });
13441
13451
  return this;
13442
13452
  }
13443
13453
  build() {
@@ -13624,7 +13634,7 @@ function buildToolKey(serverName, toolName) {
13624
13634
  return `mcp__${serverName}__${toolName}`;
13625
13635
  }
13626
13636
  function delay2(ms) {
13627
- return new Promise((resolve6) => setTimeout(resolve6, ms));
13637
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
13628
13638
  }
13629
13639
  async function fetchMcpToolMetadata(q, logger = new Logger({ debug: false, prefix: "[McpToolMetadata]" })) {
13630
13640
  let retries = 0;
@@ -15945,8 +15955,8 @@ var AsyncMutex = class {
15945
15955
  this.locked = true;
15946
15956
  return;
15947
15957
  }
15948
- return new Promise((resolve6) => {
15949
- this.queue.push(resolve6);
15958
+ return new Promise((resolve7) => {
15959
+ this.queue.push(resolve7);
15950
15960
  });
15951
15961
  }
15952
15962
  release() {
@@ -16473,8 +16483,8 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
16473
16483
  if (this.session.promptRunning) {
16474
16484
  this.session.input.push(userMessage);
16475
16485
  const order = this.session.nextPendingOrder++;
16476
- const cancelled = await new Promise((resolve6) => {
16477
- this.session.pendingMessages.set(promptUuid, { resolve: resolve6, order });
16486
+ const cancelled = await new Promise((resolve7) => {
16487
+ this.session.pendingMessages.set(promptUuid, { resolve: resolve7, order });
16478
16488
  });
16479
16489
  if (cancelled) {
16480
16490
  return { stopReason: "cancelled" };
@@ -17355,7 +17365,7 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
17355
17365
  */
17356
17366
  deferBackgroundFetches(q) {
17357
17367
  Promise.all([
17358
- new Promise((resolve6) => setTimeout(resolve6, 10)).then(
17368
+ new Promise((resolve7) => setTimeout(resolve7, 10)).then(
17359
17369
  () => this.sendAvailableCommandsUpdate()
17360
17370
  ),
17361
17371
  fetchMcpToolMetadata(q, this.logger).then(() => {
@@ -18323,245 +18333,19 @@ function createCodexConnection(config) {
18323
18333
  };
18324
18334
  }
18325
18335
 
18326
- // src/utils/gateway.ts
18327
- function getGatewayBaseUrl(posthogHost) {
18328
- const url = new URL(posthogHost);
18329
- const hostname = url.hostname;
18330
- if (hostname === "localhost" || hostname === "127.0.0.1") {
18331
- return `${url.protocol}//localhost:3308`;
18332
- }
18333
- if (hostname === "host.docker.internal") {
18334
- return `${url.protocol}//host.docker.internal:3308`;
18335
- }
18336
- const region = hostname.match(/^(us|eu)\.posthog\.com$/)?.[1] ?? "us";
18337
- return `https://gateway.${region}.posthog.com`;
18338
- }
18339
- function getLlmGatewayUrl(posthogHost, product = "posthog_code") {
18340
- return `${getGatewayBaseUrl(posthogHost)}/${product}`;
18341
- }
18336
+ // src/handoff-checkpoint.ts
18337
+ import { mkdir as mkdir4, readFile as readFile4, rm as rm4, writeFile as writeFile2 } from "fs/promises";
18338
+ import { join as join9 } from "path";
18342
18339
 
18343
- // src/posthog-api.ts
18344
- var DEFAULT_USER_AGENT = `posthog/agent.hog.dev; version: ${package_default.version}`;
18345
- var PostHogAPIClient = class {
18346
- config;
18347
- constructor(config) {
18348
- this.config = config;
18349
- }
18350
- get baseUrl() {
18351
- const host = this.config.apiUrl.endsWith("/") ? this.config.apiUrl.slice(0, -1) : this.config.apiUrl;
18352
- return host;
18353
- }
18354
- isAuthFailure(status) {
18355
- return status === 401 || status === 403;
18356
- }
18357
- async resolveApiKey(forceRefresh = false) {
18358
- if (forceRefresh && this.config.refreshApiKey) {
18359
- return this.config.refreshApiKey();
18360
- }
18361
- return this.config.getApiKey();
18362
- }
18363
- async buildHeaders(options, forceRefresh = false) {
18364
- const headers = new Headers(options.headers);
18365
- headers.set(
18366
- "Authorization",
18367
- `Bearer ${await this.resolveApiKey(forceRefresh)}`
18368
- );
18369
- headers.set("Content-Type", "application/json");
18370
- headers.set("User-Agent", this.config.userAgent ?? DEFAULT_USER_AGENT);
18371
- return headers;
18372
- }
18373
- async performRequest(endpoint, options, forceRefresh = false) {
18374
- const url = `${this.baseUrl}${endpoint}`;
18375
- return fetch(url, {
18376
- ...options,
18377
- headers: await this.buildHeaders(options, forceRefresh)
18378
- });
18379
- }
18380
- async performRequestWithRetry(endpoint, options = {}) {
18381
- let response = await this.performRequest(endpoint, options);
18382
- if (!response.ok && this.isAuthFailure(response.status)) {
18383
- response = await this.performRequest(endpoint, options, true);
18384
- }
18385
- return response;
18386
- }
18387
- async apiRequest(endpoint, options = {}) {
18388
- const response = await this.performRequestWithRetry(endpoint, options);
18389
- if (!response.ok) {
18390
- let errorMessage;
18391
- try {
18392
- const errorResponse = await response.json();
18393
- errorMessage = `Failed request: [${response.status}] ${JSON.stringify(errorResponse)}`;
18394
- } catch {
18395
- errorMessage = `Failed request: [${response.status}] ${response.statusText}`;
18396
- }
18397
- throw new Error(errorMessage);
18398
- }
18399
- return response.json();
18400
- }
18401
- getTeamId() {
18402
- return this.config.projectId;
18403
- }
18404
- async getApiKey(forceRefresh = false) {
18405
- return this.resolveApiKey(forceRefresh);
18406
- }
18407
- getLlmGatewayUrl() {
18408
- return getLlmGatewayUrl(this.baseUrl);
18409
- }
18410
- async getTask(taskId) {
18411
- const teamId = this.getTeamId();
18412
- return this.apiRequest(`/api/projects/${teamId}/tasks/${taskId}/`);
18413
- }
18414
- async getTaskRun(taskId, runId) {
18415
- const teamId = this.getTeamId();
18416
- return this.apiRequest(
18417
- `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/`
18418
- );
18419
- }
18420
- async updateTaskRun(taskId, runId, payload) {
18421
- const teamId = this.getTeamId();
18422
- return this.apiRequest(
18423
- `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/`,
18424
- {
18425
- method: "PATCH",
18426
- body: JSON.stringify(payload)
18427
- }
18428
- );
18429
- }
18430
- async setTaskRunOutput(taskId, runId, output) {
18431
- return this.apiRequest(
18432
- `/api/projects/${this.getTeamId()}/tasks/${taskId}/runs/${runId}/set_output/`,
18433
- {
18434
- method: "PATCH",
18435
- body: JSON.stringify(output)
18436
- }
18437
- );
18438
- }
18439
- async appendTaskRunLog(taskId, runId, entries) {
18440
- const teamId = this.getTeamId();
18441
- return this.apiRequest(
18442
- `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/append_log/`,
18443
- {
18444
- method: "POST",
18445
- body: JSON.stringify({ entries })
18446
- }
18447
- );
18448
- }
18449
- async relayMessage(taskId, runId, text2) {
18450
- const teamId = this.getTeamId();
18451
- await this.apiRequest(
18452
- `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/relay_message/`,
18453
- {
18454
- method: "POST",
18455
- body: JSON.stringify({ text: text2 })
18456
- }
18457
- );
18458
- }
18459
- async uploadTaskArtifacts(taskId, runId, artifacts) {
18460
- if (!artifacts.length) {
18461
- return [];
18462
- }
18463
- const teamId = this.getTeamId();
18464
- const response = await this.apiRequest(
18465
- `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/artifacts/`,
18466
- {
18467
- method: "POST",
18468
- body: JSON.stringify({ artifacts })
18469
- }
18470
- );
18471
- return response.artifacts ?? [];
18472
- }
18473
- /**
18474
- * Download artifact content by storage path
18475
- * Streams the file through the PostHog backend so the sandbox does not need
18476
- * direct access to object storage.
18477
- */
18478
- async downloadArtifact(taskId, runId, storagePath) {
18479
- const teamId = this.getTeamId();
18480
- try {
18481
- const response = await this.performRequestWithRetry(
18482
- `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/artifacts/download/`,
18483
- {
18484
- method: "POST",
18485
- body: JSON.stringify({ storage_path: storagePath })
18486
- }
18487
- );
18488
- if (!response.ok) {
18489
- throw new Error(`Failed to download artifact: ${response.status}`);
18490
- }
18491
- return response.arrayBuffer();
18492
- } catch {
18493
- return null;
18494
- }
18495
- }
18496
- /**
18497
- * Fetch logs for a task run via the logs API endpoint
18498
- * @param taskRun - The task run to fetch logs for
18499
- * @returns Array of stored entries, or empty array if no logs available
18500
- */
18501
- async fetchTaskRunLogs(taskRun) {
18502
- const teamId = this.getTeamId();
18503
- const endpoint = `/api/projects/${teamId}/tasks/${taskRun.task}/runs/${taskRun.id}/logs`;
18504
- try {
18505
- const response = await this.performRequestWithRetry(endpoint);
18506
- if (!response.ok) {
18507
- if (response.status === 404) {
18508
- return [];
18509
- }
18510
- throw new Error(
18511
- `Failed to fetch logs: ${response.status} ${response.statusText}`
18512
- );
18513
- }
18514
- const content = await response.text();
18515
- if (!content.trim()) {
18516
- return [];
18517
- }
18518
- return content.trim().split("\n").map((line) => JSON.parse(line));
18519
- } catch (error) {
18520
- throw new Error(
18521
- `Failed to fetch task run logs: ${error instanceof Error ? error.message : String(error)}`
18522
- );
18523
- }
18524
- }
18525
- };
18340
+ // ../git/dist/handoff.js
18341
+ import { spawn as spawn4 } from "child_process";
18342
+ import { copyFile, mkdir as mkdir3, readFile as readFile3, rm as rm3, stat as stat3 } from "fs/promises";
18343
+ import path13 from "path";
18526
18344
 
18527
- // src/adapters/claude/session/jsonl-hydration.ts
18345
+ // ../git/dist/sagas/checkpoint.js
18528
18346
  import { randomUUID as randomUUID2 } from "crypto";
18529
18347
  import * as fs10 from "fs/promises";
18530
- import * as os6 from "os";
18531
18348
  import * as path12 from "path";
18532
- var CHARS_PER_TOKEN = 4;
18533
- var DEFAULT_MAX_TOKENS = 15e4;
18534
- function estimateTurnTokens(turn) {
18535
- let chars = 0;
18536
- for (const block of turn.content) {
18537
- if ("text" in block && typeof block.text === "string") {
18538
- chars += block.text.length;
18539
- }
18540
- }
18541
- if (turn.toolCalls) {
18542
- for (const tc of turn.toolCalls) {
18543
- chars += JSON.stringify(tc.input ?? "").length;
18544
- if (tc.result !== void 0) {
18545
- chars += typeof tc.result === "string" ? tc.result.length : JSON.stringify(tc.result).length;
18546
- }
18547
- }
18548
- }
18549
- return Math.ceil(chars / CHARS_PER_TOKEN);
18550
- }
18551
- function selectRecentTurns(turns, maxTokens = DEFAULT_MAX_TOKENS) {
18552
- let budget = maxTokens;
18553
- let startIndex = turns.length;
18554
- for (let i2 = turns.length - 1; i2 >= 0; i2--) {
18555
- const cost = estimateTurnTokens(turns[i2]);
18556
- if (cost > budget) break;
18557
- budget -= cost;
18558
- startIndex = i2;
18559
- }
18560
- while (startIndex < turns.length && turns[startIndex].role !== "user") {
18561
- startIndex++;
18562
- }
18563
- return turns.slice(startIndex);
18564
- }
18565
18349
 
18566
18350
  // ../shared/dist/index.js
18567
18351
  var CLOUD_PROMPT_PREFIX = "__twig_cloud_prompt_v1__:";
@@ -18705,16 +18489,6 @@ var Saga = class {
18705
18489
  }
18706
18490
  };
18707
18491
 
18708
- // src/sagas/apply-snapshot-saga.ts
18709
- import { mkdir as mkdir4, rm as rm3, writeFile as writeFile4 } from "fs/promises";
18710
- import { join as join10 } from "path";
18711
-
18712
- // ../git/dist/sagas/tree.js
18713
- import { existsSync as existsSync5 } from "fs";
18714
- import * as fs11 from "fs/promises";
18715
- import * as path13 from "path";
18716
- import * as tar from "tar";
18717
-
18718
18492
  // ../git/dist/git-saga.js
18719
18493
  var GitSaga = class extends Saga {
18720
18494
  _git = null;
@@ -18733,20 +18507,1082 @@ var GitSaga = class extends Saga {
18733
18507
  }
18734
18508
  };
18735
18509
 
18736
- // ../git/dist/sagas/tree.js
18737
- var CaptureTreeSaga = class extends GitSaga {
18738
- sagaName = "CaptureTreeSaga";
18739
- tempIndexPath = null;
18510
+ // ../git/dist/sagas/checkpoint.js
18511
+ var CHECKPOINT_REF_PREFIX = "refs/posthog-code-checkpoint/";
18512
+ var CHECKPOINT_VERSION = "v1";
18513
+ var UNMERGED_INDEX_ERROR = "Cannot capture checkpoint with unresolved merge conflicts in the index";
18514
+ var GIT_BUSY_ERROR = "Cannot capture checkpoint while git operation is in progress";
18515
+ var CHECKPOINT_AUTHOR = {
18516
+ name: "PostHog Code",
18517
+ email: "posthog-code@local"
18518
+ };
18519
+ var CaptureCheckpointSaga = class extends GitSaga {
18520
+ sagaName = "CaptureCheckpointSaga";
18740
18521
  async executeGitOperations(input) {
18741
- const { baseDir, lastTreeHash, archivePath, signal } = input;
18742
- const tmpDir = path13.join(baseDir, ".git", "posthog-code-tmp");
18743
- await this.step({
18744
- name: "create_tmp_dir",
18745
- execute: () => fs11.mkdir(tmpDir, { recursive: true }),
18522
+ const { baseDir } = input;
18523
+ const headInfo = await this.readOnlyStep("get_head_info", () => getHeadInfo(this.git));
18524
+ const busyState = await this.readOnlyStep("check_git_busy", () => getGitBusyState(this.git));
18525
+ if (busyState.busy) {
18526
+ throw new Error(`${GIT_BUSY_ERROR}: ${busyState.operation}`);
18527
+ }
18528
+ const hasUnmerged = await this.readOnlyStep("check_unmerged_index", () => hasUnmergedEntries(this.git));
18529
+ if (hasUnmerged) {
18530
+ throw new Error(UNMERGED_INDEX_ERROR);
18531
+ }
18532
+ const indexTree = await this.readOnlyStep("write_index_tree", () => this.git.raw(["write-tree"]));
18533
+ const worktreeTree = await this.readOnlyStep("write_worktree_tree", () => createWorktreeTree(this.git, baseDir, headInfo.head));
18534
+ const metaTree = await this.readOnlyStep("write_meta_tree", () => createMetaTree(this.git, baseDir, indexTree.trim(), worktreeTree.trim()));
18535
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
18536
+ const message = formatCheckpointMessage({
18537
+ head: headInfo.head,
18538
+ branch: headInfo.branch,
18539
+ indexTree: indexTree.trim(),
18540
+ worktreeTree: worktreeTree.trim(),
18541
+ timestamp
18542
+ });
18543
+ const commitHash = await this.step({
18544
+ name: "create_checkpoint_commit",
18545
+ execute: async () => {
18546
+ const commitGit = this.git.env({
18547
+ ...process.env,
18548
+ GIT_AUTHOR_NAME: CHECKPOINT_AUTHOR.name,
18549
+ GIT_AUTHOR_EMAIL: CHECKPOINT_AUTHOR.email,
18550
+ GIT_COMMITTER_NAME: CHECKPOINT_AUTHOR.name,
18551
+ GIT_COMMITTER_EMAIL: CHECKPOINT_AUTHOR.email
18552
+ });
18553
+ const rawCommit = await commitGit.raw([
18554
+ "commit-tree",
18555
+ metaTree.trim(),
18556
+ ...headInfo.head ? ["-p", headInfo.head] : [],
18557
+ "-m",
18558
+ message
18559
+ ]);
18560
+ return rawCommit.trim();
18561
+ },
18746
18562
  rollback: async () => {
18747
18563
  }
18748
18564
  });
18749
- this.tempIndexPath = path13.join(tmpDir, `index-${Date.now()}`);
18565
+ const checkpointId = input.checkpointId ?? randomUUID2();
18566
+ const refName = `${CHECKPOINT_REF_PREFIX}${checkpointId}`;
18567
+ const existingRef = await this.readOnlyStep("check_existing_ref", async () => {
18568
+ try {
18569
+ await this.git.revparse(["--verify", refName]);
18570
+ return true;
18571
+ } catch {
18572
+ return false;
18573
+ }
18574
+ });
18575
+ if (existingRef) {
18576
+ throw new Error(`Checkpoint ref already exists: ${refName}`);
18577
+ }
18578
+ await this.step({
18579
+ name: "update_checkpoint_ref",
18580
+ execute: () => this.git.raw(["update-ref", refName, commitHash]),
18581
+ rollback: async () => {
18582
+ await this.git.raw(["update-ref", "-d", refName]).catch(() => {
18583
+ });
18584
+ }
18585
+ });
18586
+ return {
18587
+ checkpointId,
18588
+ commit: commitHash,
18589
+ head: headInfo.head,
18590
+ branch: headInfo.branch,
18591
+ indexTree: indexTree.trim(),
18592
+ worktreeTree: worktreeTree.trim(),
18593
+ timestamp
18594
+ };
18595
+ }
18596
+ };
18597
+ async function getHeadInfo(git) {
18598
+ let head = null;
18599
+ let branch = null;
18600
+ try {
18601
+ head = (await git.revparse(["HEAD"]))?.trim() || null;
18602
+ } catch {
18603
+ head = null;
18604
+ }
18605
+ try {
18606
+ const rawBranch = await git.raw(["symbolic-ref", "--short", "HEAD"]);
18607
+ branch = rawBranch.trim() || null;
18608
+ } catch {
18609
+ branch = null;
18610
+ }
18611
+ return { head, branch };
18612
+ }
18613
+ async function hasUnmergedEntries(git) {
18614
+ const output = await git.raw(["ls-files", "--unmerged"]);
18615
+ return output.trim().length > 0;
18616
+ }
18617
+ async function getGitBusyState(git) {
18618
+ const toplevel = (await git.raw(["rev-parse", "--show-toplevel"])).trim();
18619
+ const resolveGitPath = async (gitPath) => {
18620
+ const relative = (await git.raw(["rev-parse", "--git-path", gitPath])).trim();
18621
+ return path12.isAbsolute(relative) ? relative : path12.resolve(toplevel, relative);
18622
+ };
18623
+ const pathExists = async (gitPath) => {
18624
+ const resolved = await resolveGitPath(gitPath);
18625
+ try {
18626
+ await fs10.access(resolved);
18627
+ return true;
18628
+ } catch {
18629
+ return false;
18630
+ }
18631
+ };
18632
+ const dirExists = async (gitPath) => {
18633
+ const resolved = await resolveGitPath(gitPath);
18634
+ try {
18635
+ const stat4 = await fs10.stat(resolved);
18636
+ return stat4.isDirectory();
18637
+ } catch {
18638
+ return false;
18639
+ }
18640
+ };
18641
+ if (await dirExists("rebase-merge") || await dirExists("rebase-apply")) {
18642
+ return { busy: true, operation: "rebase" };
18643
+ }
18644
+ if (await pathExists("MERGE_HEAD")) {
18645
+ return { busy: true, operation: "merge" };
18646
+ }
18647
+ if (await pathExists("CHERRY_PICK_HEAD")) {
18648
+ return { busy: true, operation: "cherry-pick" };
18649
+ }
18650
+ if (await pathExists("REVERT_HEAD")) {
18651
+ return { busy: true, operation: "revert" };
18652
+ }
18653
+ return { busy: false };
18654
+ }
18655
+ async function createWorktreeTree(git, baseDir, head) {
18656
+ const { tempGit, tempIndexPath } = await createTempIndexGit(git, baseDir, "checkpoint-worktree");
18657
+ try {
18658
+ if (head) {
18659
+ await tempGit.raw(["read-tree", head]);
18660
+ } else {
18661
+ await tempGit.raw(["read-tree", "--empty"]);
18662
+ }
18663
+ await tempGit.raw(["add", "-A", "--", "."]);
18664
+ const treeHash = await tempGit.raw(["write-tree"]);
18665
+ return treeHash.trim();
18666
+ } finally {
18667
+ await fs10.rm(tempIndexPath, { force: true }).catch(() => {
18668
+ });
18669
+ }
18670
+ }
18671
+ async function createMetaTree(git, baseDir, indexTree, worktreeTree) {
18672
+ const { tempGit, tempIndexPath } = await createTempIndexGit(git, baseDir, "checkpoint-meta");
18673
+ try {
18674
+ await tempGit.raw(["read-tree", "--empty"]);
18675
+ await tempGit.raw([
18676
+ "update-index",
18677
+ "--add",
18678
+ "--cacheinfo",
18679
+ "040000",
18680
+ indexTree,
18681
+ "index"
18682
+ ]);
18683
+ await tempGit.raw([
18684
+ "update-index",
18685
+ "--add",
18686
+ "--cacheinfo",
18687
+ "040000",
18688
+ worktreeTree,
18689
+ "worktree"
18690
+ ]);
18691
+ const metaTree = await tempGit.raw(["write-tree"]);
18692
+ return metaTree.trim();
18693
+ } finally {
18694
+ await fs10.rm(tempIndexPath, { force: true }).catch(() => {
18695
+ });
18696
+ }
18697
+ }
18698
+ function formatCheckpointMessage(meta) {
18699
+ return [
18700
+ `POSTHOG-CODE-CHECKPOINT ${CHECKPOINT_VERSION}`,
18701
+ `head=${meta.head ?? "null"}`,
18702
+ `branch=${meta.branch ?? "null"}`,
18703
+ `index=${meta.indexTree}`,
18704
+ `worktree=${meta.worktreeTree}`,
18705
+ `timestamp=${meta.timestamp}`
18706
+ ].join("\n");
18707
+ }
18708
+ async function getGitCommonDir(git, baseDir) {
18709
+ const raw = await git.raw(["rev-parse", "--git-common-dir"]);
18710
+ const dir = raw.trim() || ".git";
18711
+ return path12.isAbsolute(dir) ? dir : path12.resolve(baseDir, dir);
18712
+ }
18713
+ async function createTempIndexGit(git, baseDir, label) {
18714
+ const tmpDir = path12.join(await getGitCommonDir(git, baseDir), "posthog-code-tmp");
18715
+ await fs10.mkdir(tmpDir, { recursive: true });
18716
+ const tempIndexPath = path12.join(tmpDir, `${label}-${Date.now()}-${randomUUID2()}`);
18717
+ const tempGit = createGitClient(baseDir).env({
18718
+ ...process.env,
18719
+ GIT_INDEX_FILE: tempIndexPath
18720
+ });
18721
+ return { tempGit, tempIndexPath };
18722
+ }
18723
+ async function refExists(git, refName) {
18724
+ try {
18725
+ await git.revparse(["--verify", refName]);
18726
+ return true;
18727
+ } catch {
18728
+ return false;
18729
+ }
18730
+ }
18731
+ async function deleteCheckpoint(git, checkpointId) {
18732
+ const refName = `${CHECKPOINT_REF_PREFIX}${checkpointId}`;
18733
+ const exists2 = await refExists(git, refName);
18734
+ if (!exists2) {
18735
+ throw new Error(`Checkpoint not found: ${checkpointId}`);
18736
+ }
18737
+ await git.raw(["update-ref", "-d", refName]);
18738
+ }
18739
+
18740
+ // ../git/dist/handoff.js
18741
+ var HANDOFF_HEAD_REF_PREFIX = "refs/posthog-code-handoff/head/";
18742
+ var CHECKPOINT_REF_PREFIX2 = "refs/posthog-code-checkpoint/";
18743
+ var GitHandoffTracker = class {
18744
+ repositoryPath;
18745
+ logger;
18746
+ constructor(config) {
18747
+ this.repositoryPath = config.repositoryPath;
18748
+ this.logger = config.logger;
18749
+ }
18750
+ async captureForHandoff(localGitState) {
18751
+ const captureSaga = new CaptureCheckpointSaga(this.logger);
18752
+ const result = await captureSaga.run({ baseDir: this.repositoryPath });
18753
+ if (!result.success) {
18754
+ throw new Error(`Failed to capture checkpoint at step '${result.failedStep}': ${result.error}`);
18755
+ }
18756
+ const checkpoint = result.data;
18757
+ const git = createGitClient(this.repositoryPath);
18758
+ const tempDir = await this.getTempDir(git);
18759
+ const checkpointRef = `${CHECKPOINT_REF_PREFIX2}${checkpoint.checkpointId}`;
18760
+ const shouldIncludeHead = !!checkpoint.head && checkpoint.head !== localGitState?.head;
18761
+ const headRef = shouldIncludeHead ? `${HANDOFF_HEAD_REF_PREFIX}${checkpoint.checkpointId}` : void 0;
18762
+ const packPrefix = path13.join(tempDir, checkpoint.checkpointId);
18763
+ try {
18764
+ const [headPack, indexFile, tracking] = await Promise.all([
18765
+ shouldIncludeHead && checkpoint.head ? this.captureHeadPack(packPrefix, checkpoint.head) : Promise.resolve(void 0),
18766
+ this.copyIndexFile(git, checkpoint.checkpointId),
18767
+ getTrackingMetadata(git, checkpoint.branch)
18768
+ ]);
18769
+ return {
18770
+ checkpoint: {
18771
+ checkpointId: checkpoint.checkpointId,
18772
+ commit: checkpoint.commit,
18773
+ checkpointRef,
18774
+ headRef,
18775
+ head: checkpoint.head,
18776
+ branch: checkpoint.branch,
18777
+ indexTree: checkpoint.indexTree,
18778
+ worktreeTree: checkpoint.worktreeTree,
18779
+ timestamp: checkpoint.timestamp,
18780
+ upstreamRemote: tracking.upstreamRemote,
18781
+ upstreamMergeRef: tracking.upstreamMergeRef,
18782
+ remoteUrl: tracking.remoteUrl
18783
+ },
18784
+ headPack,
18785
+ indexFile,
18786
+ totalBytes: (headPack?.rawBytes ?? 0) + indexFile.rawBytes
18787
+ };
18788
+ } finally {
18789
+ await deleteCheckpoint(git, checkpoint.checkpointId).catch(() => {
18790
+ });
18791
+ }
18792
+ }
18793
+ async applyFromHandoff(input) {
18794
+ const { checkpoint, headPackPath, indexPath, localGitState, onDivergedBranch } = input;
18795
+ const git = createGitClient(this.repositoryPath);
18796
+ if (headPackPath) {
18797
+ await this.unpackPackFile(headPackPath);
18798
+ }
18799
+ if (checkpoint.branch && checkpoint.head) {
18800
+ const branchStatus2 = await this.resolveBranchRestoreStatus(git, checkpoint.branch, checkpoint.head, localGitState);
18801
+ const tracking = this.getPreferredTracking(localGitState, checkpoint);
18802
+ if (branchStatus2.kind === "diverged" && !await onDivergedBranch?.(branchStatus2.divergence)) {
18803
+ throw new Error(`Handoff aborted: local branch '${checkpoint.branch}' has diverged`);
18804
+ }
18805
+ await this.checkoutBranchAtHead(git, checkpoint.branch, checkpoint.head);
18806
+ if (this.shouldRestoreTracking(branchStatus2, localGitState, tracking)) {
18807
+ await this.ensureRemoteForTracking(git, tracking);
18808
+ await this.configureUpstream(git, checkpoint.branch, tracking);
18809
+ }
18810
+ } else if (checkpoint.head) {
18811
+ await git.checkout(checkpoint.head);
18812
+ }
18813
+ if (indexPath) {
18814
+ await this.restoreIndexFile(git, indexPath);
18815
+ }
18816
+ const packBytes = headPackPath ? await this.getFileSize(headPackPath) : 0;
18817
+ const indexBytes = indexPath ? await this.getFileSize(indexPath) : 0;
18818
+ return {
18819
+ packBytes,
18820
+ indexBytes,
18821
+ totalBytes: packBytes + indexBytes
18822
+ };
18823
+ }
18824
+ async captureHeadPack(packPrefix, headCommit) {
18825
+ const hash = await this.runGitWithInput(["pack-objects", packPrefix, "--revs"], `${headCommit}
18826
+ `);
18827
+ const packPath = `${packPrefix}-${hash.trim()}.pack`;
18828
+ const rawBytes = await this.getFileSize(packPath);
18829
+ await rm3(`${packPath}.idx`, { force: true }).catch(() => {
18830
+ });
18831
+ return { path: packPath, rawBytes };
18832
+ }
18833
+ async copyIndexFile(git, checkpointId) {
18834
+ const indexPath = await this.getGitPath(git, "index");
18835
+ const tempDir = await this.getTempDir(git);
18836
+ const copiedIndexPath = path13.join(tempDir, `${checkpointId}.index`);
18837
+ await copyFile(indexPath, copiedIndexPath);
18838
+ return {
18839
+ path: copiedIndexPath,
18840
+ rawBytes: await this.getFileSize(copiedIndexPath)
18841
+ };
18842
+ }
18843
+ async restoreIndexFile(git, indexPath) {
18844
+ const gitIndexPath = await this.getGitPath(git, "index");
18845
+ await copyFile(indexPath, gitIndexPath);
18846
+ }
18847
+ async unpackPackFile(packPath) {
18848
+ const content = await readFile3(packPath);
18849
+ await this.runGitWithBuffer(["unpack-objects", "-r"], content);
18850
+ }
18851
+ getPreferredTracking(localGitState, checkpoint) {
18852
+ const state = localGitState;
18853
+ if (state && hasTrackingConfig(state)) {
18854
+ return {
18855
+ upstreamRemote: state.upstreamRemote ?? null,
18856
+ upstreamMergeRef: state.upstreamMergeRef ?? null,
18857
+ remoteUrl: state.upstreamRemote && state.upstreamRemote === checkpoint.upstreamRemote ? checkpoint.remoteUrl : null
18858
+ };
18859
+ }
18860
+ return {
18861
+ upstreamRemote: checkpoint.upstreamRemote,
18862
+ upstreamMergeRef: checkpoint.upstreamMergeRef,
18863
+ remoteUrl: checkpoint.remoteUrl
18864
+ };
18865
+ }
18866
+ shouldRestoreTracking(branchStatus2, localGitState, tracking) {
18867
+ return branchStatus2.kind === "missing" || !hasTrackingConfig(localGitState) && (tracking.upstreamRemote !== null || tracking.upstreamMergeRef !== null);
18868
+ }
18869
+ async ensureRemoteForTracking(git, tracking) {
18870
+ if (!tracking.upstreamRemote || !tracking.remoteUrl)
18871
+ return;
18872
+ const remotes = await git.getRemotes(true);
18873
+ const existing = remotes.find((remote) => remote.name === tracking.upstreamRemote);
18874
+ if (!existing) {
18875
+ await git.addRemote(tracking.upstreamRemote, tracking.remoteUrl);
18876
+ }
18877
+ }
18878
+ async configureUpstream(git, branch, tracking) {
18879
+ if (tracking.upstreamRemote) {
18880
+ await git.raw([
18881
+ "config",
18882
+ `branch.${branch}.remote`,
18883
+ tracking.upstreamRemote
18884
+ ]);
18885
+ }
18886
+ if (tracking.upstreamMergeRef) {
18887
+ await git.raw([
18888
+ "config",
18889
+ `branch.${branch}.merge`,
18890
+ tracking.upstreamMergeRef
18891
+ ]);
18892
+ }
18893
+ }
18894
+ async resolveBranchRestoreStatus(git, branch, cloudHead, localGitState) {
18895
+ const branchRef = `refs/heads/${branch}`;
18896
+ const branchExists = await this.refExists(git, branchRef);
18897
+ if (!branchExists) {
18898
+ return { kind: "missing" };
18899
+ }
18900
+ const currentBranchHead = (await git.revparse([branchRef])).trim();
18901
+ const candidateHeads = [
18902
+ currentBranchHead,
18903
+ ...localGitState?.branch === branch && localGitState.head ? [localGitState.head] : []
18904
+ ].filter((value, index, array) => array.indexOf(value) === index);
18905
+ if (candidateHeads.every((head) => head === cloudHead)) {
18906
+ return { kind: "match" };
18907
+ }
18908
+ const nonAncestorHead = await this.findNonAncestorHead(git, candidateHeads, cloudHead);
18909
+ if (!nonAncestorHead) {
18910
+ return { kind: "fast_forward" };
18911
+ }
18912
+ return {
18913
+ kind: "diverged",
18914
+ divergence: {
18915
+ branch,
18916
+ localHead: nonAncestorHead,
18917
+ cloudHead
18918
+ }
18919
+ };
18920
+ }
18921
+ async findNonAncestorHead(_git, heads, cloudHead) {
18922
+ for (const head of heads) {
18923
+ if (head === cloudHead) {
18924
+ continue;
18925
+ }
18926
+ if (!await this.isAncestor(head, cloudHead)) {
18927
+ return head;
18928
+ }
18929
+ }
18930
+ return null;
18931
+ }
18932
+ async checkoutBranchAtHead(git, branch, head) {
18933
+ const currentBranch = await getCurrentBranchName(git);
18934
+ if (currentBranch === branch) {
18935
+ await git.reset(["--hard", head]);
18936
+ return;
18937
+ }
18938
+ const branchRef = `refs/heads/${branch}`;
18939
+ if (await this.refExists(git, branchRef)) {
18940
+ await git.branch(["-f", branch, head]);
18941
+ await git.checkout(branch);
18942
+ return;
18943
+ }
18944
+ await git.checkout(["-b", branch, head]);
18945
+ }
18946
+ async refExists(git, ref) {
18947
+ try {
18948
+ await git.revparse(["--verify", ref]);
18949
+ return true;
18950
+ } catch {
18951
+ return false;
18952
+ }
18953
+ }
18954
+ async isAncestor(ancestor, descendant) {
18955
+ const exitCode = await this.runGitProcessAllowingFailure([
18956
+ "merge-base",
18957
+ "--is-ancestor",
18958
+ ancestor,
18959
+ descendant
18960
+ ]);
18961
+ return exitCode === 0;
18962
+ }
18963
+ async getTempDir(git) {
18964
+ const raw = await git.raw(["rev-parse", "--git-common-dir"]);
18965
+ const commonDir = raw.trim() || ".git";
18966
+ const resolved = path13.isAbsolute(commonDir) ? commonDir : path13.resolve(this.repositoryPath, commonDir);
18967
+ const tempDir = path13.join(resolved, "posthog-code-tmp");
18968
+ await mkdir3(tempDir, { recursive: true });
18969
+ return tempDir;
18970
+ }
18971
+ async getGitPath(git, gitPath) {
18972
+ const raw = await git.raw(["rev-parse", "--git-path", gitPath]);
18973
+ const resolved = raw.trim();
18974
+ return path13.isAbsolute(resolved) ? resolved : path13.resolve(this.repositoryPath, resolved);
18975
+ }
18976
+ async getFileSize(filePath) {
18977
+ return (await stat3(filePath)).size;
18978
+ }
18979
+ async runGitWithInput(args2, input) {
18980
+ const { stdout } = await this.runGitProcess(args2, input);
18981
+ return stdout;
18982
+ }
18983
+ async runGitWithBuffer(args2, input) {
18984
+ await this.runGitProcess(args2, input);
18985
+ }
18986
+ async runGitProcessAllowingFailure(args2) {
18987
+ return new Promise((resolve7, reject) => {
18988
+ const child = spawn4("git", args2, {
18989
+ cwd: this.repositoryPath,
18990
+ stdio: ["ignore", "ignore", "pipe"]
18991
+ });
18992
+ let stderr = "";
18993
+ child.stderr.on("data", (chunk) => {
18994
+ stderr += chunk.toString();
18995
+ });
18996
+ child.on("error", reject);
18997
+ child.on("close", (code) => {
18998
+ if (code === null) {
18999
+ reject(new Error(`git ${args2.join(" ")} exited unexpectedly`));
19000
+ return;
19001
+ }
19002
+ if (code > 1) {
19003
+ reject(new Error(stderr || `git ${args2.join(" ")} failed with code ${code}`));
19004
+ return;
19005
+ }
19006
+ resolve7(code);
19007
+ });
19008
+ });
19009
+ }
19010
+ runGitProcess(args2, input) {
19011
+ return new Promise((resolve7, reject) => {
19012
+ const child = spawn4("git", args2, {
19013
+ cwd: this.repositoryPath,
19014
+ stdio: "pipe"
19015
+ });
19016
+ let stdout = "";
19017
+ let stderr = "";
19018
+ child.stdout.on("data", (chunk) => {
19019
+ stdout += chunk.toString();
19020
+ });
19021
+ child.stderr.on("data", (chunk) => {
19022
+ stderr += chunk.toString();
19023
+ });
19024
+ child.on("error", reject);
19025
+ child.on("close", (code) => {
19026
+ if (code === 0) {
19027
+ resolve7({ stdout, stderr });
19028
+ return;
19029
+ }
19030
+ reject(new Error(stderr || `git ${args2.join(" ")} failed with code ${code}`));
19031
+ });
19032
+ child.stdin.end(input);
19033
+ });
19034
+ }
19035
+ };
19036
+ async function getCurrentBranchName(git) {
19037
+ try {
19038
+ const raw = await git.revparse(["--abbrev-ref", "HEAD"]);
19039
+ const branch = raw.trim();
19040
+ return branch === "HEAD" ? null : branch;
19041
+ } catch {
19042
+ return null;
19043
+ }
19044
+ }
19045
+ async function getTrackingMetadata(git, branch) {
19046
+ if (!branch) {
19047
+ return {
19048
+ upstreamRemote: null,
19049
+ upstreamMergeRef: null,
19050
+ remoteUrl: null
19051
+ };
19052
+ }
19053
+ const upstreamRemote = await getGitConfigValue(git, `branch.${branch}.remote`);
19054
+ const upstreamMergeRef = await getGitConfigValue(git, `branch.${branch}.merge`);
19055
+ const remoteUrl = upstreamRemote ? await getRemoteUrl(git, upstreamRemote) : null;
19056
+ return { upstreamRemote, upstreamMergeRef, remoteUrl };
19057
+ }
19058
+ async function getGitConfigValue(git, key) {
19059
+ try {
19060
+ const value = await git.raw(["config", "--get", key]);
19061
+ return value.trim() || null;
19062
+ } catch {
19063
+ return null;
19064
+ }
19065
+ }
19066
+ async function getRemoteUrl(git, remote) {
19067
+ try {
19068
+ const value = await git.remote(["get-url", remote]);
19069
+ return typeof value === "string" ? value.trim() || null : null;
19070
+ } catch {
19071
+ return null;
19072
+ }
19073
+ }
19074
+ function hasTrackingConfig(localGitState) {
19075
+ return !!(localGitState?.upstreamRemote || localGitState?.upstreamMergeRef);
19076
+ }
19077
+
19078
+ // src/handoff-checkpoint.ts
19079
+ var HandoffCheckpointTracker = class {
19080
+ repositoryPath;
19081
+ taskId;
19082
+ runId;
19083
+ apiClient;
19084
+ logger;
19085
+ constructor(config) {
19086
+ this.repositoryPath = config.repositoryPath;
19087
+ this.taskId = config.taskId;
19088
+ this.runId = config.runId;
19089
+ this.apiClient = config.apiClient;
19090
+ this.logger = config.logger || new Logger({ debug: false, prefix: "[HandoffCheckpointTracker]" });
19091
+ }
19092
+ async captureForHandoff(localGitState) {
19093
+ if (!this.apiClient) {
19094
+ throw new Error(
19095
+ "Cannot capture handoff checkpoint: API client not configured"
19096
+ );
19097
+ }
19098
+ const gitTracker = this.createGitTracker();
19099
+ const capture = await gitTracker.captureForHandoff(localGitState);
19100
+ try {
19101
+ const uploads = await this.uploadArtifacts([
19102
+ {
19103
+ key: "pack",
19104
+ filePath: capture.headPack?.path,
19105
+ name: `handoff/${capture.checkpoint.checkpointId}.pack`,
19106
+ contentType: "application/x-git-packed-objects"
19107
+ },
19108
+ {
19109
+ key: "index",
19110
+ filePath: capture.indexFile.path,
19111
+ name: `handoff/${capture.checkpoint.checkpointId}.index`,
19112
+ contentType: "application/octet-stream"
19113
+ }
19114
+ ]);
19115
+ this.logCaptureMetrics(capture.checkpoint, uploads);
19116
+ return {
19117
+ ...capture.checkpoint,
19118
+ artifactPath: uploads.pack?.storagePath,
19119
+ indexArtifactPath: uploads.index?.storagePath
19120
+ };
19121
+ } finally {
19122
+ await this.removeIfPresent(capture.headPack?.path);
19123
+ await this.removeIfPresent(capture.indexFile.path);
19124
+ }
19125
+ }
19126
+ async applyFromHandoff(checkpoint, options) {
19127
+ if (!this.apiClient) {
19128
+ throw new Error(
19129
+ "Cannot apply handoff checkpoint: API client not configured"
19130
+ );
19131
+ }
19132
+ const gitTracker = this.createGitTracker();
19133
+ const tmpDir = join9(this.repositoryPath, ".posthog", "tmp");
19134
+ await mkdir4(tmpDir, { recursive: true });
19135
+ const packPath = join9(tmpDir, `${checkpoint.checkpointId}.pack`);
19136
+ const indexPath = join9(tmpDir, `${checkpoint.checkpointId}.index`);
19137
+ try {
19138
+ const downloads = await this.downloadArtifacts([
19139
+ {
19140
+ key: "pack",
19141
+ storagePath: checkpoint.artifactPath,
19142
+ filePath: packPath,
19143
+ label: "handoff pack"
19144
+ },
19145
+ {
19146
+ key: "index",
19147
+ storagePath: checkpoint.indexArtifactPath,
19148
+ filePath: indexPath,
19149
+ label: "handoff index"
19150
+ }
19151
+ ]);
19152
+ const applyResult = await gitTracker.applyFromHandoff({
19153
+ checkpoint: this.toGitCheckpoint(checkpoint),
19154
+ headPackPath: downloads.pack?.filePath,
19155
+ indexPath: downloads.index?.filePath,
19156
+ localGitState: options?.localGitState,
19157
+ onDivergedBranch: options?.onDivergedBranch
19158
+ });
19159
+ this.logApplyMetrics(checkpoint, downloads, applyResult.totalBytes);
19160
+ } finally {
19161
+ await this.removeIfPresent(packPath);
19162
+ await this.removeIfPresent(indexPath);
19163
+ }
19164
+ }
19165
+ toGitCheckpoint(checkpoint) {
19166
+ return {
19167
+ checkpointId: checkpoint.checkpointId,
19168
+ commit: checkpoint.commit,
19169
+ checkpointRef: checkpoint.checkpointRef,
19170
+ headRef: checkpoint.headRef,
19171
+ head: checkpoint.head,
19172
+ branch: checkpoint.branch,
19173
+ indexTree: checkpoint.indexTree,
19174
+ worktreeTree: checkpoint.worktreeTree,
19175
+ timestamp: checkpoint.timestamp,
19176
+ upstreamRemote: checkpoint.upstreamRemote ?? null,
19177
+ upstreamMergeRef: checkpoint.upstreamMergeRef ?? null,
19178
+ remoteUrl: checkpoint.remoteUrl ?? null
19179
+ };
19180
+ }
19181
+ async uploadArtifactFile(filePath, name2, contentType) {
19182
+ if (!this.apiClient) {
19183
+ return { rawBytes: 0, wireBytes: 0 };
19184
+ }
19185
+ const content = await readFile4(filePath);
19186
+ const base64Content = content.toString("base64");
19187
+ const artifacts = await this.apiClient.uploadTaskArtifacts(
19188
+ this.taskId,
19189
+ this.runId,
19190
+ [
19191
+ {
19192
+ name: name2,
19193
+ type: "artifact",
19194
+ content: base64Content,
19195
+ content_type: contentType
19196
+ }
19197
+ ]
19198
+ );
19199
+ return {
19200
+ storagePath: artifacts.at(-1)?.storage_path,
19201
+ rawBytes: content.byteLength,
19202
+ wireBytes: Buffer.byteLength(base64Content, "utf-8")
19203
+ };
19204
+ }
19205
+ async uploadArtifacts(specs) {
19206
+ const uploads = await Promise.all(
19207
+ specs.map(async (spec) => {
19208
+ if (!spec.filePath) {
19209
+ return [spec.key, void 0];
19210
+ }
19211
+ return [
19212
+ spec.key,
19213
+ await this.uploadArtifactFile(
19214
+ spec.filePath,
19215
+ spec.name,
19216
+ spec.contentType
19217
+ )
19218
+ ];
19219
+ })
19220
+ );
19221
+ return Object.fromEntries(uploads);
19222
+ }
19223
+ async downloadArtifactToFile(artifactPath, filePath, label) {
19224
+ if (!this.apiClient) {
19225
+ throw new Error(`Cannot download ${label}: API client not configured`);
19226
+ }
19227
+ const arrayBuffer = await this.apiClient.downloadArtifact(
19228
+ this.taskId,
19229
+ this.runId,
19230
+ artifactPath
19231
+ );
19232
+ if (!arrayBuffer) {
19233
+ throw new Error(`Failed to download ${label}`);
19234
+ }
19235
+ const base64Content = Buffer.from(arrayBuffer).toString("utf-8");
19236
+ const binaryContent = Buffer.from(base64Content, "base64");
19237
+ await writeFile2(filePath, binaryContent);
19238
+ return {
19239
+ filePath,
19240
+ rawBytes: binaryContent.byteLength,
19241
+ wireBytes: arrayBuffer.byteLength
19242
+ };
19243
+ }
19244
+ async downloadArtifacts(specs) {
19245
+ const downloads = await Promise.all(
19246
+ specs.map(async (spec) => {
19247
+ if (!spec.storagePath) {
19248
+ return [spec.key, void 0];
19249
+ }
19250
+ return [
19251
+ spec.key,
19252
+ await this.downloadArtifactToFile(
19253
+ spec.storagePath,
19254
+ spec.filePath,
19255
+ spec.label
19256
+ )
19257
+ ];
19258
+ })
19259
+ );
19260
+ return Object.fromEntries(downloads);
19261
+ }
19262
+ createGitTracker() {
19263
+ return new GitHandoffTracker({
19264
+ repositoryPath: this.repositoryPath,
19265
+ logger: this.logger
19266
+ });
19267
+ }
19268
+ logCaptureMetrics(checkpoint, uploads) {
19269
+ this.logger.info("Captured handoff checkpoint", {
19270
+ checkpointId: checkpoint.checkpointId,
19271
+ branch: checkpoint.branch,
19272
+ head: checkpoint.head,
19273
+ artifactPath: uploads.pack?.storagePath,
19274
+ indexArtifactPath: uploads.index?.storagePath,
19275
+ ...this.buildMetricPayload(uploads)
19276
+ });
19277
+ }
19278
+ logApplyMetrics(checkpoint, downloads, totalBytes) {
19279
+ this.logger.info("Applied handoff checkpoint", {
19280
+ checkpointId: checkpoint.checkpointId,
19281
+ commit: checkpoint.commit,
19282
+ branch: checkpoint.branch,
19283
+ head: checkpoint.head,
19284
+ packBytes: downloads.pack?.rawBytes ?? 0,
19285
+ packWireBytes: downloads.pack?.wireBytes ?? 0,
19286
+ indexBytes: downloads.index?.rawBytes ?? 0,
19287
+ indexWireBytes: downloads.index?.wireBytes ?? 0,
19288
+ totalBytes,
19289
+ totalWireBytes: this.sumWireBytes(downloads.pack, downloads.index)
19290
+ });
19291
+ }
19292
+ buildMetricPayload(metrics) {
19293
+ return {
19294
+ packBytes: metrics.pack?.rawBytes ?? 0,
19295
+ packWireBytes: metrics.pack?.wireBytes ?? 0,
19296
+ indexBytes: metrics.index?.rawBytes ?? 0,
19297
+ indexWireBytes: metrics.index?.wireBytes ?? 0,
19298
+ totalBytes: this.sumRawBytes(metrics.pack, metrics.index),
19299
+ totalWireBytes: this.sumWireBytes(metrics.pack, metrics.index)
19300
+ };
19301
+ }
19302
+ sumRawBytes(...artifacts) {
19303
+ return artifacts.reduce(
19304
+ (total, artifact) => total + (artifact?.rawBytes ?? 0),
19305
+ 0
19306
+ );
19307
+ }
19308
+ sumWireBytes(...artifacts) {
19309
+ return artifacts.reduce(
19310
+ (total, artifact) => total + (artifact?.wireBytes ?? 0),
19311
+ 0
19312
+ );
19313
+ }
19314
+ async removeIfPresent(filePath) {
19315
+ if (!filePath) {
19316
+ return;
19317
+ }
19318
+ await rm4(filePath, { force: true }).catch(() => {
19319
+ });
19320
+ }
19321
+ };
19322
+
19323
+ // src/utils/gateway.ts
19324
+ function getGatewayBaseUrl(posthogHost) {
19325
+ const url = new URL(posthogHost);
19326
+ const hostname = url.hostname;
19327
+ if (hostname === "localhost" || hostname === "127.0.0.1") {
19328
+ return `${url.protocol}//localhost:3308`;
19329
+ }
19330
+ if (hostname === "host.docker.internal") {
19331
+ return `${url.protocol}//host.docker.internal:3308`;
19332
+ }
19333
+ const region = hostname.match(/^(us|eu)\.posthog\.com$/)?.[1] ?? "us";
19334
+ return `https://gateway.${region}.posthog.com`;
19335
+ }
19336
+ function getLlmGatewayUrl(posthogHost, product = "posthog_code") {
19337
+ return `${getGatewayBaseUrl(posthogHost)}/${product}`;
19338
+ }
19339
+
19340
+ // src/posthog-api.ts
19341
+ var DEFAULT_USER_AGENT = `posthog/agent.hog.dev; version: ${package_default.version}`;
19342
+ var PostHogAPIClient = class {
19343
+ config;
19344
+ constructor(config) {
19345
+ this.config = config;
19346
+ }
19347
+ get baseUrl() {
19348
+ const host = this.config.apiUrl.endsWith("/") ? this.config.apiUrl.slice(0, -1) : this.config.apiUrl;
19349
+ return host;
19350
+ }
19351
+ isAuthFailure(status) {
19352
+ return status === 401 || status === 403;
19353
+ }
19354
+ async resolveApiKey(forceRefresh = false) {
19355
+ if (forceRefresh && this.config.refreshApiKey) {
19356
+ return this.config.refreshApiKey();
19357
+ }
19358
+ return this.config.getApiKey();
19359
+ }
19360
+ async buildHeaders(options, forceRefresh = false) {
19361
+ const headers = new Headers(options.headers);
19362
+ headers.set(
19363
+ "Authorization",
19364
+ `Bearer ${await this.resolveApiKey(forceRefresh)}`
19365
+ );
19366
+ headers.set("Content-Type", "application/json");
19367
+ headers.set("User-Agent", this.config.userAgent ?? DEFAULT_USER_AGENT);
19368
+ return headers;
19369
+ }
19370
+ async performRequest(endpoint, options, forceRefresh = false) {
19371
+ const url = `${this.baseUrl}${endpoint}`;
19372
+ return fetch(url, {
19373
+ ...options,
19374
+ headers: await this.buildHeaders(options, forceRefresh)
19375
+ });
19376
+ }
19377
+ async performRequestWithRetry(endpoint, options = {}) {
19378
+ let response = await this.performRequest(endpoint, options);
19379
+ if (!response.ok && this.isAuthFailure(response.status)) {
19380
+ response = await this.performRequest(endpoint, options, true);
19381
+ }
19382
+ return response;
19383
+ }
19384
+ async apiRequest(endpoint, options = {}) {
19385
+ const response = await this.performRequestWithRetry(endpoint, options);
19386
+ if (!response.ok) {
19387
+ let errorMessage;
19388
+ try {
19389
+ const errorResponse = await response.json();
19390
+ errorMessage = `Failed request: [${response.status}] ${JSON.stringify(errorResponse)}`;
19391
+ } catch {
19392
+ errorMessage = `Failed request: [${response.status}] ${response.statusText}`;
19393
+ }
19394
+ throw new Error(errorMessage);
19395
+ }
19396
+ return response.json();
19397
+ }
19398
+ getTeamId() {
19399
+ return this.config.projectId;
19400
+ }
19401
+ async getApiKey(forceRefresh = false) {
19402
+ return this.resolveApiKey(forceRefresh);
19403
+ }
19404
+ getLlmGatewayUrl() {
19405
+ return getLlmGatewayUrl(this.baseUrl);
19406
+ }
19407
+ async getTask(taskId) {
19408
+ const teamId = this.getTeamId();
19409
+ return this.apiRequest(`/api/projects/${teamId}/tasks/${taskId}/`);
19410
+ }
19411
+ async getTaskRun(taskId, runId) {
19412
+ const teamId = this.getTeamId();
19413
+ return this.apiRequest(
19414
+ `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/`
19415
+ );
19416
+ }
19417
+ async updateTaskRun(taskId, runId, payload) {
19418
+ const teamId = this.getTeamId();
19419
+ return this.apiRequest(
19420
+ `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/`,
19421
+ {
19422
+ method: "PATCH",
19423
+ body: JSON.stringify(payload)
19424
+ }
19425
+ );
19426
+ }
19427
+ async setTaskRunOutput(taskId, runId, output) {
19428
+ return this.apiRequest(
19429
+ `/api/projects/${this.getTeamId()}/tasks/${taskId}/runs/${runId}/set_output/`,
19430
+ {
19431
+ method: "PATCH",
19432
+ body: JSON.stringify(output)
19433
+ }
19434
+ );
19435
+ }
19436
+ async appendTaskRunLog(taskId, runId, entries) {
19437
+ const teamId = this.getTeamId();
19438
+ return this.apiRequest(
19439
+ `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/append_log/`,
19440
+ {
19441
+ method: "POST",
19442
+ body: JSON.stringify({ entries })
19443
+ }
19444
+ );
19445
+ }
19446
+ async relayMessage(taskId, runId, text2) {
19447
+ const teamId = this.getTeamId();
19448
+ await this.apiRequest(
19449
+ `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/relay_message/`,
19450
+ {
19451
+ method: "POST",
19452
+ body: JSON.stringify({ text: text2 })
19453
+ }
19454
+ );
19455
+ }
19456
+ async uploadTaskArtifacts(taskId, runId, artifacts) {
19457
+ if (!artifacts.length) {
19458
+ return [];
19459
+ }
19460
+ const teamId = this.getTeamId();
19461
+ const response = await this.apiRequest(
19462
+ `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/artifacts/`,
19463
+ {
19464
+ method: "POST",
19465
+ body: JSON.stringify({ artifacts })
19466
+ }
19467
+ );
19468
+ const manifest = response.artifacts ?? [];
19469
+ return manifest.slice(-artifacts.length);
19470
+ }
19471
+ /**
19472
+ * Download artifact content by storage path
19473
+ * Streams the file through the PostHog backend so the sandbox does not need
19474
+ * direct access to object storage.
19475
+ */
19476
+ async downloadArtifact(taskId, runId, storagePath) {
19477
+ const teamId = this.getTeamId();
19478
+ try {
19479
+ const response = await this.performRequestWithRetry(
19480
+ `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/artifacts/download/`,
19481
+ {
19482
+ method: "POST",
19483
+ body: JSON.stringify({ storage_path: storagePath })
19484
+ }
19485
+ );
19486
+ if (!response.ok) {
19487
+ throw new Error(`Failed to download artifact: ${response.status}`);
19488
+ }
19489
+ return response.arrayBuffer();
19490
+ } catch {
19491
+ return null;
19492
+ }
19493
+ }
19494
+ /**
19495
+ * Fetch logs for a task run via the logs API endpoint
19496
+ * @param taskRun - The task run to fetch logs for
19497
+ * @returns Array of stored entries, or empty array if no logs available
19498
+ */
19499
+ async fetchTaskRunLogs(taskRun) {
19500
+ const teamId = this.getTeamId();
19501
+ const endpoint = `/api/projects/${teamId}/tasks/${taskRun.task}/runs/${taskRun.id}/logs`;
19502
+ try {
19503
+ const response = await this.performRequestWithRetry(endpoint);
19504
+ if (!response.ok) {
19505
+ if (response.status === 404) {
19506
+ return [];
19507
+ }
19508
+ throw new Error(
19509
+ `Failed to fetch logs: ${response.status} ${response.statusText}`
19510
+ );
19511
+ }
19512
+ const content = await response.text();
19513
+ if (!content.trim()) {
19514
+ return [];
19515
+ }
19516
+ return content.trim().split("\n").map((line) => JSON.parse(line));
19517
+ } catch (error) {
19518
+ throw new Error(
19519
+ `Failed to fetch task run logs: ${error instanceof Error ? error.message : String(error)}`
19520
+ );
19521
+ }
19522
+ }
19523
+ };
19524
+
19525
+ // src/adapters/claude/session/jsonl-hydration.ts
19526
+ import { randomUUID as randomUUID3 } from "crypto";
19527
+ import * as fs11 from "fs/promises";
19528
+ import * as os6 from "os";
19529
+ import * as path14 from "path";
19530
+ var CHARS_PER_TOKEN = 4;
19531
+ var DEFAULT_MAX_TOKENS = 15e4;
19532
+ function estimateTurnTokens(turn) {
19533
+ let chars = 0;
19534
+ for (const block of turn.content) {
19535
+ if ("text" in block && typeof block.text === "string") {
19536
+ chars += block.text.length;
19537
+ }
19538
+ }
19539
+ if (turn.toolCalls) {
19540
+ for (const tc of turn.toolCalls) {
19541
+ chars += JSON.stringify(tc.input ?? "").length;
19542
+ if (tc.result !== void 0) {
19543
+ chars += typeof tc.result === "string" ? tc.result.length : JSON.stringify(tc.result).length;
19544
+ }
19545
+ }
19546
+ }
19547
+ return Math.ceil(chars / CHARS_PER_TOKEN);
19548
+ }
19549
+ function selectRecentTurns(turns, maxTokens = DEFAULT_MAX_TOKENS) {
19550
+ let budget = maxTokens;
19551
+ let startIndex = turns.length;
19552
+ for (let i2 = turns.length - 1; i2 >= 0; i2--) {
19553
+ const cost = estimateTurnTokens(turns[i2]);
19554
+ if (cost > budget) break;
19555
+ budget -= cost;
19556
+ startIndex = i2;
19557
+ }
19558
+ while (startIndex < turns.length && turns[startIndex].role !== "user") {
19559
+ startIndex++;
19560
+ }
19561
+ return turns.slice(startIndex);
19562
+ }
19563
+
19564
+ // src/sagas/apply-snapshot-saga.ts
19565
+ import { mkdir as mkdir7, rm as rm6, writeFile as writeFile5 } from "fs/promises";
19566
+ import { join as join12 } from "path";
19567
+
19568
+ // ../git/dist/sagas/tree.js
19569
+ import { existsSync as existsSync5 } from "fs";
19570
+ import * as fs12 from "fs/promises";
19571
+ import * as path15 from "path";
19572
+ import * as tar from "tar";
19573
+ var CaptureTreeSaga = class extends GitSaga {
19574
+ sagaName = "CaptureTreeSaga";
19575
+ tempIndexPath = null;
19576
+ async executeGitOperations(input) {
19577
+ const { baseDir, lastTreeHash, archivePath, signal } = input;
19578
+ const tmpDir = path15.join(baseDir, ".git", "posthog-code-tmp");
19579
+ await this.step({
19580
+ name: "create_tmp_dir",
19581
+ execute: () => fs12.mkdir(tmpDir, { recursive: true }),
19582
+ rollback: async () => {
19583
+ }
19584
+ });
19585
+ this.tempIndexPath = path15.join(tmpDir, `index-${Date.now()}`);
18750
19586
  const tempIndexGit = this.git.env({
18751
19587
  ...process.env,
18752
19588
  GIT_INDEX_FILE: this.tempIndexPath
@@ -18756,7 +19592,7 @@ var CaptureTreeSaga = class extends GitSaga {
18756
19592
  execute: () => tempIndexGit.raw(["read-tree", "HEAD"]),
18757
19593
  rollback: async () => {
18758
19594
  if (this.tempIndexPath) {
18759
- await fs11.rm(this.tempIndexPath, { force: true }).catch(() => {
19595
+ await fs12.rm(this.tempIndexPath, { force: true }).catch(() => {
18760
19596
  });
18761
19597
  }
18762
19598
  }
@@ -18765,7 +19601,7 @@ var CaptureTreeSaga = class extends GitSaga {
18765
19601
  const treeHash = await this.readOnlyStep("write_tree", () => tempIndexGit.raw(["write-tree"]));
18766
19602
  if (lastTreeHash && treeHash === lastTreeHash) {
18767
19603
  this.log.debug("No changes since last capture", { treeHash });
18768
- await fs11.rm(this.tempIndexPath, { force: true }).catch(() => {
19604
+ await fs12.rm(this.tempIndexPath, { force: true }).catch(() => {
18769
19605
  });
18770
19606
  return { snapshot: null, changed: false };
18771
19607
  }
@@ -18777,7 +19613,7 @@ var CaptureTreeSaga = class extends GitSaga {
18777
19613
  }
18778
19614
  });
18779
19615
  const changes = await this.readOnlyStep("get_changes", () => this.getChanges(this.git, baseCommit, treeHash));
18780
- await fs11.rm(this.tempIndexPath, { force: true }).catch(() => {
19616
+ await fs12.rm(this.tempIndexPath, { force: true }).catch(() => {
18781
19617
  });
18782
19618
  const snapshot = {
18783
19619
  treeHash,
@@ -18801,15 +19637,15 @@ var CaptureTreeSaga = class extends GitSaga {
18801
19637
  if (filesToArchive.length === 0) {
18802
19638
  return void 0;
18803
19639
  }
18804
- const existingFiles = filesToArchive.filter((f) => existsSync5(path13.join(baseDir, f)));
19640
+ const existingFiles = filesToArchive.filter((f) => existsSync5(path15.join(baseDir, f)));
18805
19641
  if (existingFiles.length === 0) {
18806
19642
  return void 0;
18807
19643
  }
18808
19644
  await this.step({
18809
19645
  name: "create_archive",
18810
19646
  execute: async () => {
18811
- const archiveDir = path13.dirname(archivePath);
18812
- await fs11.mkdir(archiveDir, { recursive: true });
19647
+ const archiveDir = path15.dirname(archivePath);
19648
+ await fs12.mkdir(archiveDir, { recursive: true });
18813
19649
  await tar.create({
18814
19650
  gzip: true,
18815
19651
  file: archivePath,
@@ -18817,7 +19653,7 @@ var CaptureTreeSaga = class extends GitSaga {
18817
19653
  }, existingFiles);
18818
19654
  },
18819
19655
  rollback: async () => {
18820
- await fs11.rm(archivePath, { force: true }).catch(() => {
19656
+ await fs12.rm(archivePath, { force: true }).catch(() => {
18821
19657
  });
18822
19658
  }
18823
19659
  });
@@ -18917,9 +19753,9 @@ var ApplyTreeSaga = class extends GitSaga {
18917
19753
  const filesToExtract = changes.filter((c) => c.status !== "D").map((c) => c.path);
18918
19754
  await this.readOnlyStep("backup_existing_files", async () => {
18919
19755
  for (const filePath of filesToExtract) {
18920
- const fullPath = path13.join(baseDir, filePath);
19756
+ const fullPath = path15.join(baseDir, filePath);
18921
19757
  try {
18922
- const content = await fs11.readFile(fullPath);
19758
+ const content = await fs12.readFile(fullPath);
18923
19759
  this.fileBackups.set(filePath, content);
18924
19760
  } catch {
18925
19761
  }
@@ -18936,16 +19772,16 @@ var ApplyTreeSaga = class extends GitSaga {
18936
19772
  },
18937
19773
  rollback: async () => {
18938
19774
  for (const filePath of this.extractedFiles) {
18939
- const fullPath = path13.join(baseDir, filePath);
19775
+ const fullPath = path15.join(baseDir, filePath);
18940
19776
  const backup = this.fileBackups.get(filePath);
18941
19777
  if (backup) {
18942
- const dir = path13.dirname(fullPath);
18943
- await fs11.mkdir(dir, { recursive: true }).catch(() => {
19778
+ const dir = path15.dirname(fullPath);
19779
+ await fs12.mkdir(dir, { recursive: true }).catch(() => {
18944
19780
  });
18945
- await fs11.writeFile(fullPath, backup).catch(() => {
19781
+ await fs12.writeFile(fullPath, backup).catch(() => {
18946
19782
  });
18947
19783
  } else {
18948
- await fs11.rm(fullPath, { force: true }).catch(() => {
19784
+ await fs12.rm(fullPath, { force: true }).catch(() => {
18949
19785
  });
18950
19786
  }
18951
19787
  }
@@ -18953,10 +19789,10 @@ var ApplyTreeSaga = class extends GitSaga {
18953
19789
  });
18954
19790
  }
18955
19791
  for (const change of changes.filter((c) => c.status === "D")) {
18956
- const fullPath = path13.join(baseDir, change.path);
19792
+ const fullPath = path15.join(baseDir, change.path);
18957
19793
  const backupContent = await this.readOnlyStep(`backup_${change.path}`, async () => {
18958
19794
  try {
18959
- return await fs11.readFile(fullPath);
19795
+ return await fs12.readFile(fullPath);
18960
19796
  } catch {
18961
19797
  return null;
18962
19798
  }
@@ -18964,15 +19800,15 @@ var ApplyTreeSaga = class extends GitSaga {
18964
19800
  await this.step({
18965
19801
  name: `delete_${change.path}`,
18966
19802
  execute: async () => {
18967
- await fs11.rm(fullPath, { force: true });
19803
+ await fs12.rm(fullPath, { force: true });
18968
19804
  this.log.debug(`Deleted file: ${change.path}`);
18969
19805
  },
18970
19806
  rollback: async () => {
18971
19807
  if (backupContent) {
18972
- const dir = path13.dirname(fullPath);
18973
- await fs11.mkdir(dir, { recursive: true }).catch(() => {
19808
+ const dir = path15.dirname(fullPath);
19809
+ await fs12.mkdir(dir, { recursive: true }).catch(() => {
18974
19810
  });
18975
- await fs11.writeFile(fullPath, backupContent).catch(() => {
19811
+ await fs12.writeFile(fullPath, backupContent).catch(() => {
18976
19812
  });
18977
19813
  }
18978
19814
  }
@@ -18995,18 +19831,18 @@ var ApplySnapshotSaga = class extends Saga {
18995
19831
  archivePath = null;
18996
19832
  async execute(input) {
18997
19833
  const { snapshot, repositoryPath, apiClient, taskId, runId } = input;
18998
- const tmpDir = join10(repositoryPath, ".posthog", "tmp");
19834
+ const tmpDir = join12(repositoryPath, ".posthog", "tmp");
18999
19835
  if (!snapshot.archiveUrl) {
19000
19836
  throw new Error("Cannot apply snapshot: no archive URL");
19001
19837
  }
19002
19838
  const archiveUrl = snapshot.archiveUrl;
19003
19839
  await this.step({
19004
19840
  name: "create_tmp_dir",
19005
- execute: () => mkdir4(tmpDir, { recursive: true }),
19841
+ execute: () => mkdir7(tmpDir, { recursive: true }),
19006
19842
  rollback: async () => {
19007
19843
  }
19008
19844
  });
19009
- const archivePath = join10(tmpDir, `${snapshot.treeHash}.tar.gz`);
19845
+ const archivePath = join12(tmpDir, `${snapshot.treeHash}.tar.gz`);
19010
19846
  this.archivePath = archivePath;
19011
19847
  await this.step({
19012
19848
  name: "download_archive",
@@ -19021,11 +19857,18 @@ var ApplySnapshotSaga = class extends Saga {
19021
19857
  }
19022
19858
  const base64Content = Buffer.from(arrayBuffer).toString("utf-8");
19023
19859
  const binaryContent = Buffer.from(base64Content, "base64");
19024
- await writeFile4(archivePath, binaryContent);
19860
+ await writeFile5(archivePath, binaryContent);
19861
+ this.log.info("Tree archive downloaded", {
19862
+ treeHash: snapshot.treeHash,
19863
+ snapshotBytes: binaryContent.byteLength,
19864
+ snapshotWireBytes: arrayBuffer.byteLength,
19865
+ totalBytes: binaryContent.byteLength,
19866
+ totalWireBytes: arrayBuffer.byteLength
19867
+ });
19025
19868
  },
19026
19869
  rollback: async () => {
19027
19870
  if (this.archivePath) {
19028
- await rm3(this.archivePath, { force: true }).catch(() => {
19871
+ await rm6(this.archivePath, { force: true }).catch(() => {
19029
19872
  });
19030
19873
  }
19031
19874
  }
@@ -19041,7 +19884,7 @@ var ApplySnapshotSaga = class extends Saga {
19041
19884
  if (!applyResult.success) {
19042
19885
  throw new Error(`Failed to apply tree: ${applyResult.error}`);
19043
19886
  }
19044
- await rm3(this.archivePath, { force: true }).catch(() => {
19887
+ await rm6(this.archivePath, { force: true }).catch(() => {
19045
19888
  });
19046
19889
  this.log.info("Tree snapshot applied", {
19047
19890
  treeHash: snapshot.treeHash,
@@ -19054,8 +19897,8 @@ var ApplySnapshotSaga = class extends Saga {
19054
19897
 
19055
19898
  // src/sagas/capture-tree-saga.ts
19056
19899
  import { existsSync as existsSync6 } from "fs";
19057
- import { readFile as readFile4, rm as rm4 } from "fs/promises";
19058
- import { join as join11 } from "path";
19900
+ import { readFile as readFile6, rm as rm7 } from "fs/promises";
19901
+ import { join as join13 } from "path";
19059
19902
  var CaptureTreeSaga2 = class extends Saga {
19060
19903
  sagaName = "CaptureTreeSaga";
19061
19904
  async execute(input) {
@@ -19067,14 +19910,14 @@ var CaptureTreeSaga2 = class extends Saga {
19067
19910
  taskId,
19068
19911
  runId
19069
19912
  } = input;
19070
- const tmpDir = join11(repositoryPath, ".posthog", "tmp");
19071
- if (existsSync6(join11(repositoryPath, ".gitmodules"))) {
19913
+ const tmpDir = join13(repositoryPath, ".posthog", "tmp");
19914
+ if (existsSync6(join13(repositoryPath, ".gitmodules"))) {
19072
19915
  this.log.warn(
19073
19916
  "Repository has submodules - snapshot may not capture submodule state"
19074
19917
  );
19075
19918
  }
19076
19919
  const shouldArchive = !!apiClient;
19077
- const archivePath = shouldArchive ? join11(tmpDir, `tree-${Date.now()}.tar.gz`) : void 0;
19920
+ const archivePath = shouldArchive ? join13(tmpDir, `tree-${Date.now()}.tar.gz`) : void 0;
19078
19921
  const gitCaptureSaga = new CaptureTreeSaga(this.log);
19079
19922
  const captureResult = await gitCaptureSaga.run({
19080
19923
  baseDir: repositoryPath,
@@ -19104,7 +19947,7 @@ var CaptureTreeSaga2 = class extends Saga {
19104
19947
  runId
19105
19948
  );
19106
19949
  } finally {
19107
- await rm4(createdArchivePath, { force: true }).catch(() => {
19950
+ await rm7(createdArchivePath, { force: true }).catch(() => {
19108
19951
  });
19109
19952
  }
19110
19953
  }
@@ -19128,8 +19971,10 @@ var CaptureTreeSaga2 = class extends Saga {
19128
19971
  const archiveUrl = await this.step({
19129
19972
  name: "upload_archive",
19130
19973
  execute: async () => {
19131
- const archiveContent = await readFile4(archivePath);
19974
+ const archiveContent = await readFile6(archivePath);
19132
19975
  const base64Content = archiveContent.toString("base64");
19976
+ const snapshotBytes = archiveContent.byteLength;
19977
+ const snapshotWireBytes = Buffer.byteLength(base64Content, "utf-8");
19133
19978
  const artifacts = await apiClient.uploadTaskArtifacts(taskId, runId, [
19134
19979
  {
19135
19980
  name: `trees/${treeHash}.tar.gz`,
@@ -19138,17 +19983,22 @@ var CaptureTreeSaga2 = class extends Saga {
19138
19983
  content_type: "application/gzip"
19139
19984
  }
19140
19985
  ]);
19141
- if (artifacts.length > 0 && artifacts[0].storage_path) {
19986
+ const uploadedArtifact = artifacts[0];
19987
+ if (uploadedArtifact?.storage_path) {
19142
19988
  this.log.info("Tree archive uploaded", {
19143
- storagePath: artifacts[0].storage_path,
19144
- treeHash
19989
+ storagePath: uploadedArtifact.storage_path,
19990
+ treeHash,
19991
+ snapshotBytes,
19992
+ snapshotWireBytes,
19993
+ totalBytes: snapshotBytes,
19994
+ totalWireBytes: snapshotWireBytes
19145
19995
  });
19146
- return artifacts[0].storage_path;
19996
+ return uploadedArtifact.storage_path;
19147
19997
  }
19148
19998
  return void 0;
19149
19999
  },
19150
20000
  rollback: async () => {
19151
- await rm4(archivePath, { force: true }).catch(() => {
20001
+ await rm7(archivePath, { force: true }).catch(() => {
19152
20002
  });
19153
20003
  }
19154
20004
  });
@@ -19276,6 +20126,10 @@ var ResumeSaga = class extends Saga {
19276
20126
  "find_snapshot",
19277
20127
  () => Promise.resolve(this.findLatestTreeSnapshot(entries))
19278
20128
  );
20129
+ const latestGitCheckpoint = await this.readOnlyStep(
20130
+ "find_git_checkpoint",
20131
+ () => Promise.resolve(this.findLatestGitCheckpoint(entries))
20132
+ );
19279
20133
  let snapshotApplied = false;
19280
20134
  if (latestSnapshot?.archiveUrl && repositoryPath) {
19281
20135
  this.log.info("Found tree snapshot", {
@@ -19348,6 +20202,7 @@ var ResumeSaga = class extends Saga {
19348
20202
  return {
19349
20203
  conversation,
19350
20204
  latestSnapshot,
20205
+ latestGitCheckpoint,
19351
20206
  snapshotApplied,
19352
20207
  interrupted: latestSnapshot?.interrupted ?? false,
19353
20208
  lastDevice,
@@ -19358,6 +20213,7 @@ var ResumeSaga = class extends Saga {
19358
20213
  return {
19359
20214
  conversation: [],
19360
20215
  latestSnapshot: null,
20216
+ latestGitCheckpoint: null,
19361
20217
  snapshotApplied: false,
19362
20218
  interrupted: false,
19363
20219
  logEntryCount: 0
@@ -19378,6 +20234,20 @@ var ResumeSaga = class extends Saga {
19378
20234
  }
19379
20235
  return null;
19380
20236
  }
20237
+ findLatestGitCheckpoint(entries) {
20238
+ const sdkPrefixedMethod = `_${POSTHOG_NOTIFICATIONS.GIT_CHECKPOINT}`;
20239
+ for (let i2 = entries.length - 1; i2 >= 0; i2--) {
20240
+ const entry = entries[i2];
20241
+ const method = entry.notification?.method;
20242
+ if (method === sdkPrefixedMethod || method === POSTHOG_NOTIFICATIONS.GIT_CHECKPOINT) {
20243
+ const params = entry.notification?.params;
20244
+ if (params?.checkpointId && params?.checkpointRef) {
20245
+ return params;
20246
+ }
20247
+ }
20248
+ }
20249
+ return null;
20250
+ }
19381
20251
  findLastDeviceInfo(entries) {
19382
20252
  for (let i2 = entries.length - 1; i2 >= 0; i2--) {
19383
20253
  const entry = entries[i2];
@@ -19526,6 +20396,7 @@ async function resumeFromLog(config) {
19526
20396
  return {
19527
20397
  conversation: result.data.conversation,
19528
20398
  latestSnapshot: result.data.latestSnapshot,
20399
+ latestGitCheckpoint: result.data.latestGitCheckpoint,
19529
20400
  snapshotApplied: result.data.snapshotApplied,
19530
20401
  interrupted: result.data.interrupted,
19531
20402
  lastDevice: result.data.lastDevice,
@@ -19565,9 +20436,9 @@ ${toolSummary}`);
19565
20436
  }
19566
20437
 
19567
20438
  // src/session-log-writer.ts
19568
- import fs12 from "fs";
20439
+ import fs13 from "fs";
19569
20440
  import fsp from "fs/promises";
19570
- import path14 from "path";
20441
+ import path16 from "path";
19571
20442
  var SessionLogWriter = class _SessionLogWriter {
19572
20443
  static FLUSH_DEBOUNCE_MS = 500;
19573
20444
  static FLUSH_MAX_INTERVAL_MS = 5e3;
@@ -19603,13 +20474,13 @@ var SessionLogWriter = class _SessionLogWriter {
19603
20474
  this.sessions.set(sessionId, { context, currentTurnMessages: [] });
19604
20475
  this.lastFlushAttemptTime.set(sessionId, Date.now());
19605
20476
  if (this.localCachePath) {
19606
- const sessionDir = path14.join(
20477
+ const sessionDir = path16.join(
19607
20478
  this.localCachePath,
19608
20479
  "sessions",
19609
20480
  context.runId
19610
20481
  );
19611
20482
  try {
19612
- fs12.mkdirSync(sessionDir, { recursive: true });
20483
+ fs13.mkdirSync(sessionDir, { recursive: true });
19613
20484
  } catch (error) {
19614
20485
  this.logger.warn("Failed to create local cache directory", {
19615
20486
  sessionDir,
@@ -19861,14 +20732,14 @@ var SessionLogWriter = class _SessionLogWriter {
19861
20732
  if (!this.localCachePath) return;
19862
20733
  const session = this.sessions.get(sessionId);
19863
20734
  if (!session) return;
19864
- const logPath = path14.join(
20735
+ const logPath = path16.join(
19865
20736
  this.localCachePath,
19866
20737
  "sessions",
19867
20738
  session.context.runId,
19868
20739
  "logs.ndjson"
19869
20740
  );
19870
20741
  try {
19871
- fs12.appendFileSync(logPath, `${JSON.stringify(entry)}
20742
+ fs13.appendFileSync(logPath, `${JSON.stringify(entry)}
19872
20743
  `);
19873
20744
  } catch (error) {
19874
20745
  this.logger.warn("Failed to write to local cache", {
@@ -19880,13 +20751,13 @@ var SessionLogWriter = class _SessionLogWriter {
19880
20751
  }
19881
20752
  }
19882
20753
  static async cleanupOldSessions(localCachePath) {
19883
- const sessionsDir = path14.join(localCachePath, "sessions");
20754
+ const sessionsDir = path16.join(localCachePath, "sessions");
19884
20755
  let deleted = 0;
19885
20756
  try {
19886
20757
  const entries = await fsp.readdir(sessionsDir);
19887
20758
  const now = Date.now();
19888
20759
  for (const entry of entries) {
19889
- const entryPath = path14.join(sessionsDir, entry);
20760
+ const entryPath = path16.join(sessionsDir, entry);
19890
20761
  try {
19891
20762
  const stats = await fsp.stat(entryPath);
19892
20763
  if (stats.isDirectory() && now - stats.birthtimeMs > _SessionLogWriter.SESSIONS_MAX_AGE_MS) {
@@ -19968,6 +20839,14 @@ var httpHeaderSchema = z3.object({
19968
20839
  name: z3.string(),
19969
20840
  value: z3.string()
19970
20841
  });
20842
+ var nullishString = z3.string().nullish().transform((value) => value ?? null);
20843
+ var handoffLocalGitStateSchema = z3.object({
20844
+ head: nullishString,
20845
+ branch: nullishString,
20846
+ upstreamHead: nullishString,
20847
+ upstreamRemote: nullishString,
20848
+ upstreamMergeRef: nullishString
20849
+ });
19971
20850
  var remoteMcpServerSchema = z3.object({
19972
20851
  type: z3.enum(["http", "sse"]),
19973
20852
  name: z3.string().min(1, "MCP server name is required"),
@@ -20019,13 +20898,16 @@ var setConfigOptionParamsSchema = z3.object({
20019
20898
  var refreshSessionParamsSchema = z3.object({
20020
20899
  mcpServers: mcpServersSchema
20021
20900
  });
20901
+ var closeParamsSchema = z3.object({
20902
+ localGitState: handoffLocalGitStateSchema.optional()
20903
+ }).optional();
20022
20904
  var commandParamsSchemas = {
20023
20905
  user_message: userMessageParamsSchema,
20024
20906
  "posthog/user_message": userMessageParamsSchema,
20025
20907
  cancel: z3.object({}).optional(),
20026
20908
  "posthog/cancel": z3.object({}).optional(),
20027
- close: z3.object({}).optional(),
20028
- "posthog/close": z3.object({}).optional(),
20909
+ close: closeParamsSchema,
20910
+ "posthog/close": closeParamsSchema,
20029
20911
  permission_response: permissionResponseParamsSchema,
20030
20912
  "posthog/permission_response": permissionResponseParamsSchema,
20031
20913
  set_config_option: setConfigOptionParamsSchema,
@@ -20347,7 +21229,7 @@ var AgentServer = class {
20347
21229
  return app;
20348
21230
  }
20349
21231
  async start() {
20350
- await new Promise((resolve6) => {
21232
+ await new Promise((resolve7) => {
20351
21233
  this.server = serve(
20352
21234
  {
20353
21235
  fetch: this.app.fetch,
@@ -20357,7 +21239,7 @@ var AgentServer = class {
20357
21239
  this.logger.debug(
20358
21240
  `HTTP server listening on port ${this.config.port}`
20359
21241
  );
20360
- resolve6();
21242
+ resolve7();
20361
21243
  }
20362
21244
  );
20363
21245
  });
@@ -20511,6 +21393,10 @@ var AgentServer = class {
20511
21393
  case POSTHOG_NOTIFICATIONS.CLOSE:
20512
21394
  case "close": {
20513
21395
  this.logger.debug("Close requested");
21396
+ const localGitState = this.extractHandoffLocalGitState(params);
21397
+ if (localGitState && this.session) {
21398
+ this.session.pendingHandoffGitState = localGitState;
21399
+ }
20514
21400
  await this.cleanupSession();
20515
21401
  return { closed: true };
20516
21402
  }
@@ -20737,7 +21623,8 @@ var AgentServer = class {
20737
21623
  deviceInfo,
20738
21624
  logWriter,
20739
21625
  permissionMode: initialPermissionMode,
20740
- hasDesktopConnected: sseController !== null
21626
+ hasDesktopConnected: sseController !== null,
21627
+ pendingHandoffGitState: void 0
20741
21628
  };
20742
21629
  this.logger = new Logger({
20743
21630
  debug: true,
@@ -21062,16 +21949,16 @@ Continue from where you left off. The user is waiting for your response.`
21062
21949
  throw new Error(`Failed to download artifact ${artifact.name}`);
21063
21950
  }
21064
21951
  const safeName = this.getSafeArtifactName(artifact.name);
21065
- const artifactDir = join12(
21952
+ const artifactDir = join14(
21066
21953
  this.config.repositoryPath ?? "/tmp/workspace",
21067
21954
  ".posthog",
21068
21955
  "attachments",
21069
21956
  runId,
21070
21957
  artifact.id ?? safeName
21071
21958
  );
21072
- await mkdir5(artifactDir, { recursive: true });
21073
- const artifactPath = join12(artifactDir, safeName);
21074
- await writeFile5(artifactPath, Buffer.from(data));
21959
+ await mkdir8(artifactDir, { recursive: true });
21960
+ const artifactPath = join14(artifactDir, safeName);
21961
+ await writeFile6(artifactPath, Buffer.from(data));
21075
21962
  return resourceLink(pathToFileURL(artifactPath).toString(), artifact.name, {
21076
21963
  ...artifact.content_type ? { mimeType: artifact.content_type } : {},
21077
21964
  ...typeof artifact.size === "number" ? { size: artifact.size } : {}
@@ -21570,6 +22457,11 @@ ${attributionInstructions}
21570
22457
  async cleanupSession() {
21571
22458
  if (!this.session) return;
21572
22459
  this.logger.debug("Cleaning up session");
22460
+ try {
22461
+ await this.captureHandoffCheckpoint();
22462
+ } catch (error) {
22463
+ this.logger.error("Failed to capture handoff checkpoint", error);
22464
+ }
21573
22465
  try {
21574
22466
  await this.captureTreeState();
21575
22467
  } catch (error) {
@@ -21629,6 +22521,50 @@ ${attributionInstructions}
21629
22521
  this.logger.error("Failed to capture tree state", error);
21630
22522
  }
21631
22523
  }
22524
+ async captureHandoffCheckpoint() {
22525
+ if (!this.session?.treeTracker || !this.session.pendingHandoffGitState) {
22526
+ return;
22527
+ }
22528
+ if (!this.posthogAPI) {
22529
+ this.logger.warn(
22530
+ "Skipping handoff checkpoint capture: PostHog API client is not configured"
22531
+ );
22532
+ return;
22533
+ }
22534
+ const tracker = new HandoffCheckpointTracker({
22535
+ repositoryPath: this.config.repositoryPath ?? "/tmp/workspace",
22536
+ taskId: this.session.payload.task_id,
22537
+ runId: this.session.payload.run_id,
22538
+ apiClient: this.posthogAPI,
22539
+ logger: this.logger.child("HandoffCheckpoint")
22540
+ });
22541
+ const checkpoint = await tracker.captureForHandoff(
22542
+ this.session.pendingHandoffGitState
22543
+ );
22544
+ if (!checkpoint) return;
22545
+ const checkpointWithDevice = {
22546
+ ...checkpoint,
22547
+ device: this.session.deviceInfo
22548
+ };
22549
+ const notification = {
22550
+ jsonrpc: "2.0",
22551
+ method: POSTHOG_NOTIFICATIONS.GIT_CHECKPOINT,
22552
+ params: checkpointWithDevice
22553
+ };
22554
+ this.broadcastEvent({
22555
+ type: "notification",
22556
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
22557
+ notification
22558
+ });
22559
+ this.session.logWriter.appendRawLine(
22560
+ this.session.payload.run_id,
22561
+ JSON.stringify(notification)
22562
+ );
22563
+ }
22564
+ extractHandoffLocalGitState(params) {
22565
+ const result = handoffLocalGitStateSchema.safeParse(params.localGitState);
22566
+ return result.success ? result.data : null;
22567
+ }
21632
22568
  broadcastTurnComplete(stopReason) {
21633
22569
  if (!this.session) return;
21634
22570
  this.broadcastEvent({
@@ -21682,8 +22618,8 @@ ${attributionInstructions}
21682
22618
  options: params.options,
21683
22619
  toolCall: params.toolCall
21684
22620
  });
21685
- return new Promise((resolve6) => {
21686
- this.pendingPermissions.set(requestId, { resolve: resolve6 });
22621
+ return new Promise((resolve7) => {
22622
+ this.pendingPermissions.set(requestId, { resolve: resolve7 });
21687
22623
  });
21688
22624
  }
21689
22625
  resolvePermission(requestId, optionId, customInput, answers) {