@posthog/agent 2.3.386 → 2.3.387
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/claude/session/jsonl-hydration.d.ts +1 -0
- package/dist/agent.d.ts +1 -0
- package/dist/agent.js +13 -2
- package/dist/agent.js.map +1 -1
- package/dist/handoff-checkpoint.d.ts +39 -0
- package/dist/handoff-checkpoint.js +6679 -0
- package/dist/handoff-checkpoint.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/posthog-api.d.ts +1 -0
- package/dist/posthog-api.js +11 -2
- package/dist/posthog-api.js.map +1 -1
- package/dist/resume.d.ts +3 -1
- package/dist/resume.js +41 -4
- package/dist/resume.js.map +1 -1
- package/dist/server/agent-server.d.ts +5 -15
- package/dist/server/agent-server.js +1330 -394
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +1332 -396
- package/dist/server/bin.cjs.map +1 -1
- package/dist/server/schemas.d.ts +191 -0
- package/dist/server/schemas.js +108 -0
- package/dist/server/schemas.js.map +1 -0
- package/dist/tree-tracker.d.ts +1 -0
- package/dist/tree-tracker.js +18 -4
- package/dist/tree-tracker.js.map +1 -1
- package/dist/types.d.ts +18 -1
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -1
- package/package.json +9 -1
- package/src/acp-extensions.ts +3 -0
- package/src/handoff-checkpoint.test.ts +183 -0
- package/src/handoff-checkpoint.ts +361 -0
- package/src/posthog-api.test.ts +29 -0
- package/src/posthog-api.ts +5 -1
- package/src/resume.ts +7 -1
- package/src/sagas/apply-snapshot-saga.ts +7 -0
- package/src/sagas/capture-tree-saga.ts +10 -3
- package/src/sagas/resume-saga.ts +32 -0
- package/src/sagas/test-fixtures.ts +46 -0
- package/src/server/agent-server.ts +74 -1
- package/src/server/schemas.ts +21 -2
- package/src/types.ts +24 -0
package/dist/server/bin.cjs
CHANGED
|
@@ -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(
|
|
809
|
-
log(`checking %s`,
|
|
808
|
+
function check(path17, isFile2, isDirectory) {
|
|
809
|
+
log(`checking %s`, path17);
|
|
810
810
|
try {
|
|
811
|
-
const
|
|
812
|
-
if (
|
|
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 (
|
|
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(
|
|
832
|
-
return check(
|
|
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(
|
|
928
|
+
function locateFile(path17) {
|
|
929
929
|
if (Module["locateFile"]) {
|
|
930
|
-
return Module["locateFile"](
|
|
930
|
+
return Module["locateFile"](path17, scriptDirectory);
|
|
931
931
|
}
|
|
932
|
-
return scriptDirectory +
|
|
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((
|
|
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
|
|
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,
|
|
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
|
-
|
|
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(
|
|
1957
|
-
asyncLoad(libFile,
|
|
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
|
|
3454
|
-
bytes = Promise.resolve(
|
|
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
|
|
3916
|
-
var
|
|
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(
|
|
3963
|
-
return
|
|
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(
|
|
4053
|
-
return (0, import_file_exists.exists)(
|
|
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(
|
|
4458
|
-
return /^\.(git)?$/.test(
|
|
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 [
|
|
4893
|
-
paths.add(
|
|
4894
|
-
(results[
|
|
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:
|
|
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,
|
|
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,
|
|
5665
|
+
return new InitSummary(bare, path17, false, result[1]);
|
|
5666
5666
|
}
|
|
5667
5667
|
if (result = reInitResponseRegex.exec(response)) {
|
|
5668
|
-
return new InitSummary(bare,
|
|
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,
|
|
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,
|
|
5688
|
+
constructor(bare, path17, existing, gitDir) {
|
|
5689
5689
|
this.bare = bare;
|
|
5690
|
-
this.path =
|
|
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,
|
|
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"),
|
|
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(
|
|
6528
|
-
this.path =
|
|
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(
|
|
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,
|
|
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,
|
|
6567
|
+
handler(result, path17);
|
|
6568
6568
|
}
|
|
6569
6569
|
if (raw !== "##" && raw !== "!!") {
|
|
6570
|
-
result.files.push(new FileStatusSummary(
|
|
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(
|
|
6883
|
+
hashObject(path17, write) {
|
|
6884
6884
|
return this._runTask(
|
|
6885
|
-
hashObjectTask(
|
|
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
|
|
7239
|
-
return
|
|
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,
|
|
7554
|
-
return subModuleTask(["add", repo,
|
|
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,
|
|
7885
|
-
return this._runTask(addSubModuleTask2(repo,
|
|
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
|
|
8501
|
+
const stat4 = await import_promises.default.stat(lockPath);
|
|
8502
8502
|
return {
|
|
8503
8503
|
path: lockPath,
|
|
8504
|
-
ageMs: Date.now() -
|
|
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((
|
|
8539
|
+
return new Promise((resolve7) => {
|
|
8540
8540
|
this.readQueue.push(() => {
|
|
8541
8541
|
this.readers++;
|
|
8542
|
-
|
|
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((
|
|
8556
|
+
return new Promise((resolve7) => {
|
|
8557
8557
|
this.writeQueue.push(() => {
|
|
8558
8558
|
this.writerWaiting = this.writeQueue.length > 0;
|
|
8559
8559
|
this.writer = true;
|
|
8560
|
-
|
|
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.
|
|
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: {
|
|
@@ -8789,6 +8789,10 @@ var package_default = {
|
|
|
8789
8789
|
types: "./dist/resume.d.ts",
|
|
8790
8790
|
import: "./dist/resume.js"
|
|
8791
8791
|
},
|
|
8792
|
+
"./handoff-checkpoint": {
|
|
8793
|
+
types: "./dist/handoff-checkpoint.d.ts",
|
|
8794
|
+
import: "./dist/handoff-checkpoint.js"
|
|
8795
|
+
},
|
|
8792
8796
|
"./tree-tracker": {
|
|
8793
8797
|
types: "./dist/tree-tracker.d.ts",
|
|
8794
8798
|
import: "./dist/tree-tracker.js"
|
|
@@ -8796,6 +8800,10 @@ var package_default = {
|
|
|
8796
8800
|
"./server": {
|
|
8797
8801
|
types: "./dist/server/agent-server.d.ts",
|
|
8798
8802
|
import: "./dist/server/agent-server.js"
|
|
8803
|
+
},
|
|
8804
|
+
"./server/schemas": {
|
|
8805
|
+
types: "./dist/server/schemas.d.ts",
|
|
8806
|
+
import: "./dist/server/schemas.js"
|
|
8799
8807
|
}
|
|
8800
8808
|
},
|
|
8801
8809
|
bin: {
|
|
@@ -8886,6 +8894,8 @@ var POSTHOG_NOTIFICATIONS = {
|
|
|
8886
8894
|
SDK_SESSION: "_posthog/sdk_session",
|
|
8887
8895
|
/** Tree state snapshot captured (git tree hash + file archive) */
|
|
8888
8896
|
TREE_SNAPSHOT: "_posthog/tree_snapshot",
|
|
8897
|
+
/** Git checkpoint captured for handoff */
|
|
8898
|
+
GIT_CHECKPOINT: "_posthog/git_checkpoint",
|
|
8889
8899
|
/** Agent mode changed (interactive/background) */
|
|
8890
8900
|
MODE_CHANGE: "_posthog/mode_change",
|
|
8891
8901
|
/** Request to resume a session from previous state */
|
|
@@ -8992,17 +9002,17 @@ var Pushable = class {
|
|
|
8992
9002
|
resolvers = [];
|
|
8993
9003
|
done = false;
|
|
8994
9004
|
push(item) {
|
|
8995
|
-
const
|
|
8996
|
-
if (
|
|
8997
|
-
|
|
9005
|
+
const resolve7 = this.resolvers.shift();
|
|
9006
|
+
if (resolve7) {
|
|
9007
|
+
resolve7({ value: item, done: false });
|
|
8998
9008
|
} else {
|
|
8999
9009
|
this.queue.push(item);
|
|
9000
9010
|
}
|
|
9001
9011
|
}
|
|
9002
9012
|
end() {
|
|
9003
9013
|
this.done = true;
|
|
9004
|
-
for (const
|
|
9005
|
-
|
|
9014
|
+
for (const resolve7 of this.resolvers) {
|
|
9015
|
+
resolve7({ value: void 0, done: true });
|
|
9006
9016
|
}
|
|
9007
9017
|
this.resolvers = [];
|
|
9008
9018
|
}
|
|
@@ -9019,8 +9029,8 @@ var Pushable = class {
|
|
|
9019
9029
|
done: true
|
|
9020
9030
|
});
|
|
9021
9031
|
}
|
|
9022
|
-
return new Promise((
|
|
9023
|
-
this.resolvers.push(
|
|
9032
|
+
return new Promise((resolve7) => {
|
|
9033
|
+
this.resolvers.push(resolve7);
|
|
9024
9034
|
});
|
|
9025
9035
|
}
|
|
9026
9036
|
};
|
|
@@ -9134,20 +9144,20 @@ function nodeReadableToWebReadable(nodeStream) {
|
|
|
9134
9144
|
function nodeWritableToWebWritable(nodeStream) {
|
|
9135
9145
|
return new import_web.WritableStream({
|
|
9136
9146
|
write(chunk) {
|
|
9137
|
-
return new Promise((
|
|
9147
|
+
return new Promise((resolve7, reject) => {
|
|
9138
9148
|
const ok = nodeStream.write(Buffer.from(chunk), (err2) => {
|
|
9139
9149
|
if (err2) reject(err2);
|
|
9140
9150
|
});
|
|
9141
9151
|
if (ok) {
|
|
9142
|
-
|
|
9152
|
+
resolve7();
|
|
9143
9153
|
} else {
|
|
9144
|
-
nodeStream.once("drain",
|
|
9154
|
+
nodeStream.once("drain", resolve7);
|
|
9145
9155
|
}
|
|
9146
9156
|
});
|
|
9147
9157
|
},
|
|
9148
9158
|
close() {
|
|
9149
|
-
return new Promise((
|
|
9150
|
-
nodeStream.end(
|
|
9159
|
+
return new Promise((resolve7) => {
|
|
9160
|
+
nodeStream.end(resolve7);
|
|
9151
9161
|
});
|
|
9152
9162
|
},
|
|
9153
9163
|
abort(reason) {
|
|
@@ -13089,9 +13099,9 @@ var PostHogEnricher = class {
|
|
|
13089
13099
|
}
|
|
13090
13100
|
let mtimeMs = 0;
|
|
13091
13101
|
try {
|
|
13092
|
-
const
|
|
13093
|
-
mtimeMs =
|
|
13094
|
-
if (
|
|
13102
|
+
const stat22 = await fs4.stat(absPath);
|
|
13103
|
+
mtimeMs = stat22.mtimeMs;
|
|
13104
|
+
if (stat22.size > MAX_WRAPPER_SOURCE_BYTES) {
|
|
13095
13105
|
return this.setWrapperCache(absPath, mtimeMs, []);
|
|
13096
13106
|
}
|
|
13097
13107
|
} catch {
|
|
@@ -13257,7 +13267,7 @@ async function buildWrapperContext(deps, content, langId, absPath) {
|
|
|
13257
13267
|
// src/utils/common.ts
|
|
13258
13268
|
async function withTimeout(operation, timeoutMs) {
|
|
13259
13269
|
const timeoutPromise = new Promise(
|
|
13260
|
-
(
|
|
13270
|
+
(resolve7) => setTimeout(() => resolve7({ result: "timeout" }), timeoutMs)
|
|
13261
13271
|
);
|
|
13262
13272
|
const operationPromise = operation.then((value) => ({
|
|
13263
13273
|
result: "success",
|
|
@@ -13555,8 +13565,8 @@ var ToolContentBuilder = class {
|
|
|
13555
13565
|
this.items.push({ type: "content", content: image(data, mimeType, uri) });
|
|
13556
13566
|
return this;
|
|
13557
13567
|
}
|
|
13558
|
-
diff(
|
|
13559
|
-
this.items.push({ type: "diff", path:
|
|
13568
|
+
diff(path17, oldText, newText) {
|
|
13569
|
+
this.items.push({ type: "diff", path: path17, oldText, newText });
|
|
13560
13570
|
return this;
|
|
13561
13571
|
}
|
|
13562
13572
|
build() {
|
|
@@ -13743,7 +13753,7 @@ function buildToolKey(serverName, toolName) {
|
|
|
13743
13753
|
return `mcp__${serverName}__${toolName}`;
|
|
13744
13754
|
}
|
|
13745
13755
|
function delay2(ms) {
|
|
13746
|
-
return new Promise((
|
|
13756
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
13747
13757
|
}
|
|
13748
13758
|
async function fetchMcpToolMetadata(q, logger = new Logger({ debug: false, prefix: "[McpToolMetadata]" })) {
|
|
13749
13759
|
let retries = 0;
|
|
@@ -15957,8 +15967,8 @@ var AsyncMutex = class {
|
|
|
15957
15967
|
this.locked = true;
|
|
15958
15968
|
return;
|
|
15959
15969
|
}
|
|
15960
|
-
return new Promise((
|
|
15961
|
-
this.queue.push(
|
|
15970
|
+
return new Promise((resolve7) => {
|
|
15971
|
+
this.queue.push(resolve7);
|
|
15962
15972
|
});
|
|
15963
15973
|
}
|
|
15964
15974
|
release() {
|
|
@@ -16485,8 +16495,8 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
16485
16495
|
if (this.session.promptRunning) {
|
|
16486
16496
|
this.session.input.push(userMessage);
|
|
16487
16497
|
const order = this.session.nextPendingOrder++;
|
|
16488
|
-
const cancelled = await new Promise((
|
|
16489
|
-
this.session.pendingMessages.set(promptUuid, { resolve:
|
|
16498
|
+
const cancelled = await new Promise((resolve7) => {
|
|
16499
|
+
this.session.pendingMessages.set(promptUuid, { resolve: resolve7, order });
|
|
16490
16500
|
});
|
|
16491
16501
|
if (cancelled) {
|
|
16492
16502
|
return { stopReason: "cancelled" };
|
|
@@ -17367,7 +17377,7 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
17367
17377
|
*/
|
|
17368
17378
|
deferBackgroundFetches(q) {
|
|
17369
17379
|
Promise.all([
|
|
17370
|
-
new Promise((
|
|
17380
|
+
new Promise((resolve7) => setTimeout(resolve7, 10)).then(
|
|
17371
17381
|
() => this.sendAvailableCommandsUpdate()
|
|
17372
17382
|
),
|
|
17373
17383
|
fetchMcpToolMetadata(q, this.logger).then(() => {
|
|
@@ -18331,245 +18341,19 @@ function createCodexConnection(config) {
|
|
|
18331
18341
|
};
|
|
18332
18342
|
}
|
|
18333
18343
|
|
|
18334
|
-
// src/
|
|
18335
|
-
|
|
18336
|
-
|
|
18337
|
-
const hostname = url.hostname;
|
|
18338
|
-
if (hostname === "localhost" || hostname === "127.0.0.1") {
|
|
18339
|
-
return `${url.protocol}//localhost:3308`;
|
|
18340
|
-
}
|
|
18341
|
-
if (hostname === "host.docker.internal") {
|
|
18342
|
-
return `${url.protocol}//host.docker.internal:3308`;
|
|
18343
|
-
}
|
|
18344
|
-
const region = hostname.match(/^(us|eu)\.posthog\.com$/)?.[1] ?? "us";
|
|
18345
|
-
return `https://gateway.${region}.posthog.com`;
|
|
18346
|
-
}
|
|
18347
|
-
function getLlmGatewayUrl(posthogHost, product = "posthog_code") {
|
|
18348
|
-
return `${getGatewayBaseUrl(posthogHost)}/${product}`;
|
|
18349
|
-
}
|
|
18344
|
+
// src/handoff-checkpoint.ts
|
|
18345
|
+
var import_promises3 = require("fs/promises");
|
|
18346
|
+
var import_node_path6 = require("path");
|
|
18350
18347
|
|
|
18351
|
-
//
|
|
18352
|
-
var
|
|
18353
|
-
var
|
|
18354
|
-
|
|
18355
|
-
constructor(config) {
|
|
18356
|
-
this.config = config;
|
|
18357
|
-
}
|
|
18358
|
-
get baseUrl() {
|
|
18359
|
-
const host = this.config.apiUrl.endsWith("/") ? this.config.apiUrl.slice(0, -1) : this.config.apiUrl;
|
|
18360
|
-
return host;
|
|
18361
|
-
}
|
|
18362
|
-
isAuthFailure(status) {
|
|
18363
|
-
return status === 401 || status === 403;
|
|
18364
|
-
}
|
|
18365
|
-
async resolveApiKey(forceRefresh = false) {
|
|
18366
|
-
if (forceRefresh && this.config.refreshApiKey) {
|
|
18367
|
-
return this.config.refreshApiKey();
|
|
18368
|
-
}
|
|
18369
|
-
return this.config.getApiKey();
|
|
18370
|
-
}
|
|
18371
|
-
async buildHeaders(options, forceRefresh = false) {
|
|
18372
|
-
const headers = new Headers(options.headers);
|
|
18373
|
-
headers.set(
|
|
18374
|
-
"Authorization",
|
|
18375
|
-
`Bearer ${await this.resolveApiKey(forceRefresh)}`
|
|
18376
|
-
);
|
|
18377
|
-
headers.set("Content-Type", "application/json");
|
|
18378
|
-
headers.set("User-Agent", this.config.userAgent ?? DEFAULT_USER_AGENT);
|
|
18379
|
-
return headers;
|
|
18380
|
-
}
|
|
18381
|
-
async performRequest(endpoint, options, forceRefresh = false) {
|
|
18382
|
-
const url = `${this.baseUrl}${endpoint}`;
|
|
18383
|
-
return fetch(url, {
|
|
18384
|
-
...options,
|
|
18385
|
-
headers: await this.buildHeaders(options, forceRefresh)
|
|
18386
|
-
});
|
|
18387
|
-
}
|
|
18388
|
-
async performRequestWithRetry(endpoint, options = {}) {
|
|
18389
|
-
let response = await this.performRequest(endpoint, options);
|
|
18390
|
-
if (!response.ok && this.isAuthFailure(response.status)) {
|
|
18391
|
-
response = await this.performRequest(endpoint, options, true);
|
|
18392
|
-
}
|
|
18393
|
-
return response;
|
|
18394
|
-
}
|
|
18395
|
-
async apiRequest(endpoint, options = {}) {
|
|
18396
|
-
const response = await this.performRequestWithRetry(endpoint, options);
|
|
18397
|
-
if (!response.ok) {
|
|
18398
|
-
let errorMessage;
|
|
18399
|
-
try {
|
|
18400
|
-
const errorResponse = await response.json();
|
|
18401
|
-
errorMessage = `Failed request: [${response.status}] ${JSON.stringify(errorResponse)}`;
|
|
18402
|
-
} catch {
|
|
18403
|
-
errorMessage = `Failed request: [${response.status}] ${response.statusText}`;
|
|
18404
|
-
}
|
|
18405
|
-
throw new Error(errorMessage);
|
|
18406
|
-
}
|
|
18407
|
-
return response.json();
|
|
18408
|
-
}
|
|
18409
|
-
getTeamId() {
|
|
18410
|
-
return this.config.projectId;
|
|
18411
|
-
}
|
|
18412
|
-
async getApiKey(forceRefresh = false) {
|
|
18413
|
-
return this.resolveApiKey(forceRefresh);
|
|
18414
|
-
}
|
|
18415
|
-
getLlmGatewayUrl() {
|
|
18416
|
-
return getLlmGatewayUrl(this.baseUrl);
|
|
18417
|
-
}
|
|
18418
|
-
async getTask(taskId) {
|
|
18419
|
-
const teamId = this.getTeamId();
|
|
18420
|
-
return this.apiRequest(`/api/projects/${teamId}/tasks/${taskId}/`);
|
|
18421
|
-
}
|
|
18422
|
-
async getTaskRun(taskId, runId) {
|
|
18423
|
-
const teamId = this.getTeamId();
|
|
18424
|
-
return this.apiRequest(
|
|
18425
|
-
`/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/`
|
|
18426
|
-
);
|
|
18427
|
-
}
|
|
18428
|
-
async updateTaskRun(taskId, runId, payload) {
|
|
18429
|
-
const teamId = this.getTeamId();
|
|
18430
|
-
return this.apiRequest(
|
|
18431
|
-
`/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/`,
|
|
18432
|
-
{
|
|
18433
|
-
method: "PATCH",
|
|
18434
|
-
body: JSON.stringify(payload)
|
|
18435
|
-
}
|
|
18436
|
-
);
|
|
18437
|
-
}
|
|
18438
|
-
async setTaskRunOutput(taskId, runId, output) {
|
|
18439
|
-
return this.apiRequest(
|
|
18440
|
-
`/api/projects/${this.getTeamId()}/tasks/${taskId}/runs/${runId}/set_output/`,
|
|
18441
|
-
{
|
|
18442
|
-
method: "PATCH",
|
|
18443
|
-
body: JSON.stringify(output)
|
|
18444
|
-
}
|
|
18445
|
-
);
|
|
18446
|
-
}
|
|
18447
|
-
async appendTaskRunLog(taskId, runId, entries) {
|
|
18448
|
-
const teamId = this.getTeamId();
|
|
18449
|
-
return this.apiRequest(
|
|
18450
|
-
`/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/append_log/`,
|
|
18451
|
-
{
|
|
18452
|
-
method: "POST",
|
|
18453
|
-
body: JSON.stringify({ entries })
|
|
18454
|
-
}
|
|
18455
|
-
);
|
|
18456
|
-
}
|
|
18457
|
-
async relayMessage(taskId, runId, text2) {
|
|
18458
|
-
const teamId = this.getTeamId();
|
|
18459
|
-
await this.apiRequest(
|
|
18460
|
-
`/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/relay_message/`,
|
|
18461
|
-
{
|
|
18462
|
-
method: "POST",
|
|
18463
|
-
body: JSON.stringify({ text: text2 })
|
|
18464
|
-
}
|
|
18465
|
-
);
|
|
18466
|
-
}
|
|
18467
|
-
async uploadTaskArtifacts(taskId, runId, artifacts) {
|
|
18468
|
-
if (!artifacts.length) {
|
|
18469
|
-
return [];
|
|
18470
|
-
}
|
|
18471
|
-
const teamId = this.getTeamId();
|
|
18472
|
-
const response = await this.apiRequest(
|
|
18473
|
-
`/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/artifacts/`,
|
|
18474
|
-
{
|
|
18475
|
-
method: "POST",
|
|
18476
|
-
body: JSON.stringify({ artifacts })
|
|
18477
|
-
}
|
|
18478
|
-
);
|
|
18479
|
-
return response.artifacts ?? [];
|
|
18480
|
-
}
|
|
18481
|
-
/**
|
|
18482
|
-
* Download artifact content by storage path
|
|
18483
|
-
* Streams the file through the PostHog backend so the sandbox does not need
|
|
18484
|
-
* direct access to object storage.
|
|
18485
|
-
*/
|
|
18486
|
-
async downloadArtifact(taskId, runId, storagePath) {
|
|
18487
|
-
const teamId = this.getTeamId();
|
|
18488
|
-
try {
|
|
18489
|
-
const response = await this.performRequestWithRetry(
|
|
18490
|
-
`/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/artifacts/download/`,
|
|
18491
|
-
{
|
|
18492
|
-
method: "POST",
|
|
18493
|
-
body: JSON.stringify({ storage_path: storagePath })
|
|
18494
|
-
}
|
|
18495
|
-
);
|
|
18496
|
-
if (!response.ok) {
|
|
18497
|
-
throw new Error(`Failed to download artifact: ${response.status}`);
|
|
18498
|
-
}
|
|
18499
|
-
return response.arrayBuffer();
|
|
18500
|
-
} catch {
|
|
18501
|
-
return null;
|
|
18502
|
-
}
|
|
18503
|
-
}
|
|
18504
|
-
/**
|
|
18505
|
-
* Fetch logs for a task run via the logs API endpoint
|
|
18506
|
-
* @param taskRun - The task run to fetch logs for
|
|
18507
|
-
* @returns Array of stored entries, or empty array if no logs available
|
|
18508
|
-
*/
|
|
18509
|
-
async fetchTaskRunLogs(taskRun) {
|
|
18510
|
-
const teamId = this.getTeamId();
|
|
18511
|
-
const endpoint = `/api/projects/${teamId}/tasks/${taskRun.task}/runs/${taskRun.id}/logs`;
|
|
18512
|
-
try {
|
|
18513
|
-
const response = await this.performRequestWithRetry(endpoint);
|
|
18514
|
-
if (!response.ok) {
|
|
18515
|
-
if (response.status === 404) {
|
|
18516
|
-
return [];
|
|
18517
|
-
}
|
|
18518
|
-
throw new Error(
|
|
18519
|
-
`Failed to fetch logs: ${response.status} ${response.statusText}`
|
|
18520
|
-
);
|
|
18521
|
-
}
|
|
18522
|
-
const content = await response.text();
|
|
18523
|
-
if (!content.trim()) {
|
|
18524
|
-
return [];
|
|
18525
|
-
}
|
|
18526
|
-
return content.trim().split("\n").map((line) => JSON.parse(line));
|
|
18527
|
-
} catch (error) {
|
|
18528
|
-
throw new Error(
|
|
18529
|
-
`Failed to fetch task run logs: ${error instanceof Error ? error.message : String(error)}`
|
|
18530
|
-
);
|
|
18531
|
-
}
|
|
18532
|
-
}
|
|
18533
|
-
};
|
|
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);
|
|
18534
18352
|
|
|
18535
|
-
//
|
|
18353
|
+
// ../git/dist/sagas/checkpoint.js
|
|
18536
18354
|
var import_node_crypto2 = require("crypto");
|
|
18537
18355
|
var fs10 = __toESM(require("fs/promises"), 1);
|
|
18538
|
-
var os6 = __toESM(require("os"), 1);
|
|
18539
18356
|
var path12 = __toESM(require("path"), 1);
|
|
18540
|
-
var CHARS_PER_TOKEN = 4;
|
|
18541
|
-
var DEFAULT_MAX_TOKENS = 15e4;
|
|
18542
|
-
function estimateTurnTokens(turn) {
|
|
18543
|
-
let chars = 0;
|
|
18544
|
-
for (const block of turn.content) {
|
|
18545
|
-
if ("text" in block && typeof block.text === "string") {
|
|
18546
|
-
chars += block.text.length;
|
|
18547
|
-
}
|
|
18548
|
-
}
|
|
18549
|
-
if (turn.toolCalls) {
|
|
18550
|
-
for (const tc of turn.toolCalls) {
|
|
18551
|
-
chars += JSON.stringify(tc.input ?? "").length;
|
|
18552
|
-
if (tc.result !== void 0) {
|
|
18553
|
-
chars += typeof tc.result === "string" ? tc.result.length : JSON.stringify(tc.result).length;
|
|
18554
|
-
}
|
|
18555
|
-
}
|
|
18556
|
-
}
|
|
18557
|
-
return Math.ceil(chars / CHARS_PER_TOKEN);
|
|
18558
|
-
}
|
|
18559
|
-
function selectRecentTurns(turns, maxTokens = DEFAULT_MAX_TOKENS) {
|
|
18560
|
-
let budget = maxTokens;
|
|
18561
|
-
let startIndex = turns.length;
|
|
18562
|
-
for (let i2 = turns.length - 1; i2 >= 0; i2--) {
|
|
18563
|
-
const cost = estimateTurnTokens(turns[i2]);
|
|
18564
|
-
if (cost > budget) break;
|
|
18565
|
-
budget -= cost;
|
|
18566
|
-
startIndex = i2;
|
|
18567
|
-
}
|
|
18568
|
-
while (startIndex < turns.length && turns[startIndex].role !== "user") {
|
|
18569
|
-
startIndex++;
|
|
18570
|
-
}
|
|
18571
|
-
return turns.slice(startIndex);
|
|
18572
|
-
}
|
|
18573
18357
|
|
|
18574
18358
|
// ../shared/dist/index.js
|
|
18575
18359
|
var CLOUD_PROMPT_PREFIX = "__twig_cloud_prompt_v1__:";
|
|
@@ -18713,16 +18497,6 @@ var Saga = class {
|
|
|
18713
18497
|
}
|
|
18714
18498
|
};
|
|
18715
18499
|
|
|
18716
|
-
// src/sagas/apply-snapshot-saga.ts
|
|
18717
|
-
var import_promises2 = require("fs/promises");
|
|
18718
|
-
var import_node_path5 = require("path");
|
|
18719
|
-
|
|
18720
|
-
// ../git/dist/sagas/tree.js
|
|
18721
|
-
var import_node_fs3 = require("fs");
|
|
18722
|
-
var fs11 = __toESM(require("fs/promises"), 1);
|
|
18723
|
-
var path13 = __toESM(require("path"), 1);
|
|
18724
|
-
var tar = __toESM(require("tar"), 1);
|
|
18725
|
-
|
|
18726
18500
|
// ../git/dist/git-saga.js
|
|
18727
18501
|
var GitSaga = class extends Saga {
|
|
18728
18502
|
_git = null;
|
|
@@ -18741,20 +18515,1082 @@ var GitSaga = class extends Saga {
|
|
|
18741
18515
|
}
|
|
18742
18516
|
};
|
|
18743
18517
|
|
|
18744
|
-
// ../git/dist/sagas/
|
|
18745
|
-
var
|
|
18746
|
-
|
|
18747
|
-
|
|
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";
|
|
18748
18529
|
async executeGitOperations(input) {
|
|
18749
|
-
const { baseDir
|
|
18750
|
-
const
|
|
18751
|
-
await this.
|
|
18752
|
-
|
|
18753
|
-
|
|
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 {
|
|
19582
|
+
sagaName = "CaptureTreeSaga";
|
|
19583
|
+
tempIndexPath = null;
|
|
19584
|
+
async executeGitOperations(input) {
|
|
19585
|
+
const { baseDir, lastTreeHash, archivePath, signal } = input;
|
|
19586
|
+
const tmpDir = path15.join(baseDir, ".git", "posthog-code-tmp");
|
|
19587
|
+
await this.step({
|
|
19588
|
+
name: "create_tmp_dir",
|
|
19589
|
+
execute: () => fs12.mkdir(tmpDir, { recursive: true }),
|
|
18754
19590
|
rollback: async () => {
|
|
18755
19591
|
}
|
|
18756
19592
|
});
|
|
18757
|
-
this.tempIndexPath =
|
|
19593
|
+
this.tempIndexPath = path15.join(tmpDir, `index-${Date.now()}`);
|
|
18758
19594
|
const tempIndexGit = this.git.env({
|
|
18759
19595
|
...process.env,
|
|
18760
19596
|
GIT_INDEX_FILE: this.tempIndexPath
|
|
@@ -18764,7 +19600,7 @@ var CaptureTreeSaga = class extends GitSaga {
|
|
|
18764
19600
|
execute: () => tempIndexGit.raw(["read-tree", "HEAD"]),
|
|
18765
19601
|
rollback: async () => {
|
|
18766
19602
|
if (this.tempIndexPath) {
|
|
18767
|
-
await
|
|
19603
|
+
await fs12.rm(this.tempIndexPath, { force: true }).catch(() => {
|
|
18768
19604
|
});
|
|
18769
19605
|
}
|
|
18770
19606
|
}
|
|
@@ -18773,7 +19609,7 @@ var CaptureTreeSaga = class extends GitSaga {
|
|
|
18773
19609
|
const treeHash = await this.readOnlyStep("write_tree", () => tempIndexGit.raw(["write-tree"]));
|
|
18774
19610
|
if (lastTreeHash && treeHash === lastTreeHash) {
|
|
18775
19611
|
this.log.debug("No changes since last capture", { treeHash });
|
|
18776
|
-
await
|
|
19612
|
+
await fs12.rm(this.tempIndexPath, { force: true }).catch(() => {
|
|
18777
19613
|
});
|
|
18778
19614
|
return { snapshot: null, changed: false };
|
|
18779
19615
|
}
|
|
@@ -18785,7 +19621,7 @@ var CaptureTreeSaga = class extends GitSaga {
|
|
|
18785
19621
|
}
|
|
18786
19622
|
});
|
|
18787
19623
|
const changes = await this.readOnlyStep("get_changes", () => this.getChanges(this.git, baseCommit, treeHash));
|
|
18788
|
-
await
|
|
19624
|
+
await fs12.rm(this.tempIndexPath, { force: true }).catch(() => {
|
|
18789
19625
|
});
|
|
18790
19626
|
const snapshot = {
|
|
18791
19627
|
treeHash,
|
|
@@ -18809,15 +19645,15 @@ var CaptureTreeSaga = class extends GitSaga {
|
|
|
18809
19645
|
if (filesToArchive.length === 0) {
|
|
18810
19646
|
return void 0;
|
|
18811
19647
|
}
|
|
18812
|
-
const existingFiles = filesToArchive.filter((f) => (0, import_node_fs3.existsSync)(
|
|
19648
|
+
const existingFiles = filesToArchive.filter((f) => (0, import_node_fs3.existsSync)(path15.join(baseDir, f)));
|
|
18813
19649
|
if (existingFiles.length === 0) {
|
|
18814
19650
|
return void 0;
|
|
18815
19651
|
}
|
|
18816
19652
|
await this.step({
|
|
18817
19653
|
name: "create_archive",
|
|
18818
19654
|
execute: async () => {
|
|
18819
|
-
const archiveDir =
|
|
18820
|
-
await
|
|
19655
|
+
const archiveDir = path15.dirname(archivePath);
|
|
19656
|
+
await fs12.mkdir(archiveDir, { recursive: true });
|
|
18821
19657
|
await tar.create({
|
|
18822
19658
|
gzip: true,
|
|
18823
19659
|
file: archivePath,
|
|
@@ -18825,7 +19661,7 @@ var CaptureTreeSaga = class extends GitSaga {
|
|
|
18825
19661
|
}, existingFiles);
|
|
18826
19662
|
},
|
|
18827
19663
|
rollback: async () => {
|
|
18828
|
-
await
|
|
19664
|
+
await fs12.rm(archivePath, { force: true }).catch(() => {
|
|
18829
19665
|
});
|
|
18830
19666
|
}
|
|
18831
19667
|
});
|
|
@@ -18925,9 +19761,9 @@ var ApplyTreeSaga = class extends GitSaga {
|
|
|
18925
19761
|
const filesToExtract = changes.filter((c) => c.status !== "D").map((c) => c.path);
|
|
18926
19762
|
await this.readOnlyStep("backup_existing_files", async () => {
|
|
18927
19763
|
for (const filePath of filesToExtract) {
|
|
18928
|
-
const fullPath =
|
|
19764
|
+
const fullPath = path15.join(baseDir, filePath);
|
|
18929
19765
|
try {
|
|
18930
|
-
const content = await
|
|
19766
|
+
const content = await fs12.readFile(fullPath);
|
|
18931
19767
|
this.fileBackups.set(filePath, content);
|
|
18932
19768
|
} catch {
|
|
18933
19769
|
}
|
|
@@ -18944,16 +19780,16 @@ var ApplyTreeSaga = class extends GitSaga {
|
|
|
18944
19780
|
},
|
|
18945
19781
|
rollback: async () => {
|
|
18946
19782
|
for (const filePath of this.extractedFiles) {
|
|
18947
|
-
const fullPath =
|
|
19783
|
+
const fullPath = path15.join(baseDir, filePath);
|
|
18948
19784
|
const backup = this.fileBackups.get(filePath);
|
|
18949
19785
|
if (backup) {
|
|
18950
|
-
const dir =
|
|
18951
|
-
await
|
|
19786
|
+
const dir = path15.dirname(fullPath);
|
|
19787
|
+
await fs12.mkdir(dir, { recursive: true }).catch(() => {
|
|
18952
19788
|
});
|
|
18953
|
-
await
|
|
19789
|
+
await fs12.writeFile(fullPath, backup).catch(() => {
|
|
18954
19790
|
});
|
|
18955
19791
|
} else {
|
|
18956
|
-
await
|
|
19792
|
+
await fs12.rm(fullPath, { force: true }).catch(() => {
|
|
18957
19793
|
});
|
|
18958
19794
|
}
|
|
18959
19795
|
}
|
|
@@ -18961,10 +19797,10 @@ var ApplyTreeSaga = class extends GitSaga {
|
|
|
18961
19797
|
});
|
|
18962
19798
|
}
|
|
18963
19799
|
for (const change of changes.filter((c) => c.status === "D")) {
|
|
18964
|
-
const fullPath =
|
|
19800
|
+
const fullPath = path15.join(baseDir, change.path);
|
|
18965
19801
|
const backupContent = await this.readOnlyStep(`backup_${change.path}`, async () => {
|
|
18966
19802
|
try {
|
|
18967
|
-
return await
|
|
19803
|
+
return await fs12.readFile(fullPath);
|
|
18968
19804
|
} catch {
|
|
18969
19805
|
return null;
|
|
18970
19806
|
}
|
|
@@ -18972,15 +19808,15 @@ var ApplyTreeSaga = class extends GitSaga {
|
|
|
18972
19808
|
await this.step({
|
|
18973
19809
|
name: `delete_${change.path}`,
|
|
18974
19810
|
execute: async () => {
|
|
18975
|
-
await
|
|
19811
|
+
await fs12.rm(fullPath, { force: true });
|
|
18976
19812
|
this.log.debug(`Deleted file: ${change.path}`);
|
|
18977
19813
|
},
|
|
18978
19814
|
rollback: async () => {
|
|
18979
19815
|
if (backupContent) {
|
|
18980
|
-
const dir =
|
|
18981
|
-
await
|
|
19816
|
+
const dir = path15.dirname(fullPath);
|
|
19817
|
+
await fs12.mkdir(dir, { recursive: true }).catch(() => {
|
|
18982
19818
|
});
|
|
18983
|
-
await
|
|
19819
|
+
await fs12.writeFile(fullPath, backupContent).catch(() => {
|
|
18984
19820
|
});
|
|
18985
19821
|
}
|
|
18986
19822
|
}
|
|
@@ -19003,18 +19839,18 @@ var ApplySnapshotSaga = class extends Saga {
|
|
|
19003
19839
|
archivePath = null;
|
|
19004
19840
|
async execute(input) {
|
|
19005
19841
|
const { snapshot, repositoryPath, apiClient, taskId, runId } = input;
|
|
19006
|
-
const tmpDir = (0,
|
|
19842
|
+
const tmpDir = (0, import_node_path7.join)(repositoryPath, ".posthog", "tmp");
|
|
19007
19843
|
if (!snapshot.archiveUrl) {
|
|
19008
19844
|
throw new Error("Cannot apply snapshot: no archive URL");
|
|
19009
19845
|
}
|
|
19010
19846
|
const archiveUrl = snapshot.archiveUrl;
|
|
19011
19847
|
await this.step({
|
|
19012
19848
|
name: "create_tmp_dir",
|
|
19013
|
-
execute: () => (0,
|
|
19849
|
+
execute: () => (0, import_promises4.mkdir)(tmpDir, { recursive: true }),
|
|
19014
19850
|
rollback: async () => {
|
|
19015
19851
|
}
|
|
19016
19852
|
});
|
|
19017
|
-
const archivePath = (0,
|
|
19853
|
+
const archivePath = (0, import_node_path7.join)(tmpDir, `${snapshot.treeHash}.tar.gz`);
|
|
19018
19854
|
this.archivePath = archivePath;
|
|
19019
19855
|
await this.step({
|
|
19020
19856
|
name: "download_archive",
|
|
@@ -19029,11 +19865,18 @@ var ApplySnapshotSaga = class extends Saga {
|
|
|
19029
19865
|
}
|
|
19030
19866
|
const base64Content = Buffer.from(arrayBuffer).toString("utf-8");
|
|
19031
19867
|
const binaryContent = Buffer.from(base64Content, "base64");
|
|
19032
|
-
await (0,
|
|
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
|
+
});
|
|
19033
19876
|
},
|
|
19034
19877
|
rollback: async () => {
|
|
19035
19878
|
if (this.archivePath) {
|
|
19036
|
-
await (0,
|
|
19879
|
+
await (0, import_promises4.rm)(this.archivePath, { force: true }).catch(() => {
|
|
19037
19880
|
});
|
|
19038
19881
|
}
|
|
19039
19882
|
}
|
|
@@ -19049,7 +19892,7 @@ var ApplySnapshotSaga = class extends Saga {
|
|
|
19049
19892
|
if (!applyResult.success) {
|
|
19050
19893
|
throw new Error(`Failed to apply tree: ${applyResult.error}`);
|
|
19051
19894
|
}
|
|
19052
|
-
await (0,
|
|
19895
|
+
await (0, import_promises4.rm)(this.archivePath, { force: true }).catch(() => {
|
|
19053
19896
|
});
|
|
19054
19897
|
this.log.info("Tree snapshot applied", {
|
|
19055
19898
|
treeHash: snapshot.treeHash,
|
|
@@ -19062,8 +19905,8 @@ var ApplySnapshotSaga = class extends Saga {
|
|
|
19062
19905
|
|
|
19063
19906
|
// src/sagas/capture-tree-saga.ts
|
|
19064
19907
|
var import_node_fs4 = require("fs");
|
|
19065
|
-
var
|
|
19066
|
-
var
|
|
19908
|
+
var import_promises5 = require("fs/promises");
|
|
19909
|
+
var import_node_path8 = require("path");
|
|
19067
19910
|
var CaptureTreeSaga2 = class extends Saga {
|
|
19068
19911
|
sagaName = "CaptureTreeSaga";
|
|
19069
19912
|
async execute(input) {
|
|
@@ -19075,14 +19918,14 @@ var CaptureTreeSaga2 = class extends Saga {
|
|
|
19075
19918
|
taskId,
|
|
19076
19919
|
runId
|
|
19077
19920
|
} = input;
|
|
19078
|
-
const tmpDir = (0,
|
|
19079
|
-
if ((0, import_node_fs4.existsSync)((0,
|
|
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"))) {
|
|
19080
19923
|
this.log.warn(
|
|
19081
19924
|
"Repository has submodules - snapshot may not capture submodule state"
|
|
19082
19925
|
);
|
|
19083
19926
|
}
|
|
19084
19927
|
const shouldArchive = !!apiClient;
|
|
19085
|
-
const archivePath = shouldArchive ? (0,
|
|
19928
|
+
const archivePath = shouldArchive ? (0, import_node_path8.join)(tmpDir, `tree-${Date.now()}.tar.gz`) : void 0;
|
|
19086
19929
|
const gitCaptureSaga = new CaptureTreeSaga(this.log);
|
|
19087
19930
|
const captureResult = await gitCaptureSaga.run({
|
|
19088
19931
|
baseDir: repositoryPath,
|
|
@@ -19112,7 +19955,7 @@ var CaptureTreeSaga2 = class extends Saga {
|
|
|
19112
19955
|
runId
|
|
19113
19956
|
);
|
|
19114
19957
|
} finally {
|
|
19115
|
-
await (0,
|
|
19958
|
+
await (0, import_promises5.rm)(createdArchivePath, { force: true }).catch(() => {
|
|
19116
19959
|
});
|
|
19117
19960
|
}
|
|
19118
19961
|
}
|
|
@@ -19136,8 +19979,10 @@ var CaptureTreeSaga2 = class extends Saga {
|
|
|
19136
19979
|
const archiveUrl = await this.step({
|
|
19137
19980
|
name: "upload_archive",
|
|
19138
19981
|
execute: async () => {
|
|
19139
|
-
const archiveContent = await (0,
|
|
19982
|
+
const archiveContent = await (0, import_promises5.readFile)(archivePath);
|
|
19140
19983
|
const base64Content = archiveContent.toString("base64");
|
|
19984
|
+
const snapshotBytes = archiveContent.byteLength;
|
|
19985
|
+
const snapshotWireBytes = Buffer.byteLength(base64Content, "utf-8");
|
|
19141
19986
|
const artifacts = await apiClient.uploadTaskArtifacts(taskId, runId, [
|
|
19142
19987
|
{
|
|
19143
19988
|
name: `trees/${treeHash}.tar.gz`,
|
|
@@ -19146,17 +19991,22 @@ var CaptureTreeSaga2 = class extends Saga {
|
|
|
19146
19991
|
content_type: "application/gzip"
|
|
19147
19992
|
}
|
|
19148
19993
|
]);
|
|
19149
|
-
|
|
19994
|
+
const uploadedArtifact = artifacts[0];
|
|
19995
|
+
if (uploadedArtifact?.storage_path) {
|
|
19150
19996
|
this.log.info("Tree archive uploaded", {
|
|
19151
|
-
storagePath:
|
|
19152
|
-
treeHash
|
|
19997
|
+
storagePath: uploadedArtifact.storage_path,
|
|
19998
|
+
treeHash,
|
|
19999
|
+
snapshotBytes,
|
|
20000
|
+
snapshotWireBytes,
|
|
20001
|
+
totalBytes: snapshotBytes,
|
|
20002
|
+
totalWireBytes: snapshotWireBytes
|
|
19153
20003
|
});
|
|
19154
|
-
return
|
|
20004
|
+
return uploadedArtifact.storage_path;
|
|
19155
20005
|
}
|
|
19156
20006
|
return void 0;
|
|
19157
20007
|
},
|
|
19158
20008
|
rollback: async () => {
|
|
19159
|
-
await (0,
|
|
20009
|
+
await (0, import_promises5.rm)(archivePath, { force: true }).catch(() => {
|
|
19160
20010
|
});
|
|
19161
20011
|
}
|
|
19162
20012
|
});
|
|
@@ -19284,6 +20134,10 @@ var ResumeSaga = class extends Saga {
|
|
|
19284
20134
|
"find_snapshot",
|
|
19285
20135
|
() => Promise.resolve(this.findLatestTreeSnapshot(entries))
|
|
19286
20136
|
);
|
|
20137
|
+
const latestGitCheckpoint = await this.readOnlyStep(
|
|
20138
|
+
"find_git_checkpoint",
|
|
20139
|
+
() => Promise.resolve(this.findLatestGitCheckpoint(entries))
|
|
20140
|
+
);
|
|
19287
20141
|
let snapshotApplied = false;
|
|
19288
20142
|
if (latestSnapshot?.archiveUrl && repositoryPath) {
|
|
19289
20143
|
this.log.info("Found tree snapshot", {
|
|
@@ -19356,6 +20210,7 @@ var ResumeSaga = class extends Saga {
|
|
|
19356
20210
|
return {
|
|
19357
20211
|
conversation,
|
|
19358
20212
|
latestSnapshot,
|
|
20213
|
+
latestGitCheckpoint,
|
|
19359
20214
|
snapshotApplied,
|
|
19360
20215
|
interrupted: latestSnapshot?.interrupted ?? false,
|
|
19361
20216
|
lastDevice,
|
|
@@ -19366,6 +20221,7 @@ var ResumeSaga = class extends Saga {
|
|
|
19366
20221
|
return {
|
|
19367
20222
|
conversation: [],
|
|
19368
20223
|
latestSnapshot: null,
|
|
20224
|
+
latestGitCheckpoint: null,
|
|
19369
20225
|
snapshotApplied: false,
|
|
19370
20226
|
interrupted: false,
|
|
19371
20227
|
logEntryCount: 0
|
|
@@ -19386,6 +20242,20 @@ var ResumeSaga = class extends Saga {
|
|
|
19386
20242
|
}
|
|
19387
20243
|
return null;
|
|
19388
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
|
+
}
|
|
19389
20259
|
findLastDeviceInfo(entries) {
|
|
19390
20260
|
for (let i2 = entries.length - 1; i2 >= 0; i2--) {
|
|
19391
20261
|
const entry = entries[i2];
|
|
@@ -19534,6 +20404,7 @@ async function resumeFromLog(config) {
|
|
|
19534
20404
|
return {
|
|
19535
20405
|
conversation: result.data.conversation,
|
|
19536
20406
|
latestSnapshot: result.data.latestSnapshot,
|
|
20407
|
+
latestGitCheckpoint: result.data.latestGitCheckpoint,
|
|
19537
20408
|
snapshotApplied: result.data.snapshotApplied,
|
|
19538
20409
|
interrupted: result.data.interrupted,
|
|
19539
20410
|
lastDevice: result.data.lastDevice,
|
|
@@ -19574,8 +20445,8 @@ ${toolSummary}`);
|
|
|
19574
20445
|
|
|
19575
20446
|
// src/session-log-writer.ts
|
|
19576
20447
|
var import_node_fs5 = __toESM(require("fs"), 1);
|
|
19577
|
-
var
|
|
19578
|
-
var
|
|
20448
|
+
var import_promises6 = __toESM(require("fs/promises"), 1);
|
|
20449
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
19579
20450
|
var SessionLogWriter = class _SessionLogWriter {
|
|
19580
20451
|
static FLUSH_DEBOUNCE_MS = 500;
|
|
19581
20452
|
static FLUSH_MAX_INTERVAL_MS = 5e3;
|
|
@@ -19611,7 +20482,7 @@ var SessionLogWriter = class _SessionLogWriter {
|
|
|
19611
20482
|
this.sessions.set(sessionId, { context, currentTurnMessages: [] });
|
|
19612
20483
|
this.lastFlushAttemptTime.set(sessionId, Date.now());
|
|
19613
20484
|
if (this.localCachePath) {
|
|
19614
|
-
const sessionDir =
|
|
20485
|
+
const sessionDir = import_node_path9.default.join(
|
|
19615
20486
|
this.localCachePath,
|
|
19616
20487
|
"sessions",
|
|
19617
20488
|
context.runId
|
|
@@ -19869,7 +20740,7 @@ var SessionLogWriter = class _SessionLogWriter {
|
|
|
19869
20740
|
if (!this.localCachePath) return;
|
|
19870
20741
|
const session = this.sessions.get(sessionId);
|
|
19871
20742
|
if (!session) return;
|
|
19872
|
-
const logPath =
|
|
20743
|
+
const logPath = import_node_path9.default.join(
|
|
19873
20744
|
this.localCachePath,
|
|
19874
20745
|
"sessions",
|
|
19875
20746
|
session.context.runId,
|
|
@@ -19888,17 +20759,17 @@ var SessionLogWriter = class _SessionLogWriter {
|
|
|
19888
20759
|
}
|
|
19889
20760
|
}
|
|
19890
20761
|
static async cleanupOldSessions(localCachePath) {
|
|
19891
|
-
const sessionsDir =
|
|
20762
|
+
const sessionsDir = import_node_path9.default.join(localCachePath, "sessions");
|
|
19892
20763
|
let deleted = 0;
|
|
19893
20764
|
try {
|
|
19894
|
-
const entries = await
|
|
20765
|
+
const entries = await import_promises6.default.readdir(sessionsDir);
|
|
19895
20766
|
const now = Date.now();
|
|
19896
20767
|
for (const entry of entries) {
|
|
19897
|
-
const entryPath =
|
|
20768
|
+
const entryPath = import_node_path9.default.join(sessionsDir, entry);
|
|
19898
20769
|
try {
|
|
19899
|
-
const stats = await
|
|
20770
|
+
const stats = await import_promises6.default.stat(entryPath);
|
|
19900
20771
|
if (stats.isDirectory() && now - stats.birthtimeMs > _SessionLogWriter.SESSIONS_MAX_AGE_MS) {
|
|
19901
|
-
await
|
|
20772
|
+
await import_promises6.default.rm(entryPath, { recursive: true, force: true });
|
|
19902
20773
|
deleted++;
|
|
19903
20774
|
}
|
|
19904
20775
|
} catch {
|
|
@@ -19976,6 +20847,14 @@ var httpHeaderSchema = import_v4.z.object({
|
|
|
19976
20847
|
name: import_v4.z.string(),
|
|
19977
20848
|
value: import_v4.z.string()
|
|
19978
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
|
+
});
|
|
19979
20858
|
var remoteMcpServerSchema = import_v4.z.object({
|
|
19980
20859
|
type: import_v4.z.enum(["http", "sse"]),
|
|
19981
20860
|
name: import_v4.z.string().min(1, "MCP server name is required"),
|
|
@@ -20027,13 +20906,16 @@ var setConfigOptionParamsSchema = import_v4.z.object({
|
|
|
20027
20906
|
var refreshSessionParamsSchema = import_v4.z.object({
|
|
20028
20907
|
mcpServers: mcpServersSchema
|
|
20029
20908
|
});
|
|
20909
|
+
var closeParamsSchema = import_v4.z.object({
|
|
20910
|
+
localGitState: handoffLocalGitStateSchema.optional()
|
|
20911
|
+
}).optional();
|
|
20030
20912
|
var commandParamsSchemas = {
|
|
20031
20913
|
user_message: userMessageParamsSchema,
|
|
20032
20914
|
"posthog/user_message": userMessageParamsSchema,
|
|
20033
20915
|
cancel: import_v4.z.object({}).optional(),
|
|
20034
20916
|
"posthog/cancel": import_v4.z.object({}).optional(),
|
|
20035
|
-
close:
|
|
20036
|
-
"posthog/close":
|
|
20917
|
+
close: closeParamsSchema,
|
|
20918
|
+
"posthog/close": closeParamsSchema,
|
|
20037
20919
|
permission_response: permissionResponseParamsSchema,
|
|
20038
20920
|
"posthog/permission_response": permissionResponseParamsSchema,
|
|
20039
20921
|
set_config_option: setConfigOptionParamsSchema,
|
|
@@ -20355,7 +21237,7 @@ var AgentServer = class {
|
|
|
20355
21237
|
return app;
|
|
20356
21238
|
}
|
|
20357
21239
|
async start() {
|
|
20358
|
-
await new Promise((
|
|
21240
|
+
await new Promise((resolve7) => {
|
|
20359
21241
|
this.server = (0, import_node_server.serve)(
|
|
20360
21242
|
{
|
|
20361
21243
|
fetch: this.app.fetch,
|
|
@@ -20365,7 +21247,7 @@ var AgentServer = class {
|
|
|
20365
21247
|
this.logger.debug(
|
|
20366
21248
|
`HTTP server listening on port ${this.config.port}`
|
|
20367
21249
|
);
|
|
20368
|
-
|
|
21250
|
+
resolve7();
|
|
20369
21251
|
}
|
|
20370
21252
|
);
|
|
20371
21253
|
});
|
|
@@ -20519,6 +21401,10 @@ var AgentServer = class {
|
|
|
20519
21401
|
case POSTHOG_NOTIFICATIONS.CLOSE:
|
|
20520
21402
|
case "close": {
|
|
20521
21403
|
this.logger.debug("Close requested");
|
|
21404
|
+
const localGitState = this.extractHandoffLocalGitState(params);
|
|
21405
|
+
if (localGitState && this.session) {
|
|
21406
|
+
this.session.pendingHandoffGitState = localGitState;
|
|
21407
|
+
}
|
|
20522
21408
|
await this.cleanupSession();
|
|
20523
21409
|
return { closed: true };
|
|
20524
21410
|
}
|
|
@@ -20745,7 +21631,8 @@ var AgentServer = class {
|
|
|
20745
21631
|
deviceInfo,
|
|
20746
21632
|
logWriter,
|
|
20747
21633
|
permissionMode: initialPermissionMode,
|
|
20748
|
-
hasDesktopConnected: sseController !== null
|
|
21634
|
+
hasDesktopConnected: sseController !== null,
|
|
21635
|
+
pendingHandoffGitState: void 0
|
|
20749
21636
|
};
|
|
20750
21637
|
this.logger = new Logger({
|
|
20751
21638
|
debug: true,
|
|
@@ -21070,23 +21957,23 @@ Continue from where you left off. The user is waiting for your response.`
|
|
|
21070
21957
|
throw new Error(`Failed to download artifact ${artifact.name}`);
|
|
21071
21958
|
}
|
|
21072
21959
|
const safeName = this.getSafeArtifactName(artifact.name);
|
|
21073
|
-
const artifactDir = (0,
|
|
21960
|
+
const artifactDir = (0, import_node_path10.join)(
|
|
21074
21961
|
this.config.repositoryPath ?? "/tmp/workspace",
|
|
21075
21962
|
".posthog",
|
|
21076
21963
|
"attachments",
|
|
21077
21964
|
runId,
|
|
21078
21965
|
artifact.id ?? safeName
|
|
21079
21966
|
);
|
|
21080
|
-
await (0,
|
|
21081
|
-
const artifactPath = (0,
|
|
21082
|
-
await (0,
|
|
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));
|
|
21083
21970
|
return resourceLink((0, import_node_url2.pathToFileURL)(artifactPath).toString(), artifact.name, {
|
|
21084
21971
|
...artifact.content_type ? { mimeType: artifact.content_type } : {},
|
|
21085
21972
|
...typeof artifact.size === "number" ? { size: artifact.size } : {}
|
|
21086
21973
|
});
|
|
21087
21974
|
}
|
|
21088
21975
|
getSafeArtifactName(name2) {
|
|
21089
|
-
const baseName = (0,
|
|
21976
|
+
const baseName = (0, import_node_path10.basename)(name2).trim();
|
|
21090
21977
|
const normalizedName = baseName.replace(/[^\w.-]/g, "_");
|
|
21091
21978
|
return normalizedName.length > 0 ? normalizedName : "attachment";
|
|
21092
21979
|
}
|
|
@@ -21578,6 +22465,11 @@ ${attributionInstructions}
|
|
|
21578
22465
|
async cleanupSession() {
|
|
21579
22466
|
if (!this.session) return;
|
|
21580
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
|
+
}
|
|
21581
22473
|
try {
|
|
21582
22474
|
await this.captureTreeState();
|
|
21583
22475
|
} catch (error) {
|
|
@@ -21637,6 +22529,50 @@ ${attributionInstructions}
|
|
|
21637
22529
|
this.logger.error("Failed to capture tree state", error);
|
|
21638
22530
|
}
|
|
21639
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
|
+
}
|
|
21640
22576
|
broadcastTurnComplete(stopReason) {
|
|
21641
22577
|
if (!this.session) return;
|
|
21642
22578
|
this.broadcastEvent({
|
|
@@ -21690,8 +22626,8 @@ ${attributionInstructions}
|
|
|
21690
22626
|
options: params.options,
|
|
21691
22627
|
toolCall: params.toolCall
|
|
21692
22628
|
});
|
|
21693
|
-
return new Promise((
|
|
21694
|
-
this.pendingPermissions.set(requestId, { resolve:
|
|
22629
|
+
return new Promise((resolve7) => {
|
|
22630
|
+
this.pendingPermissions.set(requestId, { resolve: resolve7 });
|
|
21695
22631
|
});
|
|
21696
22632
|
}
|
|
21697
22633
|
resolvePermission(requestId, optionId, customInput, answers) {
|