@posthog/agent 2.3.388 → 2.3.401
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/conversion/tool-use-to-acp.js.map +1 -1
- package/dist/adapters/claude/mcp/tool-metadata.d.ts +24 -0
- package/dist/adapters/claude/mcp/tool-metadata.js +165 -0
- package/dist/adapters/claude/mcp/tool-metadata.js.map +1 -0
- package/dist/adapters/claude/tools.js.map +1 -1
- package/dist/agent.js +113 -3
- package/dist/agent.js.map +1 -1
- package/dist/handoff-checkpoint.d.ts +1 -0
- package/dist/handoff-checkpoint.js +17 -1
- package/dist/handoff-checkpoint.js.map +1 -1
- package/dist/index.d.ts +7 -9
- package/dist/index.js.map +1 -1
- package/dist/posthog-api.js +5 -1
- package/dist/posthog-api.js.map +1 -1
- package/dist/server/agent-server.js +258 -101
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +248 -98
- package/dist/server/bin.cjs.map +1 -1
- package/dist/tree-tracker.js +128 -97
- package/dist/tree-tracker.js.map +1 -1
- package/package.json +7 -3
- package/src/adapters/claude/claude-agent.ts +5 -0
- package/src/adapters/claude/mcp/tool-metadata.test.ts +93 -0
- package/src/adapters/claude/mcp/tool-metadata.ts +33 -0
- package/src/adapters/claude/permissions/permission-handlers.test.ts +165 -0
- package/src/adapters/claude/permissions/permission-handlers.ts +105 -0
- package/src/adapters/claude/session/instructions.ts +9 -1
- package/src/adapters/claude/types.ts +2 -0
- package/src/handoff-checkpoint.test.ts +1 -0
- package/src/handoff-checkpoint.ts +17 -1
- package/src/sagas/apply-snapshot-saga.test.ts +1 -0
- package/src/sagas/apply-snapshot-saga.ts +68 -54
- package/src/sagas/capture-tree-saga.test.ts +18 -0
- package/src/sagas/capture-tree-saga.ts +64 -49
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
1
|
+
import { mkdir, readdir, rm, rmdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { ApplyTreeSaga as GitApplyTreeSaga } from "@posthog/git/sagas/tree";
|
|
4
4
|
import { Saga } from "@posthog/shared";
|
|
@@ -37,64 +37,78 @@ export class ApplySnapshotSaga extends Saga<
|
|
|
37
37
|
|
|
38
38
|
const archiveUrl = snapshot.archiveUrl;
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
try {
|
|
41
|
+
await this.step({
|
|
42
|
+
name: "create_tmp_dir",
|
|
43
|
+
execute: () => mkdir(tmpDir, { recursive: true }),
|
|
44
|
+
rollback: async () => {},
|
|
45
|
+
});
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
47
|
+
const archivePath = join(tmpDir, `${snapshot.treeHash}.tar.gz`);
|
|
48
|
+
this.archivePath = archivePath;
|
|
49
|
+
await this.step({
|
|
50
|
+
name: "download_archive",
|
|
51
|
+
execute: async () => {
|
|
52
|
+
const arrayBuffer = await apiClient.downloadArtifact(
|
|
53
|
+
taskId,
|
|
54
|
+
runId,
|
|
55
|
+
archiveUrl,
|
|
56
|
+
);
|
|
57
|
+
if (!arrayBuffer) {
|
|
58
|
+
throw new Error("Failed to download archive");
|
|
59
|
+
}
|
|
60
|
+
const base64Content = Buffer.from(arrayBuffer).toString("utf-8");
|
|
61
|
+
const binaryContent = Buffer.from(base64Content, "base64");
|
|
62
|
+
await writeFile(archivePath, binaryContent);
|
|
63
|
+
this.log.info("Tree archive downloaded", {
|
|
64
|
+
treeHash: snapshot.treeHash,
|
|
65
|
+
snapshotBytes: binaryContent.byteLength,
|
|
66
|
+
snapshotWireBytes: arrayBuffer.byteLength,
|
|
67
|
+
totalBytes: binaryContent.byteLength,
|
|
68
|
+
totalWireBytes: arrayBuffer.byteLength,
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
rollback: async () => {
|
|
72
|
+
if (this.archivePath) {
|
|
73
|
+
await rm(this.archivePath, { force: true }).catch(() => {});
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
});
|
|
76
77
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
78
|
+
const gitApplySaga = new GitApplyTreeSaga(this.log);
|
|
79
|
+
const applyResult = await gitApplySaga.run({
|
|
80
|
+
baseDir: repositoryPath,
|
|
81
|
+
treeHash: snapshot.treeHash,
|
|
82
|
+
baseCommit: snapshot.baseCommit,
|
|
83
|
+
changes: snapshot.changes,
|
|
84
|
+
archivePath: this.archivePath,
|
|
85
|
+
});
|
|
85
86
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
if (!applyResult.success) {
|
|
88
|
+
throw new Error(`Failed to apply tree: ${applyResult.error}`);
|
|
89
|
+
}
|
|
89
90
|
|
|
90
|
-
|
|
91
|
+
this.log.info("Tree snapshot applied", {
|
|
92
|
+
treeHash: snapshot.treeHash,
|
|
93
|
+
totalChanges: snapshot.changes.length,
|
|
94
|
+
deletedFiles: snapshot.changes.filter((c) => c.status === "D").length,
|
|
95
|
+
});
|
|
91
96
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
return { treeHash: snapshot.treeHash };
|
|
98
|
+
} finally {
|
|
99
|
+
if (this.archivePath) {
|
|
100
|
+
await rm(this.archivePath, { force: true }).catch(() => {});
|
|
101
|
+
}
|
|
102
|
+
await this.removeTmpDirIfEmpty(tmpDir);
|
|
103
|
+
this.archivePath = null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
97
106
|
|
|
98
|
-
|
|
107
|
+
private async removeTmpDirIfEmpty(tmpDir: string): Promise<void> {
|
|
108
|
+
const entries = await readdir(tmpDir).catch(() => null);
|
|
109
|
+
if (!entries || entries.length > 0) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
await rmdir(tmpDir).catch(() => {});
|
|
99
113
|
}
|
|
100
114
|
}
|
|
@@ -366,6 +366,24 @@ describe("CaptureTreeSaga", () => {
|
|
|
366
366
|
const indexFiles = files.filter((f: string) => f.startsWith("index-"));
|
|
367
367
|
expect(indexFiles).toHaveLength(0);
|
|
368
368
|
});
|
|
369
|
+
|
|
370
|
+
it("cleans up uploaded tree archive and tmp dir on success", async () => {
|
|
371
|
+
const mockApiClient = createMockApiClient();
|
|
372
|
+
|
|
373
|
+
await repo.writeFile("new.ts", "content");
|
|
374
|
+
|
|
375
|
+
const saga = new CaptureTreeSaga(mockLogger);
|
|
376
|
+
const result = await saga.run({
|
|
377
|
+
repositoryPath: repo.path,
|
|
378
|
+
taskId: "task-1",
|
|
379
|
+
runId: "run-1",
|
|
380
|
+
lastTreeHash: null,
|
|
381
|
+
apiClient: mockApiClient,
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
expect(result.success).toBe(true);
|
|
385
|
+
expect(repo.exists(".posthog/tmp")).toBe(false);
|
|
386
|
+
});
|
|
369
387
|
});
|
|
370
388
|
|
|
371
389
|
describe("git state isolation", () => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { readFile, rm } from "node:fs/promises";
|
|
2
|
+
import { readdir, readFile, rm, rmdir } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { CaptureTreeSaga as GitCaptureTreeSaga } from "@posthog/git/sagas/tree";
|
|
5
5
|
import { Saga } from "@posthog/shared";
|
|
@@ -45,60 +45,67 @@ export class CaptureTreeSaga extends Saga<CaptureTreeInput, CaptureTreeOutput> {
|
|
|
45
45
|
? join(tmpDir, `tree-${Date.now()}.tar.gz`)
|
|
46
46
|
: undefined;
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
try {
|
|
49
|
+
const gitCaptureSaga = new GitCaptureTreeSaga(this.log);
|
|
50
|
+
const captureResult = await gitCaptureSaga.run({
|
|
51
|
+
baseDir: repositoryPath,
|
|
52
|
+
lastTreeHash,
|
|
53
|
+
archivePath,
|
|
54
|
+
});
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
if (!captureResult.success) {
|
|
57
|
+
throw new Error(`Failed to capture tree: ${captureResult.error}`);
|
|
58
|
+
}
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (!changed || !gitSnapshot) {
|
|
66
|
-
this.log.debug("No changes since last capture", { lastTreeHash });
|
|
67
|
-
return { snapshot: null, newTreeHash: lastTreeHash };
|
|
68
|
-
}
|
|
60
|
+
const {
|
|
61
|
+
snapshot: gitSnapshot,
|
|
62
|
+
archivePath: createdArchivePath,
|
|
63
|
+
changed,
|
|
64
|
+
} = captureResult.data;
|
|
69
65
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
archiveUrl = await this.uploadArchive(
|
|
74
|
-
createdArchivePath,
|
|
75
|
-
gitSnapshot.treeHash,
|
|
76
|
-
apiClient,
|
|
77
|
-
taskId,
|
|
78
|
-
runId,
|
|
79
|
-
);
|
|
80
|
-
} finally {
|
|
81
|
-
await rm(createdArchivePath, { force: true }).catch(() => {});
|
|
66
|
+
if (!changed || !gitSnapshot) {
|
|
67
|
+
this.log.debug("No changes since last capture", { lastTreeHash });
|
|
68
|
+
return { snapshot: null, newTreeHash: lastTreeHash };
|
|
82
69
|
}
|
|
83
|
-
}
|
|
84
70
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
});
|
|
71
|
+
let archiveUrl: string | undefined;
|
|
72
|
+
if (apiClient && createdArchivePath) {
|
|
73
|
+
try {
|
|
74
|
+
archiveUrl = await this.uploadArchive(
|
|
75
|
+
createdArchivePath,
|
|
76
|
+
gitSnapshot.treeHash,
|
|
77
|
+
apiClient,
|
|
78
|
+
taskId,
|
|
79
|
+
runId,
|
|
80
|
+
);
|
|
81
|
+
} finally {
|
|
82
|
+
await rm(createdArchivePath, { force: true }).catch(() => {});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
100
85
|
|
|
101
|
-
|
|
86
|
+
const snapshot: TreeSnapshot = {
|
|
87
|
+
treeHash: gitSnapshot.treeHash,
|
|
88
|
+
baseCommit: gitSnapshot.baseCommit,
|
|
89
|
+
changes: gitSnapshot.changes,
|
|
90
|
+
timestamp: gitSnapshot.timestamp,
|
|
91
|
+
interrupted,
|
|
92
|
+
archiveUrl,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
this.log.info("Tree captured", {
|
|
96
|
+
treeHash: snapshot.treeHash,
|
|
97
|
+
changes: snapshot.changes.length,
|
|
98
|
+
interrupted,
|
|
99
|
+
archiveUrl,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return { snapshot, newTreeHash: snapshot.treeHash };
|
|
103
|
+
} finally {
|
|
104
|
+
if (archivePath) {
|
|
105
|
+
await rm(archivePath, { force: true }).catch(() => {});
|
|
106
|
+
}
|
|
107
|
+
await this.removeTmpDirIfEmpty(tmpDir);
|
|
108
|
+
}
|
|
102
109
|
}
|
|
103
110
|
|
|
104
111
|
private async uploadArchive(
|
|
@@ -147,4 +154,12 @@ export class CaptureTreeSaga extends Saga<CaptureTreeInput, CaptureTreeOutput> {
|
|
|
147
154
|
|
|
148
155
|
return archiveUrl;
|
|
149
156
|
}
|
|
157
|
+
|
|
158
|
+
private async removeTmpDirIfEmpty(tmpDir: string): Promise<void> {
|
|
159
|
+
const entries = await readdir(tmpDir).catch(() => null);
|
|
160
|
+
if (!entries || entries.length > 0) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
await rmdir(tmpDir).catch(() => {});
|
|
164
|
+
}
|
|
150
165
|
}
|