@posthog/agent 2.3.385 → 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 (45) 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 +21 -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/logger-RC7sPv0S.d.ts +24 -0
  12. package/dist/posthog-api.d.ts +1 -0
  13. package/dist/posthog-api.js +19 -2
  14. package/dist/posthog-api.js.map +1 -1
  15. package/dist/resume.d.ts +71 -0
  16. package/dist/resume.js +6838 -0
  17. package/dist/resume.js.map +1 -0
  18. package/dist/server/agent-server.d.ts +5 -18
  19. package/dist/server/agent-server.js +1373 -432
  20. package/dist/server/agent-server.js.map +1 -1
  21. package/dist/server/bin.cjs +1370 -429
  22. package/dist/server/bin.cjs.map +1 -1
  23. package/dist/server/schemas.d.ts +191 -0
  24. package/dist/server/schemas.js +108 -0
  25. package/dist/server/schemas.js.map +1 -0
  26. package/dist/tree-tracker.d.ts +68 -0
  27. package/dist/tree-tracker.js +6431 -0
  28. package/dist/tree-tracker.js.map +1 -0
  29. package/dist/types.d.ts +18 -1
  30. package/dist/types.js +5 -0
  31. package/dist/types.js.map +1 -1
  32. package/package.json +17 -1
  33. package/src/acp-extensions.ts +3 -0
  34. package/src/handoff-checkpoint.test.ts +183 -0
  35. package/src/handoff-checkpoint.ts +361 -0
  36. package/src/posthog-api.test.ts +29 -0
  37. package/src/posthog-api.ts +5 -1
  38. package/src/resume.ts +58 -1
  39. package/src/sagas/apply-snapshot-saga.ts +7 -0
  40. package/src/sagas/capture-tree-saga.ts +10 -3
  41. package/src/sagas/resume-saga.ts +32 -0
  42. package/src/sagas/test-fixtures.ts +46 -0
  43. package/src/server/agent-server.ts +76 -57
  44. package/src/server/schemas.ts +21 -2
  45. package/src/types.ts +24 -0
@@ -805,15 +805,15 @@ var require_src2 = __commonJS({
805
805
  var fs_1 = require("fs");
806
806
  var debug_1 = __importDefault(require_src());
807
807
  var log = debug_1.default("@kwsites/file-exists");
808
- function check(path15, isFile2, isDirectory) {
809
- log(`checking %s`, path15);
808
+ function check(path17, isFile2, isDirectory) {
809
+ log(`checking %s`, path17);
810
810
  try {
811
- const stat2 = fs_1.statSync(path15);
812
- if (stat2.isFile() && isFile2) {
811
+ const stat4 = fs_1.statSync(path17);
812
+ if (stat4.isFile() && isFile2) {
813
813
  log(`[OK] path represents a file`);
814
814
  return true;
815
815
  }
816
- if (stat2.isDirectory() && isDirectory) {
816
+ if (stat4.isDirectory() && isDirectory) {
817
817
  log(`[OK] path represents a directory`);
818
818
  return true;
819
819
  }
@@ -828,8 +828,8 @@ var require_src2 = __commonJS({
828
828
  throw e;
829
829
  }
830
830
  }
831
- function exists2(path15, type = exports2.READABLE) {
832
- return check(path15, (type & exports2.FILE) > 0, (type & exports2.FOLDER) > 0);
831
+ function exists2(path17, type = exports2.READABLE) {
832
+ return check(path17, (type & exports2.FILE) > 0, (type & exports2.FOLDER) > 0);
833
833
  }
834
834
  exports2.exists = exists2;
835
835
  exports2.FILE = 1;
@@ -925,11 +925,11 @@ var require_tree_sitter = __commonJS({
925
925
  throw toThrow;
926
926
  };
927
927
  var scriptDirectory = "";
928
- function locateFile(path15) {
928
+ function locateFile(path17) {
929
929
  if (Module["locateFile"]) {
930
- return Module["locateFile"](path15, scriptDirectory);
930
+ return Module["locateFile"](path17, scriptDirectory);
931
931
  }
932
- return scriptDirectory + path15;
932
+ return scriptDirectory + path17;
933
933
  }
934
934
  var readAsync, readBinary;
935
935
  if (ENVIRONMENT_IS_NODE) {
@@ -943,10 +943,10 @@ var require_tree_sitter = __commonJS({
943
943
  };
944
944
  readAsync = (filename, binary2 = true) => {
945
945
  filename = isFileURI(filename) ? new URL(filename) : nodePath.normalize(filename);
946
- return new Promise((resolve6, reject) => {
946
+ return new Promise((resolve7, reject) => {
947
947
  fs.readFile(filename, binary2 ? void 0 : "utf8", (err2, data) => {
948
948
  if (err2) reject(err2);
949
- else resolve6(binary2 ? data.buffer : data);
949
+ else resolve7(binary2 ? data.buffer : data);
950
950
  });
951
951
  });
952
952
  };
@@ -987,13 +987,13 @@ var require_tree_sitter = __commonJS({
987
987
  }
988
988
  readAsync = (url) => {
989
989
  if (isFileURI(url)) {
990
- return new Promise((reject, resolve6) => {
990
+ return new Promise((reject, resolve7) => {
991
991
  var xhr = new XMLHttpRequest();
992
992
  xhr.open("GET", url, true);
993
993
  xhr.responseType = "arraybuffer";
994
994
  xhr.onload = () => {
995
995
  if (xhr.status == 200 || xhr.status == 0 && xhr.response) {
996
- resolve6(xhr.response);
996
+ resolve7(xhr.response);
997
997
  }
998
998
  reject(xhr.status);
999
999
  };
@@ -1953,8 +1953,8 @@ var require_tree_sitter = __commonJS({
1953
1953
  }
1954
1954
  var libFile = locateFile(libName2);
1955
1955
  if (flags2.loadAsync) {
1956
- return new Promise(function(resolve6, reject) {
1957
- asyncLoad(libFile, resolve6, reject);
1956
+ return new Promise(function(resolve7, reject) {
1957
+ asyncLoad(libFile, resolve7, reject);
1958
1958
  });
1959
1959
  }
1960
1960
  if (!readBinary) {
@@ -3450,8 +3450,8 @@ var require_tree_sitter = __commonJS({
3450
3450
  } else {
3451
3451
  const url = input;
3452
3452
  if (typeof process !== "undefined" && process.versions && process.versions.node) {
3453
- const fs13 = require("fs");
3454
- bytes = Promise.resolve(fs13.readFileSync(url));
3453
+ const fs14 = require("fs");
3454
+ bytes = Promise.resolve(fs14.readFileSync(url));
3455
3455
  } else {
3456
3456
  bytes = fetch(url).then((response) => response.arrayBuffer().then((buffer) => {
3457
3457
  if (response.ok) {
@@ -3912,8 +3912,8 @@ function isSupportedReasoningEffort(adapter, modelId, value) {
3912
3912
  }
3913
3913
 
3914
3914
  // src/server/agent-server.ts
3915
- var import_promises5 = require("fs/promises");
3916
- var import_node_path8 = require("path");
3915
+ var import_promises7 = require("fs/promises");
3916
+ var import_node_path10 = require("path");
3917
3917
  var import_node_url2 = require("url");
3918
3918
  var import_sdk5 = require("@agentclientprotocol/sdk");
3919
3919
  var import_node_server = require("@hono/node-server");
@@ -3959,8 +3959,8 @@ function pathspec(...paths) {
3959
3959
  cache.set(key, paths);
3960
3960
  return key;
3961
3961
  }
3962
- function isPathSpec(path15) {
3963
- return path15 instanceof String && cache.has(path15);
3962
+ function isPathSpec(path17) {
3963
+ return path17 instanceof String && cache.has(path17);
3964
3964
  }
3965
3965
  function toPaths(pathSpec) {
3966
3966
  return cache.get(pathSpec) || [];
@@ -4049,8 +4049,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
4049
4049
  function forEachLineWithContent(input, callback) {
4050
4050
  return toLinesWithContent(input, true).map((line) => callback(line));
4051
4051
  }
4052
- function folderExists(path15) {
4053
- return (0, import_file_exists.exists)(path15, import_file_exists.FOLDER);
4052
+ function folderExists(path17) {
4053
+ return (0, import_file_exists.exists)(path17, import_file_exists.FOLDER);
4054
4054
  }
4055
4055
  function append(target, item) {
4056
4056
  if (Array.isArray(target)) {
@@ -4454,8 +4454,8 @@ function checkIsRepoRootTask() {
4454
4454
  commands,
4455
4455
  format: "utf-8",
4456
4456
  onError,
4457
- parser(path15) {
4458
- return /^\.(git)?$/.test(path15.trim());
4457
+ parser(path17) {
4458
+ return /^\.(git)?$/.test(path17.trim());
4459
4459
  }
4460
4460
  };
4461
4461
  }
@@ -4889,11 +4889,11 @@ function parseGrep(grep) {
4889
4889
  const paths = /* @__PURE__ */ new Set();
4890
4890
  const results = {};
4891
4891
  forEachLineWithContent(grep, (input) => {
4892
- const [path15, line, preview] = input.split(NULL);
4893
- paths.add(path15);
4894
- (results[path15] = results[path15] || []).push({
4892
+ const [path17, line, preview] = input.split(NULL);
4893
+ paths.add(path17);
4894
+ (results[path17] = results[path17] || []).push({
4895
4895
  line: asNumber(line),
4896
- path: path15,
4896
+ path: path17,
4897
4897
  preview
4898
4898
  });
4899
4899
  });
@@ -5658,14 +5658,14 @@ var init_hash_object = __esm({
5658
5658
  init_task();
5659
5659
  }
5660
5660
  });
5661
- function parseInit(bare, path15, text2) {
5661
+ function parseInit(bare, path17, text2) {
5662
5662
  const response = String(text2).trim();
5663
5663
  let result;
5664
5664
  if (result = initResponseRegex.exec(response)) {
5665
- return new InitSummary(bare, path15, false, result[1]);
5665
+ return new InitSummary(bare, path17, false, result[1]);
5666
5666
  }
5667
5667
  if (result = reInitResponseRegex.exec(response)) {
5668
- return new InitSummary(bare, path15, true, result[1]);
5668
+ return new InitSummary(bare, path17, true, result[1]);
5669
5669
  }
5670
5670
  let gitDir = "";
5671
5671
  const tokens = response.split(" ");
@@ -5676,7 +5676,7 @@ function parseInit(bare, path15, text2) {
5676
5676
  break;
5677
5677
  }
5678
5678
  }
5679
- return new InitSummary(bare, path15, /^re/i.test(response), gitDir);
5679
+ return new InitSummary(bare, path17, /^re/i.test(response), gitDir);
5680
5680
  }
5681
5681
  var InitSummary;
5682
5682
  var initResponseRegex;
@@ -5685,9 +5685,9 @@ var init_InitSummary = __esm({
5685
5685
  "src/lib/responses/InitSummary.ts"() {
5686
5686
  "use strict";
5687
5687
  InitSummary = class {
5688
- constructor(bare, path15, existing, gitDir) {
5688
+ constructor(bare, path17, existing, gitDir) {
5689
5689
  this.bare = bare;
5690
- this.path = path15;
5690
+ this.path = path17;
5691
5691
  this.existing = existing;
5692
5692
  this.gitDir = gitDir;
5693
5693
  }
@@ -5699,7 +5699,7 @@ var init_InitSummary = __esm({
5699
5699
  function hasBareCommand(command) {
5700
5700
  return command.includes(bareCommand);
5701
5701
  }
5702
- function initTask(bare = false, path15, customArgs) {
5702
+ function initTask(bare = false, path17, customArgs) {
5703
5703
  const commands = ["init", ...customArgs];
5704
5704
  if (bare && !hasBareCommand(commands)) {
5705
5705
  commands.splice(1, 0, bareCommand);
@@ -5708,7 +5708,7 @@ function initTask(bare = false, path15, customArgs) {
5708
5708
  commands,
5709
5709
  format: "utf-8",
5710
5710
  parser(text2) {
5711
- return parseInit(commands.includes("--bare"), path15, text2);
5711
+ return parseInit(commands.includes("--bare"), path17, text2);
5712
5712
  }
5713
5713
  };
5714
5714
  }
@@ -6524,12 +6524,12 @@ var init_FileStatusSummary = __esm({
6524
6524
  "use strict";
6525
6525
  fromPathRegex = /^(.+)\0(.+)$/;
6526
6526
  FileStatusSummary = class {
6527
- constructor(path15, index, working_dir) {
6528
- this.path = path15;
6527
+ constructor(path17, index, working_dir) {
6528
+ this.path = path17;
6529
6529
  this.index = index;
6530
6530
  this.working_dir = working_dir;
6531
6531
  if (index === "R" || working_dir === "R") {
6532
- const detail = fromPathRegex.exec(path15) || [null, path15, path15];
6532
+ const detail = fromPathRegex.exec(path17) || [null, path17, path17];
6533
6533
  this.from = detail[2] || "";
6534
6534
  this.path = detail[1] || "";
6535
6535
  }
@@ -6560,14 +6560,14 @@ function splitLine(result, lineStr) {
6560
6560
  default:
6561
6561
  return;
6562
6562
  }
6563
- function data(index, workingDir, path15) {
6563
+ function data(index, workingDir, path17) {
6564
6564
  const raw = `${index}${workingDir}`;
6565
6565
  const handler = parsers6.get(raw);
6566
6566
  if (handler) {
6567
- handler(result, path15);
6567
+ handler(result, path17);
6568
6568
  }
6569
6569
  if (raw !== "##" && raw !== "!!") {
6570
- result.files.push(new FileStatusSummary(path15, index, workingDir));
6570
+ result.files.push(new FileStatusSummary(path17, index, workingDir));
6571
6571
  }
6572
6572
  }
6573
6573
  }
@@ -6880,9 +6880,9 @@ var init_simple_git_api = __esm({
6880
6880
  next
6881
6881
  );
6882
6882
  }
6883
- hashObject(path15, write) {
6883
+ hashObject(path17, write) {
6884
6884
  return this._runTask(
6885
- hashObjectTask(path15, write === true),
6885
+ hashObjectTask(path17, write === true),
6886
6886
  trailingFunctionArgument(arguments)
6887
6887
  );
6888
6888
  }
@@ -7235,8 +7235,8 @@ var init_branch = __esm({
7235
7235
  }
7236
7236
  });
7237
7237
  function toPath(input) {
7238
- const path15 = input.trim().replace(/^["']|["']$/g, "");
7239
- return path15 && (0, import_node_path.normalize)(path15);
7238
+ const path17 = input.trim().replace(/^["']|["']$/g, "");
7239
+ return path17 && (0, import_node_path.normalize)(path17);
7240
7240
  }
7241
7241
  var parseCheckIgnore;
7242
7242
  var init_CheckIgnore = __esm({
@@ -7550,8 +7550,8 @@ __export(sub_module_exports, {
7550
7550
  subModuleTask: () => subModuleTask,
7551
7551
  updateSubModuleTask: () => updateSubModuleTask
7552
7552
  });
7553
- function addSubModuleTask(repo, path15) {
7554
- return subModuleTask(["add", repo, path15]);
7553
+ function addSubModuleTask(repo, path17) {
7554
+ return subModuleTask(["add", repo, path17]);
7555
7555
  }
7556
7556
  function initSubModuleTask(customArgs) {
7557
7557
  return subModuleTask(["init", ...customArgs]);
@@ -7881,8 +7881,8 @@ var require_git = __commonJS2({
7881
7881
  }
7882
7882
  return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
7883
7883
  };
7884
- Git2.prototype.submoduleAdd = function(repo, path15, then) {
7885
- return this._runTask(addSubModuleTask2(repo, path15), trailingFunctionArgument2(arguments));
7884
+ Git2.prototype.submoduleAdd = function(repo, path17, then) {
7885
+ return this._runTask(addSubModuleTask2(repo, path17), trailingFunctionArgument2(arguments));
7886
7886
  };
7887
7887
  Git2.prototype.submoduleUpdate = function(args2, then) {
7888
7888
  return this._runTask(
@@ -8498,10 +8498,10 @@ async function getIndexLockPath(repoPath) {
8498
8498
  async function getLockInfo(repoPath) {
8499
8499
  const lockPath = await getIndexLockPath(repoPath);
8500
8500
  try {
8501
- const stat2 = await import_promises.default.stat(lockPath);
8501
+ const stat4 = await import_promises.default.stat(lockPath);
8502
8502
  return {
8503
8503
  path: lockPath,
8504
- ageMs: Date.now() - stat2.mtimeMs
8504
+ ageMs: Date.now() - stat4.mtimeMs
8505
8505
  };
8506
8506
  } catch {
8507
8507
  return null;
@@ -8536,10 +8536,10 @@ var AsyncReaderWriterLock = class {
8536
8536
  this.readers++;
8537
8537
  return;
8538
8538
  }
8539
- return new Promise((resolve6) => {
8539
+ return new Promise((resolve7) => {
8540
8540
  this.readQueue.push(() => {
8541
8541
  this.readers++;
8542
- resolve6();
8542
+ resolve7();
8543
8543
  });
8544
8544
  });
8545
8545
  }
@@ -8553,11 +8553,11 @@ var AsyncReaderWriterLock = class {
8553
8553
  return;
8554
8554
  }
8555
8555
  this.writerWaiting = true;
8556
- return new Promise((resolve6) => {
8556
+ return new Promise((resolve7) => {
8557
8557
  this.writeQueue.push(() => {
8558
8558
  this.writerWaiting = this.writeQueue.length > 0;
8559
8559
  this.writer = true;
8560
- resolve6();
8560
+ resolve7();
8561
8561
  });
8562
8562
  });
8563
8563
  }
@@ -8729,7 +8729,7 @@ var import_zod3 = require("zod");
8729
8729
  // package.json
8730
8730
  var package_default = {
8731
8731
  name: "@posthog/agent",
8732
- version: "2.3.385",
8732
+ version: "2.3.387",
8733
8733
  repository: "https://github.com/PostHog/code",
8734
8734
  description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
8735
8735
  exports: {
@@ -8785,9 +8785,25 @@ var package_default = {
8785
8785
  types: "./dist/execution-mode.d.ts",
8786
8786
  import: "./dist/execution-mode.js"
8787
8787
  },
8788
+ "./resume": {
8789
+ types: "./dist/resume.d.ts",
8790
+ import: "./dist/resume.js"
8791
+ },
8792
+ "./handoff-checkpoint": {
8793
+ types: "./dist/handoff-checkpoint.d.ts",
8794
+ import: "./dist/handoff-checkpoint.js"
8795
+ },
8796
+ "./tree-tracker": {
8797
+ types: "./dist/tree-tracker.d.ts",
8798
+ import: "./dist/tree-tracker.js"
8799
+ },
8788
8800
  "./server": {
8789
8801
  types: "./dist/server/agent-server.d.ts",
8790
8802
  import: "./dist/server/agent-server.js"
8803
+ },
8804
+ "./server/schemas": {
8805
+ types: "./dist/server/schemas.d.ts",
8806
+ import: "./dist/server/schemas.js"
8791
8807
  }
8792
8808
  },
8793
8809
  bin: {
@@ -8878,6 +8894,8 @@ var POSTHOG_NOTIFICATIONS = {
8878
8894
  SDK_SESSION: "_posthog/sdk_session",
8879
8895
  /** Tree state snapshot captured (git tree hash + file archive) */
8880
8896
  TREE_SNAPSHOT: "_posthog/tree_snapshot",
8897
+ /** Git checkpoint captured for handoff */
8898
+ GIT_CHECKPOINT: "_posthog/git_checkpoint",
8881
8899
  /** Agent mode changed (interactive/background) */
8882
8900
  MODE_CHANGE: "_posthog/mode_change",
8883
8901
  /** Request to resume a session from previous state */
@@ -8984,17 +9002,17 @@ var Pushable = class {
8984
9002
  resolvers = [];
8985
9003
  done = false;
8986
9004
  push(item) {
8987
- const resolve6 = this.resolvers.shift();
8988
- if (resolve6) {
8989
- resolve6({ value: item, done: false });
9005
+ const resolve7 = this.resolvers.shift();
9006
+ if (resolve7) {
9007
+ resolve7({ value: item, done: false });
8990
9008
  } else {
8991
9009
  this.queue.push(item);
8992
9010
  }
8993
9011
  }
8994
9012
  end() {
8995
9013
  this.done = true;
8996
- for (const resolve6 of this.resolvers) {
8997
- resolve6({ value: void 0, done: true });
9014
+ for (const resolve7 of this.resolvers) {
9015
+ resolve7({ value: void 0, done: true });
8998
9016
  }
8999
9017
  this.resolvers = [];
9000
9018
  }
@@ -9011,8 +9029,8 @@ var Pushable = class {
9011
9029
  done: true
9012
9030
  });
9013
9031
  }
9014
- return new Promise((resolve6) => {
9015
- this.resolvers.push(resolve6);
9032
+ return new Promise((resolve7) => {
9033
+ this.resolvers.push(resolve7);
9016
9034
  });
9017
9035
  }
9018
9036
  };
@@ -9126,20 +9144,20 @@ function nodeReadableToWebReadable(nodeStream) {
9126
9144
  function nodeWritableToWebWritable(nodeStream) {
9127
9145
  return new import_web.WritableStream({
9128
9146
  write(chunk) {
9129
- return new Promise((resolve6, reject) => {
9147
+ return new Promise((resolve7, reject) => {
9130
9148
  const ok = nodeStream.write(Buffer.from(chunk), (err2) => {
9131
9149
  if (err2) reject(err2);
9132
9150
  });
9133
9151
  if (ok) {
9134
- resolve6();
9152
+ resolve7();
9135
9153
  } else {
9136
- nodeStream.once("drain", resolve6);
9154
+ nodeStream.once("drain", resolve7);
9137
9155
  }
9138
9156
  });
9139
9157
  },
9140
9158
  close() {
9141
- return new Promise((resolve6) => {
9142
- nodeStream.end(resolve6);
9159
+ return new Promise((resolve7) => {
9160
+ nodeStream.end(resolve7);
9143
9161
  });
9144
9162
  },
9145
9163
  abort(reason) {
@@ -13081,9 +13099,9 @@ var PostHogEnricher = class {
13081
13099
  }
13082
13100
  let mtimeMs = 0;
13083
13101
  try {
13084
- const stat2 = await fs4.stat(absPath);
13085
- mtimeMs = stat2.mtimeMs;
13086
- if (stat2.size > MAX_WRAPPER_SOURCE_BYTES) {
13102
+ const stat22 = await fs4.stat(absPath);
13103
+ mtimeMs = stat22.mtimeMs;
13104
+ if (stat22.size > MAX_WRAPPER_SOURCE_BYTES) {
13087
13105
  return this.setWrapperCache(absPath, mtimeMs, []);
13088
13106
  }
13089
13107
  } catch {
@@ -13249,7 +13267,7 @@ async function buildWrapperContext(deps, content, langId, absPath) {
13249
13267
  // src/utils/common.ts
13250
13268
  async function withTimeout(operation, timeoutMs) {
13251
13269
  const timeoutPromise = new Promise(
13252
- (resolve6) => setTimeout(() => resolve6({ result: "timeout" }), timeoutMs)
13270
+ (resolve7) => setTimeout(() => resolve7({ result: "timeout" }), timeoutMs)
13253
13271
  );
13254
13272
  const operationPromise = operation.then((value) => ({
13255
13273
  result: "success",
@@ -13547,8 +13565,8 @@ var ToolContentBuilder = class {
13547
13565
  this.items.push({ type: "content", content: image(data, mimeType, uri) });
13548
13566
  return this;
13549
13567
  }
13550
- diff(path15, oldText, newText) {
13551
- this.items.push({ type: "diff", path: path15, oldText, newText });
13568
+ diff(path17, oldText, newText) {
13569
+ this.items.push({ type: "diff", path: path17, oldText, newText });
13552
13570
  return this;
13553
13571
  }
13554
13572
  build() {
@@ -13735,7 +13753,7 @@ function buildToolKey(serverName, toolName) {
13735
13753
  return `mcp__${serverName}__${toolName}`;
13736
13754
  }
13737
13755
  function delay2(ms) {
13738
- return new Promise((resolve6) => setTimeout(resolve6, ms));
13756
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
13739
13757
  }
13740
13758
  async function fetchMcpToolMetadata(q, logger = new Logger({ debug: false, prefix: "[McpToolMetadata]" })) {
13741
13759
  let retries = 0;
@@ -15949,8 +15967,8 @@ var AsyncMutex = class {
15949
15967
  this.locked = true;
15950
15968
  return;
15951
15969
  }
15952
- return new Promise((resolve6) => {
15953
- this.queue.push(resolve6);
15970
+ return new Promise((resolve7) => {
15971
+ this.queue.push(resolve7);
15954
15972
  });
15955
15973
  }
15956
15974
  release() {
@@ -16477,8 +16495,8 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
16477
16495
  if (this.session.promptRunning) {
16478
16496
  this.session.input.push(userMessage);
16479
16497
  const order = this.session.nextPendingOrder++;
16480
- const cancelled = await new Promise((resolve6) => {
16481
- this.session.pendingMessages.set(promptUuid, { resolve: resolve6, order });
16498
+ const cancelled = await new Promise((resolve7) => {
16499
+ this.session.pendingMessages.set(promptUuid, { resolve: resolve7, order });
16482
16500
  });
16483
16501
  if (cancelled) {
16484
16502
  return { stopReason: "cancelled" };
@@ -17359,7 +17377,7 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
17359
17377
  */
17360
17378
  deferBackgroundFetches(q) {
17361
17379
  Promise.all([
17362
- new Promise((resolve6) => setTimeout(resolve6, 10)).then(
17380
+ new Promise((resolve7) => setTimeout(resolve7, 10)).then(
17363
17381
  () => this.sendAvailableCommandsUpdate()
17364
17382
  ),
17365
17383
  fetchMcpToolMetadata(q, this.logger).then(() => {
@@ -18323,245 +18341,19 @@ function createCodexConnection(config) {
18323
18341
  };
18324
18342
  }
18325
18343
 
18326
- // src/adapters/claude/session/jsonl-hydration.ts
18344
+ // src/handoff-checkpoint.ts
18345
+ var import_promises3 = require("fs/promises");
18346
+ var import_node_path6 = require("path");
18347
+
18348
+ // ../git/dist/handoff.js
18349
+ var import_node_child_process4 = require("child_process");
18350
+ var import_promises2 = require("fs/promises");
18351
+ var import_node_path5 = __toESM(require("path"), 1);
18352
+
18353
+ // ../git/dist/sagas/checkpoint.js
18327
18354
  var import_node_crypto2 = require("crypto");
18328
18355
  var fs10 = __toESM(require("fs/promises"), 1);
18329
- var os6 = __toESM(require("os"), 1);
18330
18356
  var path12 = __toESM(require("path"), 1);
18331
- var CHARS_PER_TOKEN = 4;
18332
- var DEFAULT_MAX_TOKENS = 15e4;
18333
- function estimateTurnTokens(turn) {
18334
- let chars = 0;
18335
- for (const block of turn.content) {
18336
- if ("text" in block && typeof block.text === "string") {
18337
- chars += block.text.length;
18338
- }
18339
- }
18340
- if (turn.toolCalls) {
18341
- for (const tc of turn.toolCalls) {
18342
- chars += JSON.stringify(tc.input ?? "").length;
18343
- if (tc.result !== void 0) {
18344
- chars += typeof tc.result === "string" ? tc.result.length : JSON.stringify(tc.result).length;
18345
- }
18346
- }
18347
- }
18348
- return Math.ceil(chars / CHARS_PER_TOKEN);
18349
- }
18350
- function selectRecentTurns(turns, maxTokens = DEFAULT_MAX_TOKENS) {
18351
- let budget = maxTokens;
18352
- let startIndex = turns.length;
18353
- for (let i2 = turns.length - 1; i2 >= 0; i2--) {
18354
- const cost = estimateTurnTokens(turns[i2]);
18355
- if (cost > budget) break;
18356
- budget -= cost;
18357
- startIndex = i2;
18358
- }
18359
- while (startIndex < turns.length && turns[startIndex].role !== "user") {
18360
- startIndex++;
18361
- }
18362
- return turns.slice(startIndex);
18363
- }
18364
-
18365
- // src/utils/gateway.ts
18366
- function getGatewayBaseUrl(posthogHost) {
18367
- const url = new URL(posthogHost);
18368
- const hostname = url.hostname;
18369
- if (hostname === "localhost" || hostname === "127.0.0.1") {
18370
- return `${url.protocol}//localhost:3308`;
18371
- }
18372
- if (hostname === "host.docker.internal") {
18373
- return `${url.protocol}//host.docker.internal:3308`;
18374
- }
18375
- const region = hostname.match(/^(us|eu)\.posthog\.com$/)?.[1] ?? "us";
18376
- return `https://gateway.${region}.posthog.com`;
18377
- }
18378
- function getLlmGatewayUrl(posthogHost, product = "posthog_code") {
18379
- return `${getGatewayBaseUrl(posthogHost)}/${product}`;
18380
- }
18381
-
18382
- // src/posthog-api.ts
18383
- var DEFAULT_USER_AGENT = `posthog/agent.hog.dev; version: ${package_default.version}`;
18384
- var PostHogAPIClient = class {
18385
- config;
18386
- constructor(config) {
18387
- this.config = config;
18388
- }
18389
- get baseUrl() {
18390
- const host = this.config.apiUrl.endsWith("/") ? this.config.apiUrl.slice(0, -1) : this.config.apiUrl;
18391
- return host;
18392
- }
18393
- isAuthFailure(status) {
18394
- return status === 401 || status === 403;
18395
- }
18396
- async resolveApiKey(forceRefresh = false) {
18397
- if (forceRefresh && this.config.refreshApiKey) {
18398
- return this.config.refreshApiKey();
18399
- }
18400
- return this.config.getApiKey();
18401
- }
18402
- async buildHeaders(options, forceRefresh = false) {
18403
- const headers = new Headers(options.headers);
18404
- headers.set(
18405
- "Authorization",
18406
- `Bearer ${await this.resolveApiKey(forceRefresh)}`
18407
- );
18408
- headers.set("Content-Type", "application/json");
18409
- headers.set("User-Agent", this.config.userAgent ?? DEFAULT_USER_AGENT);
18410
- return headers;
18411
- }
18412
- async performRequest(endpoint, options, forceRefresh = false) {
18413
- const url = `${this.baseUrl}${endpoint}`;
18414
- return fetch(url, {
18415
- ...options,
18416
- headers: await this.buildHeaders(options, forceRefresh)
18417
- });
18418
- }
18419
- async performRequestWithRetry(endpoint, options = {}) {
18420
- let response = await this.performRequest(endpoint, options);
18421
- if (!response.ok && this.isAuthFailure(response.status)) {
18422
- response = await this.performRequest(endpoint, options, true);
18423
- }
18424
- return response;
18425
- }
18426
- async apiRequest(endpoint, options = {}) {
18427
- const response = await this.performRequestWithRetry(endpoint, options);
18428
- if (!response.ok) {
18429
- let errorMessage;
18430
- try {
18431
- const errorResponse = await response.json();
18432
- errorMessage = `Failed request: [${response.status}] ${JSON.stringify(errorResponse)}`;
18433
- } catch {
18434
- errorMessage = `Failed request: [${response.status}] ${response.statusText}`;
18435
- }
18436
- throw new Error(errorMessage);
18437
- }
18438
- return response.json();
18439
- }
18440
- getTeamId() {
18441
- return this.config.projectId;
18442
- }
18443
- async getApiKey(forceRefresh = false) {
18444
- return this.resolveApiKey(forceRefresh);
18445
- }
18446
- getLlmGatewayUrl() {
18447
- return getLlmGatewayUrl(this.baseUrl);
18448
- }
18449
- async getTask(taskId) {
18450
- const teamId = this.getTeamId();
18451
- return this.apiRequest(`/api/projects/${teamId}/tasks/${taskId}/`);
18452
- }
18453
- async getTaskRun(taskId, runId) {
18454
- const teamId = this.getTeamId();
18455
- return this.apiRequest(
18456
- `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/`
18457
- );
18458
- }
18459
- async updateTaskRun(taskId, runId, payload) {
18460
- const teamId = this.getTeamId();
18461
- return this.apiRequest(
18462
- `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/`,
18463
- {
18464
- method: "PATCH",
18465
- body: JSON.stringify(payload)
18466
- }
18467
- );
18468
- }
18469
- async setTaskRunOutput(taskId, runId, output) {
18470
- return this.apiRequest(
18471
- `/api/projects/${this.getTeamId()}/tasks/${taskId}/runs/${runId}/set_output/`,
18472
- {
18473
- method: "PATCH",
18474
- body: JSON.stringify(output)
18475
- }
18476
- );
18477
- }
18478
- async appendTaskRunLog(taskId, runId, entries) {
18479
- const teamId = this.getTeamId();
18480
- return this.apiRequest(
18481
- `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/append_log/`,
18482
- {
18483
- method: "POST",
18484
- body: JSON.stringify({ entries })
18485
- }
18486
- );
18487
- }
18488
- async relayMessage(taskId, runId, text2) {
18489
- const teamId = this.getTeamId();
18490
- await this.apiRequest(
18491
- `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/relay_message/`,
18492
- {
18493
- method: "POST",
18494
- body: JSON.stringify({ text: text2 })
18495
- }
18496
- );
18497
- }
18498
- async uploadTaskArtifacts(taskId, runId, artifacts) {
18499
- if (!artifacts.length) {
18500
- return [];
18501
- }
18502
- const teamId = this.getTeamId();
18503
- const response = await this.apiRequest(
18504
- `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/artifacts/`,
18505
- {
18506
- method: "POST",
18507
- body: JSON.stringify({ artifacts })
18508
- }
18509
- );
18510
- return response.artifacts ?? [];
18511
- }
18512
- /**
18513
- * Download artifact content by storage path
18514
- * Streams the file through the PostHog backend so the sandbox does not need
18515
- * direct access to object storage.
18516
- */
18517
- async downloadArtifact(taskId, runId, storagePath) {
18518
- const teamId = this.getTeamId();
18519
- try {
18520
- const response = await this.performRequestWithRetry(
18521
- `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/artifacts/download/`,
18522
- {
18523
- method: "POST",
18524
- body: JSON.stringify({ storage_path: storagePath })
18525
- }
18526
- );
18527
- if (!response.ok) {
18528
- throw new Error(`Failed to download artifact: ${response.status}`);
18529
- }
18530
- return response.arrayBuffer();
18531
- } catch {
18532
- return null;
18533
- }
18534
- }
18535
- /**
18536
- * Fetch logs for a task run via the logs API endpoint
18537
- * @param taskRun - The task run to fetch logs for
18538
- * @returns Array of stored entries, or empty array if no logs available
18539
- */
18540
- async fetchTaskRunLogs(taskRun) {
18541
- const teamId = this.getTeamId();
18542
- const endpoint = `/api/projects/${teamId}/tasks/${taskRun.task}/runs/${taskRun.id}/logs`;
18543
- try {
18544
- const response = await this.performRequestWithRetry(endpoint);
18545
- if (!response.ok) {
18546
- if (response.status === 404) {
18547
- return [];
18548
- }
18549
- throw new Error(
18550
- `Failed to fetch logs: ${response.status} ${response.statusText}`
18551
- );
18552
- }
18553
- const content = await response.text();
18554
- if (!content.trim()) {
18555
- return [];
18556
- }
18557
- return content.trim().split("\n").map((line) => JSON.parse(line));
18558
- } catch (error) {
18559
- throw new Error(
18560
- `Failed to fetch task run logs: ${error instanceof Error ? error.message : String(error)}`
18561
- );
18562
- }
18563
- }
18564
- };
18565
18357
 
18566
18358
  // ../shared/dist/index.js
18567
18359
  var CLOUD_PROMPT_PREFIX = "__twig_cloud_prompt_v1__:";
@@ -18705,16 +18497,6 @@ var Saga = class {
18705
18497
  }
18706
18498
  };
18707
18499
 
18708
- // src/sagas/apply-snapshot-saga.ts
18709
- var import_promises2 = require("fs/promises");
18710
- var import_node_path5 = require("path");
18711
-
18712
- // ../git/dist/sagas/tree.js
18713
- var import_node_fs3 = require("fs");
18714
- var fs11 = __toESM(require("fs/promises"), 1);
18715
- var path13 = __toESM(require("path"), 1);
18716
- var tar = __toESM(require("tar"), 1);
18717
-
18718
18500
  // ../git/dist/git-saga.js
18719
18501
  var GitSaga = class extends Saga {
18720
18502
  _git = null;
@@ -18733,20 +18515,1082 @@ var GitSaga = class extends Saga {
18733
18515
  }
18734
18516
  };
18735
18517
 
18736
- // ../git/dist/sagas/tree.js
18737
- var CaptureTreeSaga = class extends GitSaga {
18518
+ // ../git/dist/sagas/checkpoint.js
18519
+ var CHECKPOINT_REF_PREFIX = "refs/posthog-code-checkpoint/";
18520
+ var CHECKPOINT_VERSION = "v1";
18521
+ var UNMERGED_INDEX_ERROR = "Cannot capture checkpoint with unresolved merge conflicts in the index";
18522
+ var GIT_BUSY_ERROR = "Cannot capture checkpoint while git operation is in progress";
18523
+ var CHECKPOINT_AUTHOR = {
18524
+ name: "PostHog Code",
18525
+ email: "posthog-code@local"
18526
+ };
18527
+ var CaptureCheckpointSaga = class extends GitSaga {
18528
+ sagaName = "CaptureCheckpointSaga";
18529
+ async executeGitOperations(input) {
18530
+ const { baseDir } = input;
18531
+ const headInfo = await this.readOnlyStep("get_head_info", () => getHeadInfo(this.git));
18532
+ const busyState = await this.readOnlyStep("check_git_busy", () => getGitBusyState(this.git));
18533
+ if (busyState.busy) {
18534
+ throw new Error(`${GIT_BUSY_ERROR}: ${busyState.operation}`);
18535
+ }
18536
+ const hasUnmerged = await this.readOnlyStep("check_unmerged_index", () => hasUnmergedEntries(this.git));
18537
+ if (hasUnmerged) {
18538
+ throw new Error(UNMERGED_INDEX_ERROR);
18539
+ }
18540
+ const indexTree = await this.readOnlyStep("write_index_tree", () => this.git.raw(["write-tree"]));
18541
+ const worktreeTree = await this.readOnlyStep("write_worktree_tree", () => createWorktreeTree(this.git, baseDir, headInfo.head));
18542
+ const metaTree = await this.readOnlyStep("write_meta_tree", () => createMetaTree(this.git, baseDir, indexTree.trim(), worktreeTree.trim()));
18543
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
18544
+ const message = formatCheckpointMessage({
18545
+ head: headInfo.head,
18546
+ branch: headInfo.branch,
18547
+ indexTree: indexTree.trim(),
18548
+ worktreeTree: worktreeTree.trim(),
18549
+ timestamp
18550
+ });
18551
+ const commitHash = await this.step({
18552
+ name: "create_checkpoint_commit",
18553
+ execute: async () => {
18554
+ const commitGit = this.git.env({
18555
+ ...process.env,
18556
+ GIT_AUTHOR_NAME: CHECKPOINT_AUTHOR.name,
18557
+ GIT_AUTHOR_EMAIL: CHECKPOINT_AUTHOR.email,
18558
+ GIT_COMMITTER_NAME: CHECKPOINT_AUTHOR.name,
18559
+ GIT_COMMITTER_EMAIL: CHECKPOINT_AUTHOR.email
18560
+ });
18561
+ const rawCommit = await commitGit.raw([
18562
+ "commit-tree",
18563
+ metaTree.trim(),
18564
+ ...headInfo.head ? ["-p", headInfo.head] : [],
18565
+ "-m",
18566
+ message
18567
+ ]);
18568
+ return rawCommit.trim();
18569
+ },
18570
+ rollback: async () => {
18571
+ }
18572
+ });
18573
+ const checkpointId = input.checkpointId ?? (0, import_node_crypto2.randomUUID)();
18574
+ const refName = `${CHECKPOINT_REF_PREFIX}${checkpointId}`;
18575
+ const existingRef = await this.readOnlyStep("check_existing_ref", async () => {
18576
+ try {
18577
+ await this.git.revparse(["--verify", refName]);
18578
+ return true;
18579
+ } catch {
18580
+ return false;
18581
+ }
18582
+ });
18583
+ if (existingRef) {
18584
+ throw new Error(`Checkpoint ref already exists: ${refName}`);
18585
+ }
18586
+ await this.step({
18587
+ name: "update_checkpoint_ref",
18588
+ execute: () => this.git.raw(["update-ref", refName, commitHash]),
18589
+ rollback: async () => {
18590
+ await this.git.raw(["update-ref", "-d", refName]).catch(() => {
18591
+ });
18592
+ }
18593
+ });
18594
+ return {
18595
+ checkpointId,
18596
+ commit: commitHash,
18597
+ head: headInfo.head,
18598
+ branch: headInfo.branch,
18599
+ indexTree: indexTree.trim(),
18600
+ worktreeTree: worktreeTree.trim(),
18601
+ timestamp
18602
+ };
18603
+ }
18604
+ };
18605
+ async function getHeadInfo(git) {
18606
+ let head = null;
18607
+ let branch = null;
18608
+ try {
18609
+ head = (await git.revparse(["HEAD"]))?.trim() || null;
18610
+ } catch {
18611
+ head = null;
18612
+ }
18613
+ try {
18614
+ const rawBranch = await git.raw(["symbolic-ref", "--short", "HEAD"]);
18615
+ branch = rawBranch.trim() || null;
18616
+ } catch {
18617
+ branch = null;
18618
+ }
18619
+ return { head, branch };
18620
+ }
18621
+ async function hasUnmergedEntries(git) {
18622
+ const output = await git.raw(["ls-files", "--unmerged"]);
18623
+ return output.trim().length > 0;
18624
+ }
18625
+ async function getGitBusyState(git) {
18626
+ const toplevel = (await git.raw(["rev-parse", "--show-toplevel"])).trim();
18627
+ const resolveGitPath = async (gitPath) => {
18628
+ const relative = (await git.raw(["rev-parse", "--git-path", gitPath])).trim();
18629
+ return path12.isAbsolute(relative) ? relative : path12.resolve(toplevel, relative);
18630
+ };
18631
+ const pathExists = async (gitPath) => {
18632
+ const resolved = await resolveGitPath(gitPath);
18633
+ try {
18634
+ await fs10.access(resolved);
18635
+ return true;
18636
+ } catch {
18637
+ return false;
18638
+ }
18639
+ };
18640
+ const dirExists = async (gitPath) => {
18641
+ const resolved = await resolveGitPath(gitPath);
18642
+ try {
18643
+ const stat4 = await fs10.stat(resolved);
18644
+ return stat4.isDirectory();
18645
+ } catch {
18646
+ return false;
18647
+ }
18648
+ };
18649
+ if (await dirExists("rebase-merge") || await dirExists("rebase-apply")) {
18650
+ return { busy: true, operation: "rebase" };
18651
+ }
18652
+ if (await pathExists("MERGE_HEAD")) {
18653
+ return { busy: true, operation: "merge" };
18654
+ }
18655
+ if (await pathExists("CHERRY_PICK_HEAD")) {
18656
+ return { busy: true, operation: "cherry-pick" };
18657
+ }
18658
+ if (await pathExists("REVERT_HEAD")) {
18659
+ return { busy: true, operation: "revert" };
18660
+ }
18661
+ return { busy: false };
18662
+ }
18663
+ async function createWorktreeTree(git, baseDir, head) {
18664
+ const { tempGit, tempIndexPath } = await createTempIndexGit(git, baseDir, "checkpoint-worktree");
18665
+ try {
18666
+ if (head) {
18667
+ await tempGit.raw(["read-tree", head]);
18668
+ } else {
18669
+ await tempGit.raw(["read-tree", "--empty"]);
18670
+ }
18671
+ await tempGit.raw(["add", "-A", "--", "."]);
18672
+ const treeHash = await tempGit.raw(["write-tree"]);
18673
+ return treeHash.trim();
18674
+ } finally {
18675
+ await fs10.rm(tempIndexPath, { force: true }).catch(() => {
18676
+ });
18677
+ }
18678
+ }
18679
+ async function createMetaTree(git, baseDir, indexTree, worktreeTree) {
18680
+ const { tempGit, tempIndexPath } = await createTempIndexGit(git, baseDir, "checkpoint-meta");
18681
+ try {
18682
+ await tempGit.raw(["read-tree", "--empty"]);
18683
+ await tempGit.raw([
18684
+ "update-index",
18685
+ "--add",
18686
+ "--cacheinfo",
18687
+ "040000",
18688
+ indexTree,
18689
+ "index"
18690
+ ]);
18691
+ await tempGit.raw([
18692
+ "update-index",
18693
+ "--add",
18694
+ "--cacheinfo",
18695
+ "040000",
18696
+ worktreeTree,
18697
+ "worktree"
18698
+ ]);
18699
+ const metaTree = await tempGit.raw(["write-tree"]);
18700
+ return metaTree.trim();
18701
+ } finally {
18702
+ await fs10.rm(tempIndexPath, { force: true }).catch(() => {
18703
+ });
18704
+ }
18705
+ }
18706
+ function formatCheckpointMessage(meta) {
18707
+ return [
18708
+ `POSTHOG-CODE-CHECKPOINT ${CHECKPOINT_VERSION}`,
18709
+ `head=${meta.head ?? "null"}`,
18710
+ `branch=${meta.branch ?? "null"}`,
18711
+ `index=${meta.indexTree}`,
18712
+ `worktree=${meta.worktreeTree}`,
18713
+ `timestamp=${meta.timestamp}`
18714
+ ].join("\n");
18715
+ }
18716
+ async function getGitCommonDir(git, baseDir) {
18717
+ const raw = await git.raw(["rev-parse", "--git-common-dir"]);
18718
+ const dir = raw.trim() || ".git";
18719
+ return path12.isAbsolute(dir) ? dir : path12.resolve(baseDir, dir);
18720
+ }
18721
+ async function createTempIndexGit(git, baseDir, label) {
18722
+ const tmpDir = path12.join(await getGitCommonDir(git, baseDir), "posthog-code-tmp");
18723
+ await fs10.mkdir(tmpDir, { recursive: true });
18724
+ const tempIndexPath = path12.join(tmpDir, `${label}-${Date.now()}-${(0, import_node_crypto2.randomUUID)()}`);
18725
+ const tempGit = createGitClient(baseDir).env({
18726
+ ...process.env,
18727
+ GIT_INDEX_FILE: tempIndexPath
18728
+ });
18729
+ return { tempGit, tempIndexPath };
18730
+ }
18731
+ async function refExists(git, refName) {
18732
+ try {
18733
+ await git.revparse(["--verify", refName]);
18734
+ return true;
18735
+ } catch {
18736
+ return false;
18737
+ }
18738
+ }
18739
+ async function deleteCheckpoint(git, checkpointId) {
18740
+ const refName = `${CHECKPOINT_REF_PREFIX}${checkpointId}`;
18741
+ const exists2 = await refExists(git, refName);
18742
+ if (!exists2) {
18743
+ throw new Error(`Checkpoint not found: ${checkpointId}`);
18744
+ }
18745
+ await git.raw(["update-ref", "-d", refName]);
18746
+ }
18747
+
18748
+ // ../git/dist/handoff.js
18749
+ var HANDOFF_HEAD_REF_PREFIX = "refs/posthog-code-handoff/head/";
18750
+ var CHECKPOINT_REF_PREFIX2 = "refs/posthog-code-checkpoint/";
18751
+ var GitHandoffTracker = class {
18752
+ repositoryPath;
18753
+ logger;
18754
+ constructor(config) {
18755
+ this.repositoryPath = config.repositoryPath;
18756
+ this.logger = config.logger;
18757
+ }
18758
+ async captureForHandoff(localGitState) {
18759
+ const captureSaga = new CaptureCheckpointSaga(this.logger);
18760
+ const result = await captureSaga.run({ baseDir: this.repositoryPath });
18761
+ if (!result.success) {
18762
+ throw new Error(`Failed to capture checkpoint at step '${result.failedStep}': ${result.error}`);
18763
+ }
18764
+ const checkpoint = result.data;
18765
+ const git = createGitClient(this.repositoryPath);
18766
+ const tempDir = await this.getTempDir(git);
18767
+ const checkpointRef = `${CHECKPOINT_REF_PREFIX2}${checkpoint.checkpointId}`;
18768
+ const shouldIncludeHead = !!checkpoint.head && checkpoint.head !== localGitState?.head;
18769
+ const headRef = shouldIncludeHead ? `${HANDOFF_HEAD_REF_PREFIX}${checkpoint.checkpointId}` : void 0;
18770
+ const packPrefix = import_node_path5.default.join(tempDir, checkpoint.checkpointId);
18771
+ try {
18772
+ const [headPack, indexFile, tracking] = await Promise.all([
18773
+ shouldIncludeHead && checkpoint.head ? this.captureHeadPack(packPrefix, checkpoint.head) : Promise.resolve(void 0),
18774
+ this.copyIndexFile(git, checkpoint.checkpointId),
18775
+ getTrackingMetadata(git, checkpoint.branch)
18776
+ ]);
18777
+ return {
18778
+ checkpoint: {
18779
+ checkpointId: checkpoint.checkpointId,
18780
+ commit: checkpoint.commit,
18781
+ checkpointRef,
18782
+ headRef,
18783
+ head: checkpoint.head,
18784
+ branch: checkpoint.branch,
18785
+ indexTree: checkpoint.indexTree,
18786
+ worktreeTree: checkpoint.worktreeTree,
18787
+ timestamp: checkpoint.timestamp,
18788
+ upstreamRemote: tracking.upstreamRemote,
18789
+ upstreamMergeRef: tracking.upstreamMergeRef,
18790
+ remoteUrl: tracking.remoteUrl
18791
+ },
18792
+ headPack,
18793
+ indexFile,
18794
+ totalBytes: (headPack?.rawBytes ?? 0) + indexFile.rawBytes
18795
+ };
18796
+ } finally {
18797
+ await deleteCheckpoint(git, checkpoint.checkpointId).catch(() => {
18798
+ });
18799
+ }
18800
+ }
18801
+ async applyFromHandoff(input) {
18802
+ const { checkpoint, headPackPath, indexPath, localGitState, onDivergedBranch } = input;
18803
+ const git = createGitClient(this.repositoryPath);
18804
+ if (headPackPath) {
18805
+ await this.unpackPackFile(headPackPath);
18806
+ }
18807
+ if (checkpoint.branch && checkpoint.head) {
18808
+ const branchStatus2 = await this.resolveBranchRestoreStatus(git, checkpoint.branch, checkpoint.head, localGitState);
18809
+ const tracking = this.getPreferredTracking(localGitState, checkpoint);
18810
+ if (branchStatus2.kind === "diverged" && !await onDivergedBranch?.(branchStatus2.divergence)) {
18811
+ throw new Error(`Handoff aborted: local branch '${checkpoint.branch}' has diverged`);
18812
+ }
18813
+ await this.checkoutBranchAtHead(git, checkpoint.branch, checkpoint.head);
18814
+ if (this.shouldRestoreTracking(branchStatus2, localGitState, tracking)) {
18815
+ await this.ensureRemoteForTracking(git, tracking);
18816
+ await this.configureUpstream(git, checkpoint.branch, tracking);
18817
+ }
18818
+ } else if (checkpoint.head) {
18819
+ await git.checkout(checkpoint.head);
18820
+ }
18821
+ if (indexPath) {
18822
+ await this.restoreIndexFile(git, indexPath);
18823
+ }
18824
+ const packBytes = headPackPath ? await this.getFileSize(headPackPath) : 0;
18825
+ const indexBytes = indexPath ? await this.getFileSize(indexPath) : 0;
18826
+ return {
18827
+ packBytes,
18828
+ indexBytes,
18829
+ totalBytes: packBytes + indexBytes
18830
+ };
18831
+ }
18832
+ async captureHeadPack(packPrefix, headCommit) {
18833
+ const hash = await this.runGitWithInput(["pack-objects", packPrefix, "--revs"], `${headCommit}
18834
+ `);
18835
+ const packPath = `${packPrefix}-${hash.trim()}.pack`;
18836
+ const rawBytes = await this.getFileSize(packPath);
18837
+ await (0, import_promises2.rm)(`${packPath}.idx`, { force: true }).catch(() => {
18838
+ });
18839
+ return { path: packPath, rawBytes };
18840
+ }
18841
+ async copyIndexFile(git, checkpointId) {
18842
+ const indexPath = await this.getGitPath(git, "index");
18843
+ const tempDir = await this.getTempDir(git);
18844
+ const copiedIndexPath = import_node_path5.default.join(tempDir, `${checkpointId}.index`);
18845
+ await (0, import_promises2.copyFile)(indexPath, copiedIndexPath);
18846
+ return {
18847
+ path: copiedIndexPath,
18848
+ rawBytes: await this.getFileSize(copiedIndexPath)
18849
+ };
18850
+ }
18851
+ async restoreIndexFile(git, indexPath) {
18852
+ const gitIndexPath = await this.getGitPath(git, "index");
18853
+ await (0, import_promises2.copyFile)(indexPath, gitIndexPath);
18854
+ }
18855
+ async unpackPackFile(packPath) {
18856
+ const content = await (0, import_promises2.readFile)(packPath);
18857
+ await this.runGitWithBuffer(["unpack-objects", "-r"], content);
18858
+ }
18859
+ getPreferredTracking(localGitState, checkpoint) {
18860
+ const state = localGitState;
18861
+ if (state && hasTrackingConfig(state)) {
18862
+ return {
18863
+ upstreamRemote: state.upstreamRemote ?? null,
18864
+ upstreamMergeRef: state.upstreamMergeRef ?? null,
18865
+ remoteUrl: state.upstreamRemote && state.upstreamRemote === checkpoint.upstreamRemote ? checkpoint.remoteUrl : null
18866
+ };
18867
+ }
18868
+ return {
18869
+ upstreamRemote: checkpoint.upstreamRemote,
18870
+ upstreamMergeRef: checkpoint.upstreamMergeRef,
18871
+ remoteUrl: checkpoint.remoteUrl
18872
+ };
18873
+ }
18874
+ shouldRestoreTracking(branchStatus2, localGitState, tracking) {
18875
+ return branchStatus2.kind === "missing" || !hasTrackingConfig(localGitState) && (tracking.upstreamRemote !== null || tracking.upstreamMergeRef !== null);
18876
+ }
18877
+ async ensureRemoteForTracking(git, tracking) {
18878
+ if (!tracking.upstreamRemote || !tracking.remoteUrl)
18879
+ return;
18880
+ const remotes = await git.getRemotes(true);
18881
+ const existing = remotes.find((remote) => remote.name === tracking.upstreamRemote);
18882
+ if (!existing) {
18883
+ await git.addRemote(tracking.upstreamRemote, tracking.remoteUrl);
18884
+ }
18885
+ }
18886
+ async configureUpstream(git, branch, tracking) {
18887
+ if (tracking.upstreamRemote) {
18888
+ await git.raw([
18889
+ "config",
18890
+ `branch.${branch}.remote`,
18891
+ tracking.upstreamRemote
18892
+ ]);
18893
+ }
18894
+ if (tracking.upstreamMergeRef) {
18895
+ await git.raw([
18896
+ "config",
18897
+ `branch.${branch}.merge`,
18898
+ tracking.upstreamMergeRef
18899
+ ]);
18900
+ }
18901
+ }
18902
+ async resolveBranchRestoreStatus(git, branch, cloudHead, localGitState) {
18903
+ const branchRef = `refs/heads/${branch}`;
18904
+ const branchExists = await this.refExists(git, branchRef);
18905
+ if (!branchExists) {
18906
+ return { kind: "missing" };
18907
+ }
18908
+ const currentBranchHead = (await git.revparse([branchRef])).trim();
18909
+ const candidateHeads = [
18910
+ currentBranchHead,
18911
+ ...localGitState?.branch === branch && localGitState.head ? [localGitState.head] : []
18912
+ ].filter((value, index, array) => array.indexOf(value) === index);
18913
+ if (candidateHeads.every((head) => head === cloudHead)) {
18914
+ return { kind: "match" };
18915
+ }
18916
+ const nonAncestorHead = await this.findNonAncestorHead(git, candidateHeads, cloudHead);
18917
+ if (!nonAncestorHead) {
18918
+ return { kind: "fast_forward" };
18919
+ }
18920
+ return {
18921
+ kind: "diverged",
18922
+ divergence: {
18923
+ branch,
18924
+ localHead: nonAncestorHead,
18925
+ cloudHead
18926
+ }
18927
+ };
18928
+ }
18929
+ async findNonAncestorHead(_git, heads, cloudHead) {
18930
+ for (const head of heads) {
18931
+ if (head === cloudHead) {
18932
+ continue;
18933
+ }
18934
+ if (!await this.isAncestor(head, cloudHead)) {
18935
+ return head;
18936
+ }
18937
+ }
18938
+ return null;
18939
+ }
18940
+ async checkoutBranchAtHead(git, branch, head) {
18941
+ const currentBranch = await getCurrentBranchName(git);
18942
+ if (currentBranch === branch) {
18943
+ await git.reset(["--hard", head]);
18944
+ return;
18945
+ }
18946
+ const branchRef = `refs/heads/${branch}`;
18947
+ if (await this.refExists(git, branchRef)) {
18948
+ await git.branch(["-f", branch, head]);
18949
+ await git.checkout(branch);
18950
+ return;
18951
+ }
18952
+ await git.checkout(["-b", branch, head]);
18953
+ }
18954
+ async refExists(git, ref) {
18955
+ try {
18956
+ await git.revparse(["--verify", ref]);
18957
+ return true;
18958
+ } catch {
18959
+ return false;
18960
+ }
18961
+ }
18962
+ async isAncestor(ancestor, descendant) {
18963
+ const exitCode = await this.runGitProcessAllowingFailure([
18964
+ "merge-base",
18965
+ "--is-ancestor",
18966
+ ancestor,
18967
+ descendant
18968
+ ]);
18969
+ return exitCode === 0;
18970
+ }
18971
+ async getTempDir(git) {
18972
+ const raw = await git.raw(["rev-parse", "--git-common-dir"]);
18973
+ const commonDir = raw.trim() || ".git";
18974
+ const resolved = import_node_path5.default.isAbsolute(commonDir) ? commonDir : import_node_path5.default.resolve(this.repositoryPath, commonDir);
18975
+ const tempDir = import_node_path5.default.join(resolved, "posthog-code-tmp");
18976
+ await (0, import_promises2.mkdir)(tempDir, { recursive: true });
18977
+ return tempDir;
18978
+ }
18979
+ async getGitPath(git, gitPath) {
18980
+ const raw = await git.raw(["rev-parse", "--git-path", gitPath]);
18981
+ const resolved = raw.trim();
18982
+ return import_node_path5.default.isAbsolute(resolved) ? resolved : import_node_path5.default.resolve(this.repositoryPath, resolved);
18983
+ }
18984
+ async getFileSize(filePath) {
18985
+ return (await (0, import_promises2.stat)(filePath)).size;
18986
+ }
18987
+ async runGitWithInput(args2, input) {
18988
+ const { stdout } = await this.runGitProcess(args2, input);
18989
+ return stdout;
18990
+ }
18991
+ async runGitWithBuffer(args2, input) {
18992
+ await this.runGitProcess(args2, input);
18993
+ }
18994
+ async runGitProcessAllowingFailure(args2) {
18995
+ return new Promise((resolve7, reject) => {
18996
+ const child = (0, import_node_child_process4.spawn)("git", args2, {
18997
+ cwd: this.repositoryPath,
18998
+ stdio: ["ignore", "ignore", "pipe"]
18999
+ });
19000
+ let stderr = "";
19001
+ child.stderr.on("data", (chunk) => {
19002
+ stderr += chunk.toString();
19003
+ });
19004
+ child.on("error", reject);
19005
+ child.on("close", (code) => {
19006
+ if (code === null) {
19007
+ reject(new Error(`git ${args2.join(" ")} exited unexpectedly`));
19008
+ return;
19009
+ }
19010
+ if (code > 1) {
19011
+ reject(new Error(stderr || `git ${args2.join(" ")} failed with code ${code}`));
19012
+ return;
19013
+ }
19014
+ resolve7(code);
19015
+ });
19016
+ });
19017
+ }
19018
+ runGitProcess(args2, input) {
19019
+ return new Promise((resolve7, reject) => {
19020
+ const child = (0, import_node_child_process4.spawn)("git", args2, {
19021
+ cwd: this.repositoryPath,
19022
+ stdio: "pipe"
19023
+ });
19024
+ let stdout = "";
19025
+ let stderr = "";
19026
+ child.stdout.on("data", (chunk) => {
19027
+ stdout += chunk.toString();
19028
+ });
19029
+ child.stderr.on("data", (chunk) => {
19030
+ stderr += chunk.toString();
19031
+ });
19032
+ child.on("error", reject);
19033
+ child.on("close", (code) => {
19034
+ if (code === 0) {
19035
+ resolve7({ stdout, stderr });
19036
+ return;
19037
+ }
19038
+ reject(new Error(stderr || `git ${args2.join(" ")} failed with code ${code}`));
19039
+ });
19040
+ child.stdin.end(input);
19041
+ });
19042
+ }
19043
+ };
19044
+ async function getCurrentBranchName(git) {
19045
+ try {
19046
+ const raw = await git.revparse(["--abbrev-ref", "HEAD"]);
19047
+ const branch = raw.trim();
19048
+ return branch === "HEAD" ? null : branch;
19049
+ } catch {
19050
+ return null;
19051
+ }
19052
+ }
19053
+ async function getTrackingMetadata(git, branch) {
19054
+ if (!branch) {
19055
+ return {
19056
+ upstreamRemote: null,
19057
+ upstreamMergeRef: null,
19058
+ remoteUrl: null
19059
+ };
19060
+ }
19061
+ const upstreamRemote = await getGitConfigValue(git, `branch.${branch}.remote`);
19062
+ const upstreamMergeRef = await getGitConfigValue(git, `branch.${branch}.merge`);
19063
+ const remoteUrl = upstreamRemote ? await getRemoteUrl(git, upstreamRemote) : null;
19064
+ return { upstreamRemote, upstreamMergeRef, remoteUrl };
19065
+ }
19066
+ async function getGitConfigValue(git, key) {
19067
+ try {
19068
+ const value = await git.raw(["config", "--get", key]);
19069
+ return value.trim() || null;
19070
+ } catch {
19071
+ return null;
19072
+ }
19073
+ }
19074
+ async function getRemoteUrl(git, remote) {
19075
+ try {
19076
+ const value = await git.remote(["get-url", remote]);
19077
+ return typeof value === "string" ? value.trim() || null : null;
19078
+ } catch {
19079
+ return null;
19080
+ }
19081
+ }
19082
+ function hasTrackingConfig(localGitState) {
19083
+ return !!(localGitState?.upstreamRemote || localGitState?.upstreamMergeRef);
19084
+ }
19085
+
19086
+ // src/handoff-checkpoint.ts
19087
+ var HandoffCheckpointTracker = class {
19088
+ repositoryPath;
19089
+ taskId;
19090
+ runId;
19091
+ apiClient;
19092
+ logger;
19093
+ constructor(config) {
19094
+ this.repositoryPath = config.repositoryPath;
19095
+ this.taskId = config.taskId;
19096
+ this.runId = config.runId;
19097
+ this.apiClient = config.apiClient;
19098
+ this.logger = config.logger || new Logger({ debug: false, prefix: "[HandoffCheckpointTracker]" });
19099
+ }
19100
+ async captureForHandoff(localGitState) {
19101
+ if (!this.apiClient) {
19102
+ throw new Error(
19103
+ "Cannot capture handoff checkpoint: API client not configured"
19104
+ );
19105
+ }
19106
+ const gitTracker = this.createGitTracker();
19107
+ const capture = await gitTracker.captureForHandoff(localGitState);
19108
+ try {
19109
+ const uploads = await this.uploadArtifacts([
19110
+ {
19111
+ key: "pack",
19112
+ filePath: capture.headPack?.path,
19113
+ name: `handoff/${capture.checkpoint.checkpointId}.pack`,
19114
+ contentType: "application/x-git-packed-objects"
19115
+ },
19116
+ {
19117
+ key: "index",
19118
+ filePath: capture.indexFile.path,
19119
+ name: `handoff/${capture.checkpoint.checkpointId}.index`,
19120
+ contentType: "application/octet-stream"
19121
+ }
19122
+ ]);
19123
+ this.logCaptureMetrics(capture.checkpoint, uploads);
19124
+ return {
19125
+ ...capture.checkpoint,
19126
+ artifactPath: uploads.pack?.storagePath,
19127
+ indexArtifactPath: uploads.index?.storagePath
19128
+ };
19129
+ } finally {
19130
+ await this.removeIfPresent(capture.headPack?.path);
19131
+ await this.removeIfPresent(capture.indexFile.path);
19132
+ }
19133
+ }
19134
+ async applyFromHandoff(checkpoint, options) {
19135
+ if (!this.apiClient) {
19136
+ throw new Error(
19137
+ "Cannot apply handoff checkpoint: API client not configured"
19138
+ );
19139
+ }
19140
+ const gitTracker = this.createGitTracker();
19141
+ const tmpDir = (0, import_node_path6.join)(this.repositoryPath, ".posthog", "tmp");
19142
+ await (0, import_promises3.mkdir)(tmpDir, { recursive: true });
19143
+ const packPath = (0, import_node_path6.join)(tmpDir, `${checkpoint.checkpointId}.pack`);
19144
+ const indexPath = (0, import_node_path6.join)(tmpDir, `${checkpoint.checkpointId}.index`);
19145
+ try {
19146
+ const downloads = await this.downloadArtifacts([
19147
+ {
19148
+ key: "pack",
19149
+ storagePath: checkpoint.artifactPath,
19150
+ filePath: packPath,
19151
+ label: "handoff pack"
19152
+ },
19153
+ {
19154
+ key: "index",
19155
+ storagePath: checkpoint.indexArtifactPath,
19156
+ filePath: indexPath,
19157
+ label: "handoff index"
19158
+ }
19159
+ ]);
19160
+ const applyResult = await gitTracker.applyFromHandoff({
19161
+ checkpoint: this.toGitCheckpoint(checkpoint),
19162
+ headPackPath: downloads.pack?.filePath,
19163
+ indexPath: downloads.index?.filePath,
19164
+ localGitState: options?.localGitState,
19165
+ onDivergedBranch: options?.onDivergedBranch
19166
+ });
19167
+ this.logApplyMetrics(checkpoint, downloads, applyResult.totalBytes);
19168
+ } finally {
19169
+ await this.removeIfPresent(packPath);
19170
+ await this.removeIfPresent(indexPath);
19171
+ }
19172
+ }
19173
+ toGitCheckpoint(checkpoint) {
19174
+ return {
19175
+ checkpointId: checkpoint.checkpointId,
19176
+ commit: checkpoint.commit,
19177
+ checkpointRef: checkpoint.checkpointRef,
19178
+ headRef: checkpoint.headRef,
19179
+ head: checkpoint.head,
19180
+ branch: checkpoint.branch,
19181
+ indexTree: checkpoint.indexTree,
19182
+ worktreeTree: checkpoint.worktreeTree,
19183
+ timestamp: checkpoint.timestamp,
19184
+ upstreamRemote: checkpoint.upstreamRemote ?? null,
19185
+ upstreamMergeRef: checkpoint.upstreamMergeRef ?? null,
19186
+ remoteUrl: checkpoint.remoteUrl ?? null
19187
+ };
19188
+ }
19189
+ async uploadArtifactFile(filePath, name2, contentType) {
19190
+ if (!this.apiClient) {
19191
+ return { rawBytes: 0, wireBytes: 0 };
19192
+ }
19193
+ const content = await (0, import_promises3.readFile)(filePath);
19194
+ const base64Content = content.toString("base64");
19195
+ const artifacts = await this.apiClient.uploadTaskArtifacts(
19196
+ this.taskId,
19197
+ this.runId,
19198
+ [
19199
+ {
19200
+ name: name2,
19201
+ type: "artifact",
19202
+ content: base64Content,
19203
+ content_type: contentType
19204
+ }
19205
+ ]
19206
+ );
19207
+ return {
19208
+ storagePath: artifacts.at(-1)?.storage_path,
19209
+ rawBytes: content.byteLength,
19210
+ wireBytes: Buffer.byteLength(base64Content, "utf-8")
19211
+ };
19212
+ }
19213
+ async uploadArtifacts(specs) {
19214
+ const uploads = await Promise.all(
19215
+ specs.map(async (spec) => {
19216
+ if (!spec.filePath) {
19217
+ return [spec.key, void 0];
19218
+ }
19219
+ return [
19220
+ spec.key,
19221
+ await this.uploadArtifactFile(
19222
+ spec.filePath,
19223
+ spec.name,
19224
+ spec.contentType
19225
+ )
19226
+ ];
19227
+ })
19228
+ );
19229
+ return Object.fromEntries(uploads);
19230
+ }
19231
+ async downloadArtifactToFile(artifactPath, filePath, label) {
19232
+ if (!this.apiClient) {
19233
+ throw new Error(`Cannot download ${label}: API client not configured`);
19234
+ }
19235
+ const arrayBuffer = await this.apiClient.downloadArtifact(
19236
+ this.taskId,
19237
+ this.runId,
19238
+ artifactPath
19239
+ );
19240
+ if (!arrayBuffer) {
19241
+ throw new Error(`Failed to download ${label}`);
19242
+ }
19243
+ const base64Content = Buffer.from(arrayBuffer).toString("utf-8");
19244
+ const binaryContent = Buffer.from(base64Content, "base64");
19245
+ await (0, import_promises3.writeFile)(filePath, binaryContent);
19246
+ return {
19247
+ filePath,
19248
+ rawBytes: binaryContent.byteLength,
19249
+ wireBytes: arrayBuffer.byteLength
19250
+ };
19251
+ }
19252
+ async downloadArtifacts(specs) {
19253
+ const downloads = await Promise.all(
19254
+ specs.map(async (spec) => {
19255
+ if (!spec.storagePath) {
19256
+ return [spec.key, void 0];
19257
+ }
19258
+ return [
19259
+ spec.key,
19260
+ await this.downloadArtifactToFile(
19261
+ spec.storagePath,
19262
+ spec.filePath,
19263
+ spec.label
19264
+ )
19265
+ ];
19266
+ })
19267
+ );
19268
+ return Object.fromEntries(downloads);
19269
+ }
19270
+ createGitTracker() {
19271
+ return new GitHandoffTracker({
19272
+ repositoryPath: this.repositoryPath,
19273
+ logger: this.logger
19274
+ });
19275
+ }
19276
+ logCaptureMetrics(checkpoint, uploads) {
19277
+ this.logger.info("Captured handoff checkpoint", {
19278
+ checkpointId: checkpoint.checkpointId,
19279
+ branch: checkpoint.branch,
19280
+ head: checkpoint.head,
19281
+ artifactPath: uploads.pack?.storagePath,
19282
+ indexArtifactPath: uploads.index?.storagePath,
19283
+ ...this.buildMetricPayload(uploads)
19284
+ });
19285
+ }
19286
+ logApplyMetrics(checkpoint, downloads, totalBytes) {
19287
+ this.logger.info("Applied handoff checkpoint", {
19288
+ checkpointId: checkpoint.checkpointId,
19289
+ commit: checkpoint.commit,
19290
+ branch: checkpoint.branch,
19291
+ head: checkpoint.head,
19292
+ packBytes: downloads.pack?.rawBytes ?? 0,
19293
+ packWireBytes: downloads.pack?.wireBytes ?? 0,
19294
+ indexBytes: downloads.index?.rawBytes ?? 0,
19295
+ indexWireBytes: downloads.index?.wireBytes ?? 0,
19296
+ totalBytes,
19297
+ totalWireBytes: this.sumWireBytes(downloads.pack, downloads.index)
19298
+ });
19299
+ }
19300
+ buildMetricPayload(metrics) {
19301
+ return {
19302
+ packBytes: metrics.pack?.rawBytes ?? 0,
19303
+ packWireBytes: metrics.pack?.wireBytes ?? 0,
19304
+ indexBytes: metrics.index?.rawBytes ?? 0,
19305
+ indexWireBytes: metrics.index?.wireBytes ?? 0,
19306
+ totalBytes: this.sumRawBytes(metrics.pack, metrics.index),
19307
+ totalWireBytes: this.sumWireBytes(metrics.pack, metrics.index)
19308
+ };
19309
+ }
19310
+ sumRawBytes(...artifacts) {
19311
+ return artifacts.reduce(
19312
+ (total, artifact) => total + (artifact?.rawBytes ?? 0),
19313
+ 0
19314
+ );
19315
+ }
19316
+ sumWireBytes(...artifacts) {
19317
+ return artifacts.reduce(
19318
+ (total, artifact) => total + (artifact?.wireBytes ?? 0),
19319
+ 0
19320
+ );
19321
+ }
19322
+ async removeIfPresent(filePath) {
19323
+ if (!filePath) {
19324
+ return;
19325
+ }
19326
+ await (0, import_promises3.rm)(filePath, { force: true }).catch(() => {
19327
+ });
19328
+ }
19329
+ };
19330
+
19331
+ // src/utils/gateway.ts
19332
+ function getGatewayBaseUrl(posthogHost) {
19333
+ const url = new URL(posthogHost);
19334
+ const hostname = url.hostname;
19335
+ if (hostname === "localhost" || hostname === "127.0.0.1") {
19336
+ return `${url.protocol}//localhost:3308`;
19337
+ }
19338
+ if (hostname === "host.docker.internal") {
19339
+ return `${url.protocol}//host.docker.internal:3308`;
19340
+ }
19341
+ const region = hostname.match(/^(us|eu)\.posthog\.com$/)?.[1] ?? "us";
19342
+ return `https://gateway.${region}.posthog.com`;
19343
+ }
19344
+ function getLlmGatewayUrl(posthogHost, product = "posthog_code") {
19345
+ return `${getGatewayBaseUrl(posthogHost)}/${product}`;
19346
+ }
19347
+
19348
+ // src/posthog-api.ts
19349
+ var DEFAULT_USER_AGENT = `posthog/agent.hog.dev; version: ${package_default.version}`;
19350
+ var PostHogAPIClient = class {
19351
+ config;
19352
+ constructor(config) {
19353
+ this.config = config;
19354
+ }
19355
+ get baseUrl() {
19356
+ const host = this.config.apiUrl.endsWith("/") ? this.config.apiUrl.slice(0, -1) : this.config.apiUrl;
19357
+ return host;
19358
+ }
19359
+ isAuthFailure(status) {
19360
+ return status === 401 || status === 403;
19361
+ }
19362
+ async resolveApiKey(forceRefresh = false) {
19363
+ if (forceRefresh && this.config.refreshApiKey) {
19364
+ return this.config.refreshApiKey();
19365
+ }
19366
+ return this.config.getApiKey();
19367
+ }
19368
+ async buildHeaders(options, forceRefresh = false) {
19369
+ const headers = new Headers(options.headers);
19370
+ headers.set(
19371
+ "Authorization",
19372
+ `Bearer ${await this.resolveApiKey(forceRefresh)}`
19373
+ );
19374
+ headers.set("Content-Type", "application/json");
19375
+ headers.set("User-Agent", this.config.userAgent ?? DEFAULT_USER_AGENT);
19376
+ return headers;
19377
+ }
19378
+ async performRequest(endpoint, options, forceRefresh = false) {
19379
+ const url = `${this.baseUrl}${endpoint}`;
19380
+ return fetch(url, {
19381
+ ...options,
19382
+ headers: await this.buildHeaders(options, forceRefresh)
19383
+ });
19384
+ }
19385
+ async performRequestWithRetry(endpoint, options = {}) {
19386
+ let response = await this.performRequest(endpoint, options);
19387
+ if (!response.ok && this.isAuthFailure(response.status)) {
19388
+ response = await this.performRequest(endpoint, options, true);
19389
+ }
19390
+ return response;
19391
+ }
19392
+ async apiRequest(endpoint, options = {}) {
19393
+ const response = await this.performRequestWithRetry(endpoint, options);
19394
+ if (!response.ok) {
19395
+ let errorMessage;
19396
+ try {
19397
+ const errorResponse = await response.json();
19398
+ errorMessage = `Failed request: [${response.status}] ${JSON.stringify(errorResponse)}`;
19399
+ } catch {
19400
+ errorMessage = `Failed request: [${response.status}] ${response.statusText}`;
19401
+ }
19402
+ throw new Error(errorMessage);
19403
+ }
19404
+ return response.json();
19405
+ }
19406
+ getTeamId() {
19407
+ return this.config.projectId;
19408
+ }
19409
+ async getApiKey(forceRefresh = false) {
19410
+ return this.resolveApiKey(forceRefresh);
19411
+ }
19412
+ getLlmGatewayUrl() {
19413
+ return getLlmGatewayUrl(this.baseUrl);
19414
+ }
19415
+ async getTask(taskId) {
19416
+ const teamId = this.getTeamId();
19417
+ return this.apiRequest(`/api/projects/${teamId}/tasks/${taskId}/`);
19418
+ }
19419
+ async getTaskRun(taskId, runId) {
19420
+ const teamId = this.getTeamId();
19421
+ return this.apiRequest(
19422
+ `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/`
19423
+ );
19424
+ }
19425
+ async updateTaskRun(taskId, runId, payload) {
19426
+ const teamId = this.getTeamId();
19427
+ return this.apiRequest(
19428
+ `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/`,
19429
+ {
19430
+ method: "PATCH",
19431
+ body: JSON.stringify(payload)
19432
+ }
19433
+ );
19434
+ }
19435
+ async setTaskRunOutput(taskId, runId, output) {
19436
+ return this.apiRequest(
19437
+ `/api/projects/${this.getTeamId()}/tasks/${taskId}/runs/${runId}/set_output/`,
19438
+ {
19439
+ method: "PATCH",
19440
+ body: JSON.stringify(output)
19441
+ }
19442
+ );
19443
+ }
19444
+ async appendTaskRunLog(taskId, runId, entries) {
19445
+ const teamId = this.getTeamId();
19446
+ return this.apiRequest(
19447
+ `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/append_log/`,
19448
+ {
19449
+ method: "POST",
19450
+ body: JSON.stringify({ entries })
19451
+ }
19452
+ );
19453
+ }
19454
+ async relayMessage(taskId, runId, text2) {
19455
+ const teamId = this.getTeamId();
19456
+ await this.apiRequest(
19457
+ `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/relay_message/`,
19458
+ {
19459
+ method: "POST",
19460
+ body: JSON.stringify({ text: text2 })
19461
+ }
19462
+ );
19463
+ }
19464
+ async uploadTaskArtifacts(taskId, runId, artifacts) {
19465
+ if (!artifacts.length) {
19466
+ return [];
19467
+ }
19468
+ const teamId = this.getTeamId();
19469
+ const response = await this.apiRequest(
19470
+ `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/artifacts/`,
19471
+ {
19472
+ method: "POST",
19473
+ body: JSON.stringify({ artifacts })
19474
+ }
19475
+ );
19476
+ const manifest = response.artifacts ?? [];
19477
+ return manifest.slice(-artifacts.length);
19478
+ }
19479
+ /**
19480
+ * Download artifact content by storage path
19481
+ * Streams the file through the PostHog backend so the sandbox does not need
19482
+ * direct access to object storage.
19483
+ */
19484
+ async downloadArtifact(taskId, runId, storagePath) {
19485
+ const teamId = this.getTeamId();
19486
+ try {
19487
+ const response = await this.performRequestWithRetry(
19488
+ `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/artifacts/download/`,
19489
+ {
19490
+ method: "POST",
19491
+ body: JSON.stringify({ storage_path: storagePath })
19492
+ }
19493
+ );
19494
+ if (!response.ok) {
19495
+ throw new Error(`Failed to download artifact: ${response.status}`);
19496
+ }
19497
+ return response.arrayBuffer();
19498
+ } catch {
19499
+ return null;
19500
+ }
19501
+ }
19502
+ /**
19503
+ * Fetch logs for a task run via the logs API endpoint
19504
+ * @param taskRun - The task run to fetch logs for
19505
+ * @returns Array of stored entries, or empty array if no logs available
19506
+ */
19507
+ async fetchTaskRunLogs(taskRun) {
19508
+ const teamId = this.getTeamId();
19509
+ const endpoint = `/api/projects/${teamId}/tasks/${taskRun.task}/runs/${taskRun.id}/logs`;
19510
+ try {
19511
+ const response = await this.performRequestWithRetry(endpoint);
19512
+ if (!response.ok) {
19513
+ if (response.status === 404) {
19514
+ return [];
19515
+ }
19516
+ throw new Error(
19517
+ `Failed to fetch logs: ${response.status} ${response.statusText}`
19518
+ );
19519
+ }
19520
+ const content = await response.text();
19521
+ if (!content.trim()) {
19522
+ return [];
19523
+ }
19524
+ return content.trim().split("\n").map((line) => JSON.parse(line));
19525
+ } catch (error) {
19526
+ throw new Error(
19527
+ `Failed to fetch task run logs: ${error instanceof Error ? error.message : String(error)}`
19528
+ );
19529
+ }
19530
+ }
19531
+ };
19532
+
19533
+ // src/adapters/claude/session/jsonl-hydration.ts
19534
+ var import_node_crypto3 = require("crypto");
19535
+ var fs11 = __toESM(require("fs/promises"), 1);
19536
+ var os6 = __toESM(require("os"), 1);
19537
+ var path14 = __toESM(require("path"), 1);
19538
+ var CHARS_PER_TOKEN = 4;
19539
+ var DEFAULT_MAX_TOKENS = 15e4;
19540
+ function estimateTurnTokens(turn) {
19541
+ let chars = 0;
19542
+ for (const block of turn.content) {
19543
+ if ("text" in block && typeof block.text === "string") {
19544
+ chars += block.text.length;
19545
+ }
19546
+ }
19547
+ if (turn.toolCalls) {
19548
+ for (const tc of turn.toolCalls) {
19549
+ chars += JSON.stringify(tc.input ?? "").length;
19550
+ if (tc.result !== void 0) {
19551
+ chars += typeof tc.result === "string" ? tc.result.length : JSON.stringify(tc.result).length;
19552
+ }
19553
+ }
19554
+ }
19555
+ return Math.ceil(chars / CHARS_PER_TOKEN);
19556
+ }
19557
+ function selectRecentTurns(turns, maxTokens = DEFAULT_MAX_TOKENS) {
19558
+ let budget = maxTokens;
19559
+ let startIndex = turns.length;
19560
+ for (let i2 = turns.length - 1; i2 >= 0; i2--) {
19561
+ const cost = estimateTurnTokens(turns[i2]);
19562
+ if (cost > budget) break;
19563
+ budget -= cost;
19564
+ startIndex = i2;
19565
+ }
19566
+ while (startIndex < turns.length && turns[startIndex].role !== "user") {
19567
+ startIndex++;
19568
+ }
19569
+ return turns.slice(startIndex);
19570
+ }
19571
+
19572
+ // src/sagas/apply-snapshot-saga.ts
19573
+ var import_promises4 = require("fs/promises");
19574
+ var import_node_path7 = require("path");
19575
+
19576
+ // ../git/dist/sagas/tree.js
19577
+ var import_node_fs3 = require("fs");
19578
+ var fs12 = __toESM(require("fs/promises"), 1);
19579
+ var path15 = __toESM(require("path"), 1);
19580
+ var tar = __toESM(require("tar"), 1);
19581
+ var CaptureTreeSaga = class extends GitSaga {
18738
19582
  sagaName = "CaptureTreeSaga";
18739
19583
  tempIndexPath = null;
18740
19584
  async executeGitOperations(input) {
18741
19585
  const { baseDir, lastTreeHash, archivePath, signal } = input;
18742
- const tmpDir = path13.join(baseDir, ".git", "posthog-code-tmp");
19586
+ const tmpDir = path15.join(baseDir, ".git", "posthog-code-tmp");
18743
19587
  await this.step({
18744
19588
  name: "create_tmp_dir",
18745
- execute: () => fs11.mkdir(tmpDir, { recursive: true }),
19589
+ execute: () => fs12.mkdir(tmpDir, { recursive: true }),
18746
19590
  rollback: async () => {
18747
19591
  }
18748
19592
  });
18749
- this.tempIndexPath = path13.join(tmpDir, `index-${Date.now()}`);
19593
+ this.tempIndexPath = path15.join(tmpDir, `index-${Date.now()}`);
18750
19594
  const tempIndexGit = this.git.env({
18751
19595
  ...process.env,
18752
19596
  GIT_INDEX_FILE: this.tempIndexPath
@@ -18756,7 +19600,7 @@ var CaptureTreeSaga = class extends GitSaga {
18756
19600
  execute: () => tempIndexGit.raw(["read-tree", "HEAD"]),
18757
19601
  rollback: async () => {
18758
19602
  if (this.tempIndexPath) {
18759
- await fs11.rm(this.tempIndexPath, { force: true }).catch(() => {
19603
+ await fs12.rm(this.tempIndexPath, { force: true }).catch(() => {
18760
19604
  });
18761
19605
  }
18762
19606
  }
@@ -18765,7 +19609,7 @@ var CaptureTreeSaga = class extends GitSaga {
18765
19609
  const treeHash = await this.readOnlyStep("write_tree", () => tempIndexGit.raw(["write-tree"]));
18766
19610
  if (lastTreeHash && treeHash === lastTreeHash) {
18767
19611
  this.log.debug("No changes since last capture", { treeHash });
18768
- await fs11.rm(this.tempIndexPath, { force: true }).catch(() => {
19612
+ await fs12.rm(this.tempIndexPath, { force: true }).catch(() => {
18769
19613
  });
18770
19614
  return { snapshot: null, changed: false };
18771
19615
  }
@@ -18777,7 +19621,7 @@ var CaptureTreeSaga = class extends GitSaga {
18777
19621
  }
18778
19622
  });
18779
19623
  const changes = await this.readOnlyStep("get_changes", () => this.getChanges(this.git, baseCommit, treeHash));
18780
- await fs11.rm(this.tempIndexPath, { force: true }).catch(() => {
19624
+ await fs12.rm(this.tempIndexPath, { force: true }).catch(() => {
18781
19625
  });
18782
19626
  const snapshot = {
18783
19627
  treeHash,
@@ -18801,15 +19645,15 @@ var CaptureTreeSaga = class extends GitSaga {
18801
19645
  if (filesToArchive.length === 0) {
18802
19646
  return void 0;
18803
19647
  }
18804
- const existingFiles = filesToArchive.filter((f) => (0, import_node_fs3.existsSync)(path13.join(baseDir, f)));
19648
+ const existingFiles = filesToArchive.filter((f) => (0, import_node_fs3.existsSync)(path15.join(baseDir, f)));
18805
19649
  if (existingFiles.length === 0) {
18806
19650
  return void 0;
18807
19651
  }
18808
19652
  await this.step({
18809
19653
  name: "create_archive",
18810
19654
  execute: async () => {
18811
- const archiveDir = path13.dirname(archivePath);
18812
- await fs11.mkdir(archiveDir, { recursive: true });
19655
+ const archiveDir = path15.dirname(archivePath);
19656
+ await fs12.mkdir(archiveDir, { recursive: true });
18813
19657
  await tar.create({
18814
19658
  gzip: true,
18815
19659
  file: archivePath,
@@ -18817,7 +19661,7 @@ var CaptureTreeSaga = class extends GitSaga {
18817
19661
  }, existingFiles);
18818
19662
  },
18819
19663
  rollback: async () => {
18820
- await fs11.rm(archivePath, { force: true }).catch(() => {
19664
+ await fs12.rm(archivePath, { force: true }).catch(() => {
18821
19665
  });
18822
19666
  }
18823
19667
  });
@@ -18917,9 +19761,9 @@ var ApplyTreeSaga = class extends GitSaga {
18917
19761
  const filesToExtract = changes.filter((c) => c.status !== "D").map((c) => c.path);
18918
19762
  await this.readOnlyStep("backup_existing_files", async () => {
18919
19763
  for (const filePath of filesToExtract) {
18920
- const fullPath = path13.join(baseDir, filePath);
19764
+ const fullPath = path15.join(baseDir, filePath);
18921
19765
  try {
18922
- const content = await fs11.readFile(fullPath);
19766
+ const content = await fs12.readFile(fullPath);
18923
19767
  this.fileBackups.set(filePath, content);
18924
19768
  } catch {
18925
19769
  }
@@ -18936,16 +19780,16 @@ var ApplyTreeSaga = class extends GitSaga {
18936
19780
  },
18937
19781
  rollback: async () => {
18938
19782
  for (const filePath of this.extractedFiles) {
18939
- const fullPath = path13.join(baseDir, filePath);
19783
+ const fullPath = path15.join(baseDir, filePath);
18940
19784
  const backup = this.fileBackups.get(filePath);
18941
19785
  if (backup) {
18942
- const dir = path13.dirname(fullPath);
18943
- await fs11.mkdir(dir, { recursive: true }).catch(() => {
19786
+ const dir = path15.dirname(fullPath);
19787
+ await fs12.mkdir(dir, { recursive: true }).catch(() => {
18944
19788
  });
18945
- await fs11.writeFile(fullPath, backup).catch(() => {
19789
+ await fs12.writeFile(fullPath, backup).catch(() => {
18946
19790
  });
18947
19791
  } else {
18948
- await fs11.rm(fullPath, { force: true }).catch(() => {
19792
+ await fs12.rm(fullPath, { force: true }).catch(() => {
18949
19793
  });
18950
19794
  }
18951
19795
  }
@@ -18953,10 +19797,10 @@ var ApplyTreeSaga = class extends GitSaga {
18953
19797
  });
18954
19798
  }
18955
19799
  for (const change of changes.filter((c) => c.status === "D")) {
18956
- const fullPath = path13.join(baseDir, change.path);
19800
+ const fullPath = path15.join(baseDir, change.path);
18957
19801
  const backupContent = await this.readOnlyStep(`backup_${change.path}`, async () => {
18958
19802
  try {
18959
- return await fs11.readFile(fullPath);
19803
+ return await fs12.readFile(fullPath);
18960
19804
  } catch {
18961
19805
  return null;
18962
19806
  }
@@ -18964,15 +19808,15 @@ var ApplyTreeSaga = class extends GitSaga {
18964
19808
  await this.step({
18965
19809
  name: `delete_${change.path}`,
18966
19810
  execute: async () => {
18967
- await fs11.rm(fullPath, { force: true });
19811
+ await fs12.rm(fullPath, { force: true });
18968
19812
  this.log.debug(`Deleted file: ${change.path}`);
18969
19813
  },
18970
19814
  rollback: async () => {
18971
19815
  if (backupContent) {
18972
- const dir = path13.dirname(fullPath);
18973
- await fs11.mkdir(dir, { recursive: true }).catch(() => {
19816
+ const dir = path15.dirname(fullPath);
19817
+ await fs12.mkdir(dir, { recursive: true }).catch(() => {
18974
19818
  });
18975
- await fs11.writeFile(fullPath, backupContent).catch(() => {
19819
+ await fs12.writeFile(fullPath, backupContent).catch(() => {
18976
19820
  });
18977
19821
  }
18978
19822
  }
@@ -18995,18 +19839,18 @@ var ApplySnapshotSaga = class extends Saga {
18995
19839
  archivePath = null;
18996
19840
  async execute(input) {
18997
19841
  const { snapshot, repositoryPath, apiClient, taskId, runId } = input;
18998
- const tmpDir = (0, import_node_path5.join)(repositoryPath, ".posthog", "tmp");
19842
+ const tmpDir = (0, import_node_path7.join)(repositoryPath, ".posthog", "tmp");
18999
19843
  if (!snapshot.archiveUrl) {
19000
19844
  throw new Error("Cannot apply snapshot: no archive URL");
19001
19845
  }
19002
19846
  const archiveUrl = snapshot.archiveUrl;
19003
19847
  await this.step({
19004
19848
  name: "create_tmp_dir",
19005
- execute: () => (0, import_promises2.mkdir)(tmpDir, { recursive: true }),
19849
+ execute: () => (0, import_promises4.mkdir)(tmpDir, { recursive: true }),
19006
19850
  rollback: async () => {
19007
19851
  }
19008
19852
  });
19009
- const archivePath = (0, import_node_path5.join)(tmpDir, `${snapshot.treeHash}.tar.gz`);
19853
+ const archivePath = (0, import_node_path7.join)(tmpDir, `${snapshot.treeHash}.tar.gz`);
19010
19854
  this.archivePath = archivePath;
19011
19855
  await this.step({
19012
19856
  name: "download_archive",
@@ -19021,11 +19865,18 @@ var ApplySnapshotSaga = class extends Saga {
19021
19865
  }
19022
19866
  const base64Content = Buffer.from(arrayBuffer).toString("utf-8");
19023
19867
  const binaryContent = Buffer.from(base64Content, "base64");
19024
- await (0, import_promises2.writeFile)(archivePath, binaryContent);
19868
+ await (0, import_promises4.writeFile)(archivePath, binaryContent);
19869
+ this.log.info("Tree archive downloaded", {
19870
+ treeHash: snapshot.treeHash,
19871
+ snapshotBytes: binaryContent.byteLength,
19872
+ snapshotWireBytes: arrayBuffer.byteLength,
19873
+ totalBytes: binaryContent.byteLength,
19874
+ totalWireBytes: arrayBuffer.byteLength
19875
+ });
19025
19876
  },
19026
19877
  rollback: async () => {
19027
19878
  if (this.archivePath) {
19028
- await (0, import_promises2.rm)(this.archivePath, { force: true }).catch(() => {
19879
+ await (0, import_promises4.rm)(this.archivePath, { force: true }).catch(() => {
19029
19880
  });
19030
19881
  }
19031
19882
  }
@@ -19041,7 +19892,7 @@ var ApplySnapshotSaga = class extends Saga {
19041
19892
  if (!applyResult.success) {
19042
19893
  throw new Error(`Failed to apply tree: ${applyResult.error}`);
19043
19894
  }
19044
- await (0, import_promises2.rm)(this.archivePath, { force: true }).catch(() => {
19895
+ await (0, import_promises4.rm)(this.archivePath, { force: true }).catch(() => {
19045
19896
  });
19046
19897
  this.log.info("Tree snapshot applied", {
19047
19898
  treeHash: snapshot.treeHash,
@@ -19054,8 +19905,8 @@ var ApplySnapshotSaga = class extends Saga {
19054
19905
 
19055
19906
  // src/sagas/capture-tree-saga.ts
19056
19907
  var import_node_fs4 = require("fs");
19057
- var import_promises3 = require("fs/promises");
19058
- var import_node_path6 = require("path");
19908
+ var import_promises5 = require("fs/promises");
19909
+ var import_node_path8 = require("path");
19059
19910
  var CaptureTreeSaga2 = class extends Saga {
19060
19911
  sagaName = "CaptureTreeSaga";
19061
19912
  async execute(input) {
@@ -19067,14 +19918,14 @@ var CaptureTreeSaga2 = class extends Saga {
19067
19918
  taskId,
19068
19919
  runId
19069
19920
  } = input;
19070
- const tmpDir = (0, import_node_path6.join)(repositoryPath, ".posthog", "tmp");
19071
- if ((0, import_node_fs4.existsSync)((0, import_node_path6.join)(repositoryPath, ".gitmodules"))) {
19921
+ const tmpDir = (0, import_node_path8.join)(repositoryPath, ".posthog", "tmp");
19922
+ if ((0, import_node_fs4.existsSync)((0, import_node_path8.join)(repositoryPath, ".gitmodules"))) {
19072
19923
  this.log.warn(
19073
19924
  "Repository has submodules - snapshot may not capture submodule state"
19074
19925
  );
19075
19926
  }
19076
19927
  const shouldArchive = !!apiClient;
19077
- const archivePath = shouldArchive ? (0, import_node_path6.join)(tmpDir, `tree-${Date.now()}.tar.gz`) : void 0;
19928
+ const archivePath = shouldArchive ? (0, import_node_path8.join)(tmpDir, `tree-${Date.now()}.tar.gz`) : void 0;
19078
19929
  const gitCaptureSaga = new CaptureTreeSaga(this.log);
19079
19930
  const captureResult = await gitCaptureSaga.run({
19080
19931
  baseDir: repositoryPath,
@@ -19104,7 +19955,7 @@ var CaptureTreeSaga2 = class extends Saga {
19104
19955
  runId
19105
19956
  );
19106
19957
  } finally {
19107
- await (0, import_promises3.rm)(createdArchivePath, { force: true }).catch(() => {
19958
+ await (0, import_promises5.rm)(createdArchivePath, { force: true }).catch(() => {
19108
19959
  });
19109
19960
  }
19110
19961
  }
@@ -19128,8 +19979,10 @@ var CaptureTreeSaga2 = class extends Saga {
19128
19979
  const archiveUrl = await this.step({
19129
19980
  name: "upload_archive",
19130
19981
  execute: async () => {
19131
- const archiveContent = await (0, import_promises3.readFile)(archivePath);
19982
+ const archiveContent = await (0, import_promises5.readFile)(archivePath);
19132
19983
  const base64Content = archiveContent.toString("base64");
19984
+ const snapshotBytes = archiveContent.byteLength;
19985
+ const snapshotWireBytes = Buffer.byteLength(base64Content, "utf-8");
19133
19986
  const artifacts = await apiClient.uploadTaskArtifacts(taskId, runId, [
19134
19987
  {
19135
19988
  name: `trees/${treeHash}.tar.gz`,
@@ -19138,17 +19991,22 @@ var CaptureTreeSaga2 = class extends Saga {
19138
19991
  content_type: "application/gzip"
19139
19992
  }
19140
19993
  ]);
19141
- if (artifacts.length > 0 && artifacts[0].storage_path) {
19994
+ const uploadedArtifact = artifacts[0];
19995
+ if (uploadedArtifact?.storage_path) {
19142
19996
  this.log.info("Tree archive uploaded", {
19143
- storagePath: artifacts[0].storage_path,
19144
- treeHash
19997
+ storagePath: uploadedArtifact.storage_path,
19998
+ treeHash,
19999
+ snapshotBytes,
20000
+ snapshotWireBytes,
20001
+ totalBytes: snapshotBytes,
20002
+ totalWireBytes: snapshotWireBytes
19145
20003
  });
19146
- return artifacts[0].storage_path;
20004
+ return uploadedArtifact.storage_path;
19147
20005
  }
19148
20006
  return void 0;
19149
20007
  },
19150
20008
  rollback: async () => {
19151
- await (0, import_promises3.rm)(archivePath, { force: true }).catch(() => {
20009
+ await (0, import_promises5.rm)(archivePath, { force: true }).catch(() => {
19152
20010
  });
19153
20011
  }
19154
20012
  });
@@ -19276,6 +20134,10 @@ var ResumeSaga = class extends Saga {
19276
20134
  "find_snapshot",
19277
20135
  () => Promise.resolve(this.findLatestTreeSnapshot(entries))
19278
20136
  );
20137
+ const latestGitCheckpoint = await this.readOnlyStep(
20138
+ "find_git_checkpoint",
20139
+ () => Promise.resolve(this.findLatestGitCheckpoint(entries))
20140
+ );
19279
20141
  let snapshotApplied = false;
19280
20142
  if (latestSnapshot?.archiveUrl && repositoryPath) {
19281
20143
  this.log.info("Found tree snapshot", {
@@ -19348,6 +20210,7 @@ var ResumeSaga = class extends Saga {
19348
20210
  return {
19349
20211
  conversation,
19350
20212
  latestSnapshot,
20213
+ latestGitCheckpoint,
19351
20214
  snapshotApplied,
19352
20215
  interrupted: latestSnapshot?.interrupted ?? false,
19353
20216
  lastDevice,
@@ -19358,6 +20221,7 @@ var ResumeSaga = class extends Saga {
19358
20221
  return {
19359
20222
  conversation: [],
19360
20223
  latestSnapshot: null,
20224
+ latestGitCheckpoint: null,
19361
20225
  snapshotApplied: false,
19362
20226
  interrupted: false,
19363
20227
  logEntryCount: 0
@@ -19378,6 +20242,20 @@ var ResumeSaga = class extends Saga {
19378
20242
  }
19379
20243
  return null;
19380
20244
  }
20245
+ findLatestGitCheckpoint(entries) {
20246
+ const sdkPrefixedMethod = `_${POSTHOG_NOTIFICATIONS.GIT_CHECKPOINT}`;
20247
+ for (let i2 = entries.length - 1; i2 >= 0; i2--) {
20248
+ const entry = entries[i2];
20249
+ const method = entry.notification?.method;
20250
+ if (method === sdkPrefixedMethod || method === POSTHOG_NOTIFICATIONS.GIT_CHECKPOINT) {
20251
+ const params = entry.notification?.params;
20252
+ if (params?.checkpointId && params?.checkpointRef) {
20253
+ return params;
20254
+ }
20255
+ }
20256
+ }
20257
+ return null;
20258
+ }
19381
20259
  findLastDeviceInfo(entries) {
19382
20260
  for (let i2 = entries.length - 1; i2 >= 0; i2--) {
19383
20261
  const entry = entries[i2];
@@ -19526,17 +20404,49 @@ async function resumeFromLog(config) {
19526
20404
  return {
19527
20405
  conversation: result.data.conversation,
19528
20406
  latestSnapshot: result.data.latestSnapshot,
20407
+ latestGitCheckpoint: result.data.latestGitCheckpoint,
19529
20408
  snapshotApplied: result.data.snapshotApplied,
19530
20409
  interrupted: result.data.interrupted,
19531
20410
  lastDevice: result.data.lastDevice,
19532
20411
  logEntryCount: result.data.logEntryCount
19533
20412
  };
19534
20413
  }
20414
+ var RESUME_HISTORY_TOKEN_BUDGET = 5e4;
20415
+ var TOOL_RESULT_MAX_CHARS = 2e3;
20416
+ function formatConversationForResume(conversation) {
20417
+ const selected = selectRecentTurns(conversation, RESUME_HISTORY_TOKEN_BUDGET);
20418
+ const parts2 = [];
20419
+ if (selected.length < conversation.length) {
20420
+ parts2.push(
20421
+ `*(${conversation.length - selected.length} earlier turns omitted)*`
20422
+ );
20423
+ }
20424
+ for (const turn of selected) {
20425
+ const role = turn.role === "user" ? "User" : "Assistant";
20426
+ const textParts = turn.content.filter((block) => block.type === "text").map((block) => block.text);
20427
+ if (textParts.length > 0) {
20428
+ parts2.push(`**${role}**: ${textParts.join("\n")}`);
20429
+ }
20430
+ if (turn.toolCalls?.length) {
20431
+ const toolSummary = turn.toolCalls.map((tc) => {
20432
+ let resultStr = "";
20433
+ if (tc.result !== void 0) {
20434
+ const raw = typeof tc.result === "string" ? tc.result : JSON.stringify(tc.result);
20435
+ resultStr = raw.length > TOOL_RESULT_MAX_CHARS ? ` \u2192 ${raw.substring(0, TOOL_RESULT_MAX_CHARS)}...(truncated)` : ` \u2192 ${raw}`;
20436
+ }
20437
+ return ` - ${tc.toolName}${resultStr}`;
20438
+ }).join("\n");
20439
+ parts2.push(`**${role} (tools)**:
20440
+ ${toolSummary}`);
20441
+ }
20442
+ }
20443
+ return parts2.join("\n\n");
20444
+ }
19535
20445
 
19536
20446
  // src/session-log-writer.ts
19537
20447
  var import_node_fs5 = __toESM(require("fs"), 1);
19538
- var import_promises4 = __toESM(require("fs/promises"), 1);
19539
- var import_node_path7 = __toESM(require("path"), 1);
20448
+ var import_promises6 = __toESM(require("fs/promises"), 1);
20449
+ var import_node_path9 = __toESM(require("path"), 1);
19540
20450
  var SessionLogWriter = class _SessionLogWriter {
19541
20451
  static FLUSH_DEBOUNCE_MS = 500;
19542
20452
  static FLUSH_MAX_INTERVAL_MS = 5e3;
@@ -19572,7 +20482,7 @@ var SessionLogWriter = class _SessionLogWriter {
19572
20482
  this.sessions.set(sessionId, { context, currentTurnMessages: [] });
19573
20483
  this.lastFlushAttemptTime.set(sessionId, Date.now());
19574
20484
  if (this.localCachePath) {
19575
- const sessionDir = import_node_path7.default.join(
20485
+ const sessionDir = import_node_path9.default.join(
19576
20486
  this.localCachePath,
19577
20487
  "sessions",
19578
20488
  context.runId
@@ -19830,7 +20740,7 @@ var SessionLogWriter = class _SessionLogWriter {
19830
20740
  if (!this.localCachePath) return;
19831
20741
  const session = this.sessions.get(sessionId);
19832
20742
  if (!session) return;
19833
- const logPath = import_node_path7.default.join(
20743
+ const logPath = import_node_path9.default.join(
19834
20744
  this.localCachePath,
19835
20745
  "sessions",
19836
20746
  session.context.runId,
@@ -19849,17 +20759,17 @@ var SessionLogWriter = class _SessionLogWriter {
19849
20759
  }
19850
20760
  }
19851
20761
  static async cleanupOldSessions(localCachePath) {
19852
- const sessionsDir = import_node_path7.default.join(localCachePath, "sessions");
20762
+ const sessionsDir = import_node_path9.default.join(localCachePath, "sessions");
19853
20763
  let deleted = 0;
19854
20764
  try {
19855
- const entries = await import_promises4.default.readdir(sessionsDir);
20765
+ const entries = await import_promises6.default.readdir(sessionsDir);
19856
20766
  const now = Date.now();
19857
20767
  for (const entry of entries) {
19858
- const entryPath = import_node_path7.default.join(sessionsDir, entry);
20768
+ const entryPath = import_node_path9.default.join(sessionsDir, entry);
19859
20769
  try {
19860
- const stats = await import_promises4.default.stat(entryPath);
20770
+ const stats = await import_promises6.default.stat(entryPath);
19861
20771
  if (stats.isDirectory() && now - stats.birthtimeMs > _SessionLogWriter.SESSIONS_MAX_AGE_MS) {
19862
- await import_promises4.default.rm(entryPath, { recursive: true, force: true });
20772
+ await import_promises6.default.rm(entryPath, { recursive: true, force: true });
19863
20773
  deleted++;
19864
20774
  }
19865
20775
  } catch {
@@ -19937,6 +20847,14 @@ var httpHeaderSchema = import_v4.z.object({
19937
20847
  name: import_v4.z.string(),
19938
20848
  value: import_v4.z.string()
19939
20849
  });
20850
+ var nullishString = import_v4.z.string().nullish().transform((value) => value ?? null);
20851
+ var handoffLocalGitStateSchema = import_v4.z.object({
20852
+ head: nullishString,
20853
+ branch: nullishString,
20854
+ upstreamHead: nullishString,
20855
+ upstreamRemote: nullishString,
20856
+ upstreamMergeRef: nullishString
20857
+ });
19940
20858
  var remoteMcpServerSchema = import_v4.z.object({
19941
20859
  type: import_v4.z.enum(["http", "sse"]),
19942
20860
  name: import_v4.z.string().min(1, "MCP server name is required"),
@@ -19988,13 +20906,16 @@ var setConfigOptionParamsSchema = import_v4.z.object({
19988
20906
  var refreshSessionParamsSchema = import_v4.z.object({
19989
20907
  mcpServers: mcpServersSchema
19990
20908
  });
20909
+ var closeParamsSchema = import_v4.z.object({
20910
+ localGitState: handoffLocalGitStateSchema.optional()
20911
+ }).optional();
19991
20912
  var commandParamsSchemas = {
19992
20913
  user_message: userMessageParamsSchema,
19993
20914
  "posthog/user_message": userMessageParamsSchema,
19994
20915
  cancel: import_v4.z.object({}).optional(),
19995
20916
  "posthog/cancel": import_v4.z.object({}).optional(),
19996
- close: import_v4.z.object({}).optional(),
19997
- "posthog/close": import_v4.z.object({}).optional(),
20917
+ close: closeParamsSchema,
20918
+ "posthog/close": closeParamsSchema,
19998
20919
  permission_response: permissionResponseParamsSchema,
19999
20920
  "posthog/permission_response": permissionResponseParamsSchema,
20000
20921
  set_config_option: setConfigOptionParamsSchema,
@@ -20117,7 +21038,7 @@ function getTaskRunStateString(taskRun, key) {
20117
21038
  const value = state[key];
20118
21039
  return typeof value === "string" ? value : null;
20119
21040
  }
20120
- var AgentServer = class _AgentServer {
21041
+ var AgentServer = class {
20121
21042
  config;
20122
21043
  logger;
20123
21044
  server = null;
@@ -20316,7 +21237,7 @@ var AgentServer = class _AgentServer {
20316
21237
  return app;
20317
21238
  }
20318
21239
  async start() {
20319
- await new Promise((resolve6) => {
21240
+ await new Promise((resolve7) => {
20320
21241
  this.server = (0, import_node_server.serve)(
20321
21242
  {
20322
21243
  fetch: this.app.fetch,
@@ -20326,7 +21247,7 @@ var AgentServer = class _AgentServer {
20326
21247
  this.logger.debug(
20327
21248
  `HTTP server listening on port ${this.config.port}`
20328
21249
  );
20329
- resolve6();
21250
+ resolve7();
20330
21251
  }
20331
21252
  );
20332
21253
  });
@@ -20480,6 +21401,10 @@ var AgentServer = class _AgentServer {
20480
21401
  case POSTHOG_NOTIFICATIONS.CLOSE:
20481
21402
  case "close": {
20482
21403
  this.logger.debug("Close requested");
21404
+ const localGitState = this.extractHandoffLocalGitState(params);
21405
+ if (localGitState && this.session) {
21406
+ this.session.pendingHandoffGitState = localGitState;
21407
+ }
20483
21408
  await this.cleanupSession();
20484
21409
  return { closed: true };
20485
21410
  }
@@ -20706,7 +21631,8 @@ var AgentServer = class _AgentServer {
20706
21631
  deviceInfo,
20707
21632
  logWriter,
20708
21633
  permissionMode: initialPermissionMode,
20709
- hasDesktopConnected: sseController !== null
21634
+ hasDesktopConnected: sseController !== null,
21635
+ pendingHandoffGitState: void 0
20710
21636
  };
20711
21637
  this.logger = new Logger({
20712
21638
  debug: true,
@@ -20842,7 +21768,7 @@ var AgentServer = class _AgentServer {
20842
21768
  async sendResumeMessage(payload, taskRun) {
20843
21769
  if (!this.session || !this.resumeState) return;
20844
21770
  try {
20845
- const conversationSummary = this.formatConversationForResume(
21771
+ const conversationSummary = formatConversationForResume(
20846
21772
  this.resumeState.conversation
20847
21773
  );
20848
21774
  const pendingUserPrompt = await this.getPendingUserPrompt(taskRun);
@@ -20913,40 +21839,6 @@ Continue from where you left off. The user is waiting for your response.`
20913
21839
  await this.classifyAndSignalFailure(payload, "resume", error);
20914
21840
  }
20915
21841
  }
20916
- static RESUME_HISTORY_TOKEN_BUDGET = 5e4;
20917
- static TOOL_RESULT_MAX_CHARS = 2e3;
20918
- formatConversationForResume(conversation) {
20919
- const selected = selectRecentTurns(
20920
- conversation,
20921
- _AgentServer.RESUME_HISTORY_TOKEN_BUDGET
20922
- );
20923
- const parts2 = [];
20924
- if (selected.length < conversation.length) {
20925
- parts2.push(
20926
- `*(${conversation.length - selected.length} earlier turns omitted)*`
20927
- );
20928
- }
20929
- for (const turn of selected) {
20930
- const role = turn.role === "user" ? "User" : "Assistant";
20931
- const textParts = turn.content.filter((block) => block.type === "text").map((block) => block.text);
20932
- if (textParts.length > 0) {
20933
- parts2.push(`**${role}**: ${textParts.join("\n")}`);
20934
- }
20935
- if (turn.toolCalls?.length) {
20936
- const toolSummary = turn.toolCalls.map((tc) => {
20937
- let resultStr = "";
20938
- if (tc.result !== void 0) {
20939
- const raw = typeof tc.result === "string" ? tc.result : JSON.stringify(tc.result);
20940
- resultStr = raw.length > _AgentServer.TOOL_RESULT_MAX_CHARS ? ` \u2192 ${raw.substring(0, _AgentServer.TOOL_RESULT_MAX_CHARS)}...(truncated)` : ` \u2192 ${raw}`;
20941
- }
20942
- return ` - ${tc.toolName}${resultStr}`;
20943
- }).join("\n");
20944
- parts2.push(`**${role} (tools)**:
20945
- ${toolSummary}`);
20946
- }
20947
- }
20948
- return parts2.join("\n\n");
20949
- }
20950
21842
  getInitialPromptOverride(taskRun) {
20951
21843
  const state = taskRun.state;
20952
21844
  const override = state?.initial_prompt_override;
@@ -21065,23 +21957,23 @@ ${toolSummary}`);
21065
21957
  throw new Error(`Failed to download artifact ${artifact.name}`);
21066
21958
  }
21067
21959
  const safeName = this.getSafeArtifactName(artifact.name);
21068
- const artifactDir = (0, import_node_path8.join)(
21960
+ const artifactDir = (0, import_node_path10.join)(
21069
21961
  this.config.repositoryPath ?? "/tmp/workspace",
21070
21962
  ".posthog",
21071
21963
  "attachments",
21072
21964
  runId,
21073
21965
  artifact.id ?? safeName
21074
21966
  );
21075
- await (0, import_promises5.mkdir)(artifactDir, { recursive: true });
21076
- const artifactPath = (0, import_node_path8.join)(artifactDir, safeName);
21077
- await (0, import_promises5.writeFile)(artifactPath, Buffer.from(data));
21967
+ await (0, import_promises7.mkdir)(artifactDir, { recursive: true });
21968
+ const artifactPath = (0, import_node_path10.join)(artifactDir, safeName);
21969
+ await (0, import_promises7.writeFile)(artifactPath, Buffer.from(data));
21078
21970
  return resourceLink((0, import_node_url2.pathToFileURL)(artifactPath).toString(), artifact.name, {
21079
21971
  ...artifact.content_type ? { mimeType: artifact.content_type } : {},
21080
21972
  ...typeof artifact.size === "number" ? { size: artifact.size } : {}
21081
21973
  });
21082
21974
  }
21083
21975
  getSafeArtifactName(name2) {
21084
- const baseName = (0, import_node_path8.basename)(name2).trim();
21976
+ const baseName = (0, import_node_path10.basename)(name2).trim();
21085
21977
  const normalizedName = baseName.replace(/[^\w.-]/g, "_");
21086
21978
  return normalizedName.length > 0 ? normalizedName : "attachment";
21087
21979
  }
@@ -21573,6 +22465,11 @@ ${attributionInstructions}
21573
22465
  async cleanupSession() {
21574
22466
  if (!this.session) return;
21575
22467
  this.logger.debug("Cleaning up session");
22468
+ try {
22469
+ await this.captureHandoffCheckpoint();
22470
+ } catch (error) {
22471
+ this.logger.error("Failed to capture handoff checkpoint", error);
22472
+ }
21576
22473
  try {
21577
22474
  await this.captureTreeState();
21578
22475
  } catch (error) {
@@ -21632,6 +22529,50 @@ ${attributionInstructions}
21632
22529
  this.logger.error("Failed to capture tree state", error);
21633
22530
  }
21634
22531
  }
22532
+ async captureHandoffCheckpoint() {
22533
+ if (!this.session?.treeTracker || !this.session.pendingHandoffGitState) {
22534
+ return;
22535
+ }
22536
+ if (!this.posthogAPI) {
22537
+ this.logger.warn(
22538
+ "Skipping handoff checkpoint capture: PostHog API client is not configured"
22539
+ );
22540
+ return;
22541
+ }
22542
+ const tracker = new HandoffCheckpointTracker({
22543
+ repositoryPath: this.config.repositoryPath ?? "/tmp/workspace",
22544
+ taskId: this.session.payload.task_id,
22545
+ runId: this.session.payload.run_id,
22546
+ apiClient: this.posthogAPI,
22547
+ logger: this.logger.child("HandoffCheckpoint")
22548
+ });
22549
+ const checkpoint = await tracker.captureForHandoff(
22550
+ this.session.pendingHandoffGitState
22551
+ );
22552
+ if (!checkpoint) return;
22553
+ const checkpointWithDevice = {
22554
+ ...checkpoint,
22555
+ device: this.session.deviceInfo
22556
+ };
22557
+ const notification = {
22558
+ jsonrpc: "2.0",
22559
+ method: POSTHOG_NOTIFICATIONS.GIT_CHECKPOINT,
22560
+ params: checkpointWithDevice
22561
+ };
22562
+ this.broadcastEvent({
22563
+ type: "notification",
22564
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
22565
+ notification
22566
+ });
22567
+ this.session.logWriter.appendRawLine(
22568
+ this.session.payload.run_id,
22569
+ JSON.stringify(notification)
22570
+ );
22571
+ }
22572
+ extractHandoffLocalGitState(params) {
22573
+ const result = handoffLocalGitStateSchema.safeParse(params.localGitState);
22574
+ return result.success ? result.data : null;
22575
+ }
21635
22576
  broadcastTurnComplete(stopReason) {
21636
22577
  if (!this.session) return;
21637
22578
  this.broadcastEvent({
@@ -21685,8 +22626,8 @@ ${attributionInstructions}
21685
22626
  options: params.options,
21686
22627
  toolCall: params.toolCall
21687
22628
  });
21688
- return new Promise((resolve6) => {
21689
- this.pendingPermissions.set(requestId, { resolve: resolve6 });
22629
+ return new Promise((resolve7) => {
22630
+ this.pendingPermissions.set(requestId, { resolve: resolve7 });
21690
22631
  });
21691
22632
  }
21692
22633
  resolvePermission(requestId, optionId, customInput, answers) {