@posthog/agent 1.30.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +221 -219
  3. package/dist/adapters/claude/conversion/tool-use-to-acp.d.ts +21 -0
  4. package/dist/adapters/claude/conversion/tool-use-to-acp.js +547 -0
  5. package/dist/adapters/claude/conversion/tool-use-to-acp.js.map +1 -0
  6. package/dist/adapters/claude/permissions/permission-options.d.ts +13 -0
  7. package/dist/adapters/claude/permissions/permission-options.js +117 -0
  8. package/dist/adapters/claude/permissions/permission-options.js.map +1 -0
  9. package/dist/adapters/claude/questions/utils.d.ts +132 -0
  10. package/dist/adapters/claude/questions/utils.js +63 -0
  11. package/dist/adapters/claude/questions/utils.js.map +1 -0
  12. package/dist/adapters/claude/tools.d.ts +18 -0
  13. package/dist/adapters/claude/tools.js +95 -0
  14. package/dist/adapters/claude/tools.js.map +1 -0
  15. package/dist/agent-DBQY1BfC.d.ts +123 -0
  16. package/dist/agent.d.ts +5 -0
  17. package/dist/agent.js +3656 -0
  18. package/dist/agent.js.map +1 -0
  19. package/dist/claude-cli/cli.js +3695 -2746
  20. package/dist/claude-cli/vendor/ripgrep/COPYING +3 -0
  21. package/dist/claude-cli/vendor/ripgrep/arm64-darwin/rg +0 -0
  22. package/dist/claude-cli/vendor/ripgrep/arm64-darwin/ripgrep.node +0 -0
  23. package/dist/claude-cli/vendor/ripgrep/arm64-linux/rg +0 -0
  24. package/dist/claude-cli/vendor/ripgrep/arm64-linux/ripgrep.node +0 -0
  25. package/dist/claude-cli/vendor/ripgrep/x64-darwin/rg +0 -0
  26. package/dist/claude-cli/vendor/ripgrep/x64-darwin/ripgrep.node +0 -0
  27. package/dist/claude-cli/vendor/ripgrep/x64-linux/rg +0 -0
  28. package/dist/claude-cli/vendor/ripgrep/x64-linux/ripgrep.node +0 -0
  29. package/dist/claude-cli/vendor/ripgrep/x64-win32/rg.exe +0 -0
  30. package/dist/claude-cli/vendor/ripgrep/x64-win32/ripgrep.node +0 -0
  31. package/dist/gateway-models.d.ts +24 -0
  32. package/dist/gateway-models.js +93 -0
  33. package/dist/gateway-models.js.map +1 -0
  34. package/dist/index.d.ts +172 -1203
  35. package/dist/index.js +3704 -6826
  36. package/dist/index.js.map +1 -1
  37. package/dist/logger-DDBiMOOD.d.ts +24 -0
  38. package/dist/posthog-api.d.ts +40 -0
  39. package/dist/posthog-api.js +175 -0
  40. package/dist/posthog-api.js.map +1 -0
  41. package/dist/server/agent-server.d.ts +41 -0
  42. package/dist/server/agent-server.js +4451 -0
  43. package/dist/server/agent-server.js.map +1 -0
  44. package/dist/server/bin.d.ts +1 -0
  45. package/dist/server/bin.js +4507 -0
  46. package/dist/server/bin.js.map +1 -0
  47. package/dist/types.d.ts +129 -0
  48. package/dist/types.js +1 -0
  49. package/dist/types.js.map +1 -0
  50. package/package.json +66 -14
  51. package/src/acp-extensions.ts +93 -61
  52. package/src/adapters/acp-connection.ts +494 -0
  53. package/src/adapters/base-acp-agent.ts +150 -0
  54. package/src/adapters/claude/claude-agent.ts +596 -0
  55. package/src/adapters/claude/conversion/acp-to-sdk.ts +102 -0
  56. package/src/adapters/claude/conversion/sdk-to-acp.ts +571 -0
  57. package/src/adapters/claude/conversion/tool-use-to-acp.ts +618 -0
  58. package/src/adapters/claude/hooks.ts +64 -0
  59. package/src/adapters/claude/mcp/tool-metadata.ts +102 -0
  60. package/src/adapters/claude/permissions/permission-handlers.ts +433 -0
  61. package/src/adapters/claude/permissions/permission-options.ts +103 -0
  62. package/src/adapters/claude/plan/utils.ts +56 -0
  63. package/src/adapters/claude/questions/utils.ts +92 -0
  64. package/src/adapters/claude/session/commands.ts +38 -0
  65. package/src/adapters/claude/session/mcp-config.ts +37 -0
  66. package/src/adapters/claude/session/models.ts +12 -0
  67. package/src/adapters/claude/session/options.ts +236 -0
  68. package/src/adapters/claude/tool-meta.ts +143 -0
  69. package/src/adapters/claude/tools.ts +53 -611
  70. package/src/adapters/claude/types.ts +61 -0
  71. package/src/adapters/codex/spawn.ts +130 -0
  72. package/src/agent.ts +97 -734
  73. package/src/execution-mode.ts +43 -0
  74. package/src/gateway-models.ts +135 -0
  75. package/src/index.ts +79 -0
  76. package/src/otel-log-writer.test.ts +105 -0
  77. package/src/otel-log-writer.ts +94 -0
  78. package/src/posthog-api.ts +75 -235
  79. package/src/resume.ts +115 -0
  80. package/src/sagas/apply-snapshot-saga.test.ts +690 -0
  81. package/src/sagas/apply-snapshot-saga.ts +88 -0
  82. package/src/sagas/capture-tree-saga.test.ts +892 -0
  83. package/src/sagas/capture-tree-saga.ts +141 -0
  84. package/src/sagas/resume-saga.test.ts +558 -0
  85. package/src/sagas/resume-saga.ts +332 -0
  86. package/src/sagas/test-fixtures.ts +250 -0
  87. package/src/server/agent-server.test.ts +220 -0
  88. package/src/server/agent-server.ts +748 -0
  89. package/src/server/bin.ts +88 -0
  90. package/src/server/jwt.ts +65 -0
  91. package/src/server/schemas.ts +47 -0
  92. package/src/server/types.ts +13 -0
  93. package/src/server/utils/retry.test.ts +122 -0
  94. package/src/server/utils/retry.ts +61 -0
  95. package/src/server/utils/sse-parser.test.ts +93 -0
  96. package/src/server/utils/sse-parser.ts +46 -0
  97. package/src/session-log-writer.test.ts +140 -0
  98. package/src/session-log-writer.ts +137 -0
  99. package/src/test/assertions.ts +114 -0
  100. package/src/test/controllers/sse-controller.ts +107 -0
  101. package/src/test/fixtures/api.ts +111 -0
  102. package/src/test/fixtures/config.ts +33 -0
  103. package/src/test/fixtures/notifications.ts +92 -0
  104. package/src/test/mocks/claude-sdk.ts +251 -0
  105. package/src/test/mocks/msw-handlers.ts +48 -0
  106. package/src/test/setup.ts +114 -0
  107. package/src/test/wait.ts +41 -0
  108. package/src/tree-tracker.ts +173 -0
  109. package/src/types.ts +51 -154
  110. package/src/utils/acp-content.ts +58 -0
  111. package/src/utils/async-mutex.test.ts +104 -0
  112. package/src/utils/async-mutex.ts +31 -0
  113. package/src/utils/common.ts +15 -0
  114. package/src/utils/gateway.ts +9 -6
  115. package/src/utils/logger.ts +0 -30
  116. package/src/utils/streams.ts +220 -0
  117. package/CLAUDE.md +0 -331
  118. package/dist/templates/plan-template.md +0 -41
  119. package/src/adapters/claude/claude.ts +0 -1543
  120. package/src/adapters/claude/mcp-server.ts +0 -810
  121. package/src/adapters/claude/utils.ts +0 -267
  122. package/src/agents/execution.ts +0 -37
  123. package/src/agents/planning.ts +0 -60
  124. package/src/agents/research.ts +0 -160
  125. package/src/file-manager.ts +0 -306
  126. package/src/git-manager.ts +0 -577
  127. package/src/prompt-builder.ts +0 -499
  128. package/src/schemas.ts +0 -241
  129. package/src/session-store.ts +0 -259
  130. package/src/task-manager.ts +0 -163
  131. package/src/template-manager.ts +0 -236
  132. package/src/templates/plan-template.md +0 -41
  133. package/src/todo-manager.ts +0 -180
  134. package/src/tools/registry.ts +0 -129
  135. package/src/tools/types.ts +0 -127
  136. package/src/utils/tapped-stream.ts +0 -60
  137. package/src/workflow/config.ts +0 -53
  138. package/src/workflow/steps/build.ts +0 -135
  139. package/src/workflow/steps/finalize.ts +0 -241
  140. package/src/workflow/steps/plan.ts +0 -167
  141. package/src/workflow/steps/research.ts +0 -223
  142. package/src/workflow/types.ts +0 -62
  143. package/src/workflow/utils.ts +0 -53
  144. package/src/worktree-manager.ts +0 -928
@@ -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
- interface PostHogApiResponse<T> {
14
- results?: T[];
15
- count?: number;
16
- next?: string | null;
17
- previous?: string | null;
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
- "status" | "branch" | "stage" | "error_message" | "output" | "state"
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 fetchTask(taskId: string): Promise<Task> {
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
- * Fetch logs from S3 using presigned URL from TaskRun
249
- * @param taskRun - The task run containing the log_url
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
- if (!taskRun.log_url) {
254
- return [];
255
- }
211
+ const teamId = this.getTeamId();
256
212
 
257
213
  try {
258
- const response = await fetch(taskRun.log_url);
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
+ }