@posthog/agent 2.0.0 → 2.0.2
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/LICENSE +1 -1
- package/README.md +221 -219
- package/dist/adapters/claude/conversion/tool-use-to-acp.d.ts +21 -0
- package/dist/adapters/claude/conversion/tool-use-to-acp.js +547 -0
- package/dist/adapters/claude/conversion/tool-use-to-acp.js.map +1 -0
- package/dist/adapters/claude/permissions/permission-options.d.ts +13 -0
- package/dist/adapters/claude/permissions/permission-options.js +117 -0
- package/dist/adapters/claude/permissions/permission-options.js.map +1 -0
- package/dist/adapters/claude/questions/utils.d.ts +132 -0
- package/dist/adapters/claude/questions/utils.js +63 -0
- package/dist/adapters/claude/questions/utils.js.map +1 -0
- package/dist/adapters/claude/tools.d.ts +18 -0
- package/dist/adapters/claude/tools.js +95 -0
- package/dist/adapters/claude/tools.js.map +1 -0
- package/dist/agent-DBQY1BfC.d.ts +123 -0
- package/dist/agent.d.ts +5 -0
- package/dist/agent.js +3656 -0
- package/dist/agent.js.map +1 -0
- package/dist/claude-cli/cli.js +3695 -2746
- package/dist/claude-cli/vendor/ripgrep/COPYING +3 -0
- package/dist/claude-cli/vendor/ripgrep/arm64-darwin/rg +0 -0
- package/dist/claude-cli/vendor/ripgrep/arm64-darwin/ripgrep.node +0 -0
- package/dist/claude-cli/vendor/ripgrep/arm64-linux/rg +0 -0
- package/dist/claude-cli/vendor/ripgrep/arm64-linux/ripgrep.node +0 -0
- package/dist/claude-cli/vendor/ripgrep/x64-darwin/rg +0 -0
- package/dist/claude-cli/vendor/ripgrep/x64-darwin/ripgrep.node +0 -0
- package/dist/claude-cli/vendor/ripgrep/x64-linux/rg +0 -0
- package/dist/claude-cli/vendor/ripgrep/x64-linux/ripgrep.node +0 -0
- package/dist/claude-cli/vendor/ripgrep/x64-win32/rg.exe +0 -0
- package/dist/claude-cli/vendor/ripgrep/x64-win32/ripgrep.node +0 -0
- package/dist/gateway-models.d.ts +24 -0
- package/dist/gateway-models.js +93 -0
- package/dist/gateway-models.js.map +1 -0
- package/dist/index.d.ts +170 -1157
- package/dist/index.js +9373 -5135
- package/dist/index.js.map +1 -1
- package/dist/logger-DDBiMOOD.d.ts +24 -0
- package/dist/posthog-api.d.ts +40 -0
- package/dist/posthog-api.js +175 -0
- package/dist/posthog-api.js.map +1 -0
- package/dist/server/agent-server.d.ts +41 -0
- package/dist/server/agent-server.js +10503 -0
- package/dist/server/agent-server.js.map +1 -0
- package/dist/server/bin.d.ts +1 -0
- package/dist/server/bin.js +10558 -0
- package/dist/server/bin.js.map +1 -0
- package/dist/types.d.ts +129 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/package.json +65 -13
- package/src/acp-extensions.ts +98 -16
- package/src/adapters/acp-connection.ts +494 -0
- package/src/adapters/base-acp-agent.ts +150 -0
- package/src/adapters/claude/claude-agent.ts +596 -0
- package/src/adapters/claude/conversion/acp-to-sdk.ts +102 -0
- package/src/adapters/claude/conversion/sdk-to-acp.ts +571 -0
- package/src/adapters/claude/conversion/tool-use-to-acp.ts +618 -0
- package/src/adapters/claude/hooks.ts +64 -0
- package/src/adapters/claude/mcp/tool-metadata.ts +102 -0
- package/src/adapters/claude/permissions/permission-handlers.ts +433 -0
- package/src/adapters/claude/permissions/permission-options.ts +103 -0
- package/src/adapters/claude/plan/utils.ts +56 -0
- package/src/adapters/claude/questions/utils.ts +92 -0
- package/src/adapters/claude/session/commands.ts +38 -0
- package/src/adapters/claude/session/mcp-config.ts +37 -0
- package/src/adapters/claude/session/models.ts +12 -0
- package/src/adapters/claude/session/options.ts +236 -0
- package/src/adapters/claude/tool-meta.ts +143 -0
- package/src/adapters/claude/tools.ts +53 -688
- package/src/adapters/claude/types.ts +61 -0
- package/src/adapters/codex/spawn.ts +130 -0
- package/src/agent.ts +96 -587
- package/src/execution-mode.ts +43 -0
- package/src/gateway-models.ts +135 -0
- package/src/index.ts +79 -0
- package/src/otel-log-writer.test.ts +105 -0
- package/src/otel-log-writer.ts +94 -0
- package/src/posthog-api.ts +75 -235
- package/src/resume.ts +115 -0
- package/src/sagas/apply-snapshot-saga.test.ts +690 -0
- package/src/sagas/apply-snapshot-saga.ts +88 -0
- package/src/sagas/capture-tree-saga.test.ts +892 -0
- package/src/sagas/capture-tree-saga.ts +141 -0
- package/src/sagas/resume-saga.test.ts +558 -0
- package/src/sagas/resume-saga.ts +332 -0
- package/src/sagas/test-fixtures.ts +250 -0
- package/src/server/agent-server.test.ts +220 -0
- package/src/server/agent-server.ts +748 -0
- package/src/server/bin.ts +88 -0
- package/src/server/jwt.ts +65 -0
- package/src/server/schemas.ts +47 -0
- package/src/server/types.ts +13 -0
- package/src/server/utils/retry.test.ts +122 -0
- package/src/server/utils/retry.ts +61 -0
- package/src/server/utils/sse-parser.test.ts +93 -0
- package/src/server/utils/sse-parser.ts +46 -0
- package/src/session-log-writer.test.ts +140 -0
- package/src/session-log-writer.ts +137 -0
- package/src/test/assertions.ts +114 -0
- package/src/test/controllers/sse-controller.ts +107 -0
- package/src/test/fixtures/api.ts +111 -0
- package/src/test/fixtures/config.ts +33 -0
- package/src/test/fixtures/notifications.ts +92 -0
- package/src/test/mocks/claude-sdk.ts +251 -0
- package/src/test/mocks/msw-handlers.ts +48 -0
- package/src/test/setup.ts +114 -0
- package/src/test/wait.ts +41 -0
- package/src/tree-tracker.ts +173 -0
- package/src/types.ts +54 -137
- package/src/utils/acp-content.ts +58 -0
- package/src/utils/async-mutex.test.ts +104 -0
- package/src/utils/async-mutex.ts +31 -0
- package/src/utils/common.ts +15 -0
- package/src/utils/gateway.ts +9 -6
- package/src/utils/logger.ts +0 -30
- package/src/utils/streams.ts +220 -0
- package/CLAUDE.md +0 -331
- package/src/adapters/claude/claude.ts +0 -1947
- package/src/adapters/claude/mcp-server.ts +0 -810
- package/src/adapters/claude/utils.ts +0 -267
- package/src/adapters/connection.ts +0 -95
- package/src/file-manager.ts +0 -273
- package/src/git-manager.ts +0 -577
- package/src/schemas.ts +0 -241
- package/src/session-store.ts +0 -259
- package/src/task-manager.ts +0 -163
- package/src/todo-manager.ts +0 -180
- package/src/tools/registry.ts +0 -134
- package/src/tools/types.ts +0 -133
- package/src/utils/tapped-stream.ts +0 -60
- package/src/worktree-manager.ts +0 -974
package/src/posthog-api.ts
CHANGED
|
@@ -1,32 +1,35 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
ArtifactType,
|
|
2
3
|
PostHogAPIConfig,
|
|
3
|
-
PostHogResource,
|
|
4
4
|
StoredEntry,
|
|
5
5
|
Task,
|
|
6
|
-
TaskArtifactUploadPayload,
|
|
7
6
|
TaskRun,
|
|
8
7
|
TaskRunArtifact,
|
|
9
|
-
UrlMention,
|
|
10
8
|
} from "./types.js";
|
|
11
9
|
import { getLlmGatewayUrl } from "./utils/gateway.js";
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
export { getLlmGatewayUrl };
|
|
12
|
+
|
|
13
|
+
export interface TaskArtifactUploadPayload {
|
|
14
|
+
name: string;
|
|
15
|
+
type: ArtifactType;
|
|
16
|
+
content: string;
|
|
17
|
+
content_type?: string;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export type TaskRunUpdate = Partial<
|
|
21
21
|
Pick<
|
|
22
22
|
TaskRun,
|
|
23
|
-
|
|
23
|
+
| "status"
|
|
24
|
+
| "branch"
|
|
25
|
+
| "stage"
|
|
26
|
+
| "error_message"
|
|
27
|
+
| "output"
|
|
28
|
+
| "state"
|
|
29
|
+
| "environment"
|
|
24
30
|
>
|
|
25
31
|
>;
|
|
26
32
|
|
|
27
|
-
export type TaskCreatePayload = Pick<Task, "description"> &
|
|
28
|
-
Partial<Pick<Task, "title" | "repository" | "origin_product">>;
|
|
29
|
-
|
|
30
33
|
export class PostHogAPIClient {
|
|
31
34
|
private config: PostHogAPIConfig;
|
|
32
35
|
|
|
@@ -76,14 +79,10 @@ export class PostHogAPIClient {
|
|
|
76
79
|
return response.json();
|
|
77
80
|
}
|
|
78
81
|
|
|
79
|
-
getTeamId(): number {
|
|
82
|
+
private getTeamId(): number {
|
|
80
83
|
return this.config.projectId;
|
|
81
84
|
}
|
|
82
85
|
|
|
83
|
-
getBaseUrl(): string {
|
|
84
|
-
return this.baseUrl;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
86
|
getApiKey(): string {
|
|
88
87
|
return this.config.getApiKey();
|
|
89
88
|
}
|
|
@@ -92,60 +91,11 @@ export class PostHogAPIClient {
|
|
|
92
91
|
return getLlmGatewayUrl(this.baseUrl);
|
|
93
92
|
}
|
|
94
93
|
|
|
95
|
-
async
|
|
94
|
+
async getTask(taskId: string): Promise<Task> {
|
|
96
95
|
const teamId = this.getTeamId();
|
|
97
96
|
return this.apiRequest<Task>(`/api/projects/${teamId}/tasks/${taskId}/`);
|
|
98
97
|
}
|
|
99
98
|
|
|
100
|
-
async listTasks(filters?: {
|
|
101
|
-
repository?: string;
|
|
102
|
-
organization?: string;
|
|
103
|
-
origin_product?: string;
|
|
104
|
-
}): Promise<Task[]> {
|
|
105
|
-
const teamId = this.getTeamId();
|
|
106
|
-
const url = new URL(`${this.baseUrl}/api/projects/${teamId}/tasks/`);
|
|
107
|
-
|
|
108
|
-
if (filters) {
|
|
109
|
-
Object.entries(filters).forEach(([key, value]) => {
|
|
110
|
-
if (value) url.searchParams.append(key, value);
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const response = await this.apiRequest<PostHogApiResponse<Task>>(
|
|
115
|
-
url.pathname + url.search,
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
return response.results || [];
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
async updateTask(taskId: string, updates: Partial<Task>): Promise<Task> {
|
|
122
|
-
const teamId = this.getTeamId();
|
|
123
|
-
return this.apiRequest<Task>(`/api/projects/${teamId}/tasks/${taskId}/`, {
|
|
124
|
-
method: "PATCH",
|
|
125
|
-
body: JSON.stringify(updates),
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
async createTask(payload: TaskCreatePayload): Promise<Task> {
|
|
130
|
-
const teamId = this.getTeamId();
|
|
131
|
-
return this.apiRequest<Task>(`/api/projects/${teamId}/tasks/`, {
|
|
132
|
-
method: "POST",
|
|
133
|
-
body: JSON.stringify({
|
|
134
|
-
origin_product: "user_created",
|
|
135
|
-
...payload,
|
|
136
|
-
}),
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// TaskRun methods
|
|
141
|
-
async listTaskRuns(taskId: string): Promise<TaskRun[]> {
|
|
142
|
-
const teamId = this.getTeamId();
|
|
143
|
-
const response = await this.apiRequest<PostHogApiResponse<TaskRun>>(
|
|
144
|
-
`/api/projects/${teamId}/tasks/${taskId}/runs/`,
|
|
145
|
-
);
|
|
146
|
-
return response.results || [];
|
|
147
|
-
}
|
|
148
|
-
|
|
149
99
|
async getTaskRun(taskId: string, runId: string): Promise<TaskRun> {
|
|
150
100
|
const teamId = this.getTeamId();
|
|
151
101
|
return this.apiRequest<TaskRun>(
|
|
@@ -153,31 +103,6 @@ export class PostHogAPIClient {
|
|
|
153
103
|
);
|
|
154
104
|
}
|
|
155
105
|
|
|
156
|
-
async createTaskRun(
|
|
157
|
-
taskId: string,
|
|
158
|
-
payload?: Partial<
|
|
159
|
-
Omit<
|
|
160
|
-
TaskRun,
|
|
161
|
-
| "id"
|
|
162
|
-
| "task"
|
|
163
|
-
| "team"
|
|
164
|
-
| "created_at"
|
|
165
|
-
| "updated_at"
|
|
166
|
-
| "completed_at"
|
|
167
|
-
| "artifacts"
|
|
168
|
-
>
|
|
169
|
-
>,
|
|
170
|
-
): Promise<TaskRun> {
|
|
171
|
-
const teamId = this.getTeamId();
|
|
172
|
-
return this.apiRequest<TaskRun>(
|
|
173
|
-
`/api/projects/${teamId}/tasks/${taskId}/runs/`,
|
|
174
|
-
{
|
|
175
|
-
method: "POST",
|
|
176
|
-
body: JSON.stringify(payload || {}),
|
|
177
|
-
},
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
106
|
async updateTaskRun(
|
|
182
107
|
taskId: string,
|
|
183
108
|
runId: string,
|
|
@@ -193,21 +118,6 @@ export class PostHogAPIClient {
|
|
|
193
118
|
);
|
|
194
119
|
}
|
|
195
120
|
|
|
196
|
-
async setTaskRunOutput(
|
|
197
|
-
taskId: string,
|
|
198
|
-
runId: string,
|
|
199
|
-
output: Record<string, unknown>,
|
|
200
|
-
): Promise<TaskRun> {
|
|
201
|
-
const teamId = this.getTeamId();
|
|
202
|
-
return this.apiRequest<TaskRun>(
|
|
203
|
-
`/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/set_output/`,
|
|
204
|
-
{
|
|
205
|
-
method: "PATCH",
|
|
206
|
-
body: JSON.stringify({ output }),
|
|
207
|
-
},
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
121
|
async appendTaskRunLog(
|
|
212
122
|
taskId: string,
|
|
213
123
|
runId: string,
|
|
@@ -244,20 +154,72 @@ export class PostHogAPIClient {
|
|
|
244
154
|
return response.artifacts ?? [];
|
|
245
155
|
}
|
|
246
156
|
|
|
157
|
+
async getArtifactPresignedUrl(
|
|
158
|
+
taskId: string,
|
|
159
|
+
runId: string,
|
|
160
|
+
storagePath: string,
|
|
161
|
+
): Promise<string | null> {
|
|
162
|
+
const teamId = this.getTeamId();
|
|
163
|
+
try {
|
|
164
|
+
const response = await this.apiRequest<{
|
|
165
|
+
url: string;
|
|
166
|
+
expires_in: number;
|
|
167
|
+
}>(
|
|
168
|
+
`/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/artifacts/presign/`,
|
|
169
|
+
{
|
|
170
|
+
method: "POST",
|
|
171
|
+
body: JSON.stringify({ storage_path: storagePath }),
|
|
172
|
+
},
|
|
173
|
+
);
|
|
174
|
+
return response.url;
|
|
175
|
+
} catch {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
247
180
|
/**
|
|
248
|
-
*
|
|
249
|
-
*
|
|
181
|
+
* Download artifact content by storage path
|
|
182
|
+
* Gets a presigned URL and fetches the content
|
|
183
|
+
*/
|
|
184
|
+
async downloadArtifact(
|
|
185
|
+
taskId: string,
|
|
186
|
+
runId: string,
|
|
187
|
+
storagePath: string,
|
|
188
|
+
): Promise<ArrayBuffer | null> {
|
|
189
|
+
const url = await this.getArtifactPresignedUrl(taskId, runId, storagePath);
|
|
190
|
+
if (!url) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const response = await fetch(url);
|
|
196
|
+
if (!response.ok) {
|
|
197
|
+
throw new Error(`Failed to download artifact: ${response.status}`);
|
|
198
|
+
}
|
|
199
|
+
return response.arrayBuffer();
|
|
200
|
+
} catch {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Fetch logs for a task run via the logs API endpoint
|
|
207
|
+
* @param taskRun - The task run to fetch logs for
|
|
250
208
|
* @returns Array of stored entries, or empty array if no logs available
|
|
251
209
|
*/
|
|
252
210
|
async fetchTaskRunLogs(taskRun: TaskRun): Promise<StoredEntry[]> {
|
|
253
|
-
|
|
254
|
-
return [];
|
|
255
|
-
}
|
|
211
|
+
const teamId = this.getTeamId();
|
|
256
212
|
|
|
257
213
|
try {
|
|
258
|
-
const response = await fetch(
|
|
214
|
+
const response = await fetch(
|
|
215
|
+
`${this.baseUrl}/api/projects/${teamId}/tasks/${taskRun.task}/runs/${taskRun.id}/logs`,
|
|
216
|
+
{ headers: this.headers },
|
|
217
|
+
);
|
|
259
218
|
|
|
260
219
|
if (!response.ok) {
|
|
220
|
+
if (response.status === 404) {
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
261
223
|
throw new Error(
|
|
262
224
|
`Failed to fetch logs: ${response.status} ${response.statusText}`,
|
|
263
225
|
);
|
|
@@ -280,126 +242,4 @@ export class PostHogAPIClient {
|
|
|
280
242
|
);
|
|
281
243
|
}
|
|
282
244
|
}
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Fetch error details from PostHog error tracking
|
|
286
|
-
*/
|
|
287
|
-
async fetchErrorDetails(
|
|
288
|
-
errorId: string,
|
|
289
|
-
projectId?: string,
|
|
290
|
-
): Promise<PostHogResource> {
|
|
291
|
-
const teamId = projectId ? parseInt(projectId, 10) : this.getTeamId();
|
|
292
|
-
|
|
293
|
-
try {
|
|
294
|
-
const errorData = await this.apiRequest<Record<string, unknown>>(
|
|
295
|
-
`/api/projects/${teamId}/error_tracking/${errorId}/`,
|
|
296
|
-
);
|
|
297
|
-
|
|
298
|
-
// Format error details for agent consumption
|
|
299
|
-
const content = this.formatErrorContent(errorData);
|
|
300
|
-
|
|
301
|
-
return {
|
|
302
|
-
type: "error",
|
|
303
|
-
id: errorId,
|
|
304
|
-
url: `${this.baseUrl}/project/${teamId}/error_tracking/${errorId}`,
|
|
305
|
-
title:
|
|
306
|
-
(typeof errorData.exception_type === "string"
|
|
307
|
-
? errorData.exception_type
|
|
308
|
-
: undefined) || "Unknown Error",
|
|
309
|
-
content,
|
|
310
|
-
metadata: {
|
|
311
|
-
exception_type: errorData.exception_type,
|
|
312
|
-
first_seen: errorData.first_seen,
|
|
313
|
-
last_seen: errorData.last_seen,
|
|
314
|
-
volume: errorData.volume,
|
|
315
|
-
users_affected: errorData.users_affected,
|
|
316
|
-
},
|
|
317
|
-
};
|
|
318
|
-
} catch (error) {
|
|
319
|
-
throw new Error(`Failed to fetch error details for ${errorId}: ${error}`);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/**
|
|
324
|
-
* Generic resource fetcher by URL or ID
|
|
325
|
-
*/
|
|
326
|
-
async fetchResourceByUrl(urlMention: UrlMention): Promise<PostHogResource> {
|
|
327
|
-
switch (urlMention.type) {
|
|
328
|
-
case "error": {
|
|
329
|
-
if (!urlMention.id) {
|
|
330
|
-
throw new Error("Error ID is required for error resources");
|
|
331
|
-
}
|
|
332
|
-
// Extract project ID from URL if available, otherwise use default team
|
|
333
|
-
let projectId: string | undefined;
|
|
334
|
-
if (urlMention.url) {
|
|
335
|
-
const projectIdMatch = urlMention.url.match(/\/project\/(\d+)\//);
|
|
336
|
-
projectId = projectIdMatch ? projectIdMatch[1] : undefined;
|
|
337
|
-
}
|
|
338
|
-
return this.fetchErrorDetails(urlMention.id, projectId);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
case "experiment":
|
|
342
|
-
case "insight":
|
|
343
|
-
case "feature_flag":
|
|
344
|
-
throw new Error(
|
|
345
|
-
`Resource type '${urlMention.type}' not yet implemented`,
|
|
346
|
-
);
|
|
347
|
-
|
|
348
|
-
case "generic":
|
|
349
|
-
// Return a minimal resource for generic URLs
|
|
350
|
-
return {
|
|
351
|
-
type: "generic",
|
|
352
|
-
id: "",
|
|
353
|
-
url: urlMention.url,
|
|
354
|
-
title: "Generic Resource",
|
|
355
|
-
content: `Generic resource: ${urlMention.url}`,
|
|
356
|
-
metadata: {},
|
|
357
|
-
};
|
|
358
|
-
|
|
359
|
-
default:
|
|
360
|
-
throw new Error(`Unknown resource type: ${urlMention.type}`);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* Format error data for agent consumption
|
|
366
|
-
*/
|
|
367
|
-
private formatErrorContent(errorData: Record<string, unknown>): string {
|
|
368
|
-
const sections = [];
|
|
369
|
-
|
|
370
|
-
if (errorData.exception_type) {
|
|
371
|
-
sections.push(`**Error Type**: ${errorData.exception_type}`);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
if (errorData.exception_message) {
|
|
375
|
-
sections.push(`**Message**: ${errorData.exception_message}`);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
if (errorData.stack_trace) {
|
|
379
|
-
sections.push(
|
|
380
|
-
`**Stack Trace**:\n\`\`\`\n${errorData.stack_trace}\n\`\`\``,
|
|
381
|
-
);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
if (errorData.volume) {
|
|
385
|
-
sections.push(`**Volume**: ${errorData.volume} occurrences`);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
if (errorData.users_affected) {
|
|
389
|
-
sections.push(`**Users Affected**: ${errorData.users_affected}`);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
if (errorData.first_seen && errorData.last_seen) {
|
|
393
|
-
sections.push(`**First Seen**: ${errorData.first_seen}`);
|
|
394
|
-
sections.push(`**Last Seen**: ${errorData.last_seen}`);
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
if (errorData.properties && Object.keys(errorData.properties).length > 0) {
|
|
398
|
-
sections.push(
|
|
399
|
-
`**Properties**: ${JSON.stringify(errorData.properties, null, 2)}`,
|
|
400
|
-
);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
return sections.join("\n\n");
|
|
404
|
-
}
|
|
405
245
|
}
|
package/src/resume.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resume - Restore agent state from persisted log
|
|
3
|
+
*
|
|
4
|
+
* Handles resuming a task from any point:
|
|
5
|
+
* - Fetches log via the PostHog API
|
|
6
|
+
* - Finds latest tree_snapshot event
|
|
7
|
+
* - Rebuilds conversation from log events
|
|
8
|
+
* - Restores working tree from snapshot
|
|
9
|
+
*
|
|
10
|
+
* Uses Saga pattern for atomic operations with clear success/failure tracking.
|
|
11
|
+
*
|
|
12
|
+
* The log is the single source of truth for:
|
|
13
|
+
* - Conversation history (user_message, agent_message_chunk, tool_call, tool_result)
|
|
14
|
+
* - Working tree state (tree_snapshot events)
|
|
15
|
+
* - Session metadata (device info, mode changes)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { ContentBlock } from "@agentclientprotocol/sdk";
|
|
19
|
+
import type { PostHogAPIClient } from "./posthog-api.js";
|
|
20
|
+
import { ResumeSaga } from "./sagas/resume-saga.js";
|
|
21
|
+
import type { DeviceInfo, TreeSnapshotEvent } from "./types.js";
|
|
22
|
+
import { Logger } from "./utils/logger.js";
|
|
23
|
+
|
|
24
|
+
export interface ResumeState {
|
|
25
|
+
conversation: ConversationTurn[];
|
|
26
|
+
latestSnapshot: TreeSnapshotEvent | null;
|
|
27
|
+
/** Whether the tree snapshot was successfully applied (files restored) */
|
|
28
|
+
snapshotApplied: boolean;
|
|
29
|
+
interrupted: boolean;
|
|
30
|
+
lastDevice?: DeviceInfo;
|
|
31
|
+
logEntryCount: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ConversationTurn {
|
|
35
|
+
role: "user" | "assistant";
|
|
36
|
+
content: ContentBlock[];
|
|
37
|
+
toolCalls?: ToolCallInfo[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ToolCallInfo {
|
|
41
|
+
toolCallId: string;
|
|
42
|
+
toolName: string;
|
|
43
|
+
input: unknown;
|
|
44
|
+
result?: unknown;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ResumeConfig {
|
|
48
|
+
taskId: string;
|
|
49
|
+
runId: string;
|
|
50
|
+
repositoryPath: string;
|
|
51
|
+
apiClient: PostHogAPIClient;
|
|
52
|
+
logger?: Logger;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Resume a task from its persisted log.
|
|
57
|
+
* Returns the rebuilt state for the agent to continue from.
|
|
58
|
+
*
|
|
59
|
+
* Uses Saga pattern internally for atomic operations.
|
|
60
|
+
* Note: snapshotApplied field indicates if files were actually restored -
|
|
61
|
+
* even if latestSnapshot is non-null, files may not have been restored if
|
|
62
|
+
* the snapshot had no archive URL or download/extraction failed.
|
|
63
|
+
*/
|
|
64
|
+
export async function resumeFromLog(
|
|
65
|
+
config: ResumeConfig,
|
|
66
|
+
): Promise<ResumeState> {
|
|
67
|
+
const logger =
|
|
68
|
+
config.logger || new Logger({ debug: false, prefix: "[Resume]" });
|
|
69
|
+
|
|
70
|
+
logger.info("Resuming from log", {
|
|
71
|
+
taskId: config.taskId,
|
|
72
|
+
runId: config.runId,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const saga = new ResumeSaga(logger);
|
|
76
|
+
|
|
77
|
+
const result = await saga.run({
|
|
78
|
+
taskId: config.taskId,
|
|
79
|
+
runId: config.runId,
|
|
80
|
+
repositoryPath: config.repositoryPath,
|
|
81
|
+
apiClient: config.apiClient,
|
|
82
|
+
logger,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
if (!result.success) {
|
|
86
|
+
logger.error("Failed to resume from log", {
|
|
87
|
+
error: result.error,
|
|
88
|
+
failedStep: result.failedStep,
|
|
89
|
+
});
|
|
90
|
+
throw new Error(
|
|
91
|
+
`Failed to resume at step '${result.failedStep}': ${result.error}`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
conversation: result.data.conversation as ConversationTurn[],
|
|
97
|
+
latestSnapshot: result.data.latestSnapshot,
|
|
98
|
+
snapshotApplied: result.data.snapshotApplied,
|
|
99
|
+
interrupted: result.data.interrupted,
|
|
100
|
+
lastDevice: result.data.lastDevice,
|
|
101
|
+
logEntryCount: result.data.logEntryCount,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Convert resumed conversation back to API format for continuation.
|
|
107
|
+
*/
|
|
108
|
+
export function conversationToPromptHistory(
|
|
109
|
+
conversation: ConversationTurn[],
|
|
110
|
+
): Array<{ role: "user" | "assistant"; content: ContentBlock[] }> {
|
|
111
|
+
return conversation.map((turn) => ({
|
|
112
|
+
role: turn.role,
|
|
113
|
+
content: turn.content,
|
|
114
|
+
}));
|
|
115
|
+
}
|