@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
package/src/agent.ts CHANGED
@@ -1,390 +1,142 @@
1
- import {
2
- type Client,
3
- ClientSideConnection,
4
- type ContentBlock,
5
- ndJsonStream,
6
- PROTOCOL_VERSION,
7
- } from "@agentclientprotocol/sdk";
8
- import { POSTHOG_NOTIFICATIONS } from "./acp-extensions.js";
9
1
  import {
10
2
  createAcpConnection,
11
3
  type InProcessAcpConnection,
12
- } from "./adapters/claude/claude.js";
13
- import { PostHogFileManager } from "./file-manager.js";
14
- import { GitManager } from "./git-manager.js";
4
+ } from "./adapters/acp-connection.js";
5
+ import {
6
+ BLOCKED_MODELS,
7
+ DEFAULT_GATEWAY_MODEL,
8
+ fetchArrayModels,
9
+ } from "./gateway-models.js";
15
10
  import { PostHogAPIClient } from "./posthog-api.js";
16
- import { PromptBuilder } from "./prompt-builder.js";
17
- import { SessionStore } from "./session-store.js";
18
- import { TaskManager } from "./task-manager.js";
19
- import { TemplateManager } from "./template-manager.js";
20
- import type {
21
- AgentConfig,
22
- CanUseTool,
23
- StoredNotification,
24
- Task,
25
- TaskExecutionOptions,
26
- } from "./types.js";
11
+ import { SessionLogWriter } from "./session-log-writer.js";
12
+ import type { AgentConfig, TaskExecutionOptions } from "./types.js";
27
13
  import { Logger } from "./utils/logger.js";
28
- import { TASK_WORKFLOW } from "./workflow/config.js";
29
- import type { SendNotification, WorkflowRuntime } from "./workflow/types.js";
30
14
 
31
15
  export class Agent {
32
- private workingDirectory: string;
33
- private taskManager: TaskManager;
34
16
  private posthogAPI?: PostHogAPIClient;
35
- private fileManager: PostHogFileManager;
36
- private gitManager: GitManager;
37
- private templateManager: TemplateManager;
38
17
  private logger: Logger;
39
18
  private acpConnection?: InProcessAcpConnection;
40
- private promptBuilder: PromptBuilder;
41
- private mcpServers?: Record<string, any>;
42
- private canUseTool?: CanUseTool;
43
- private currentRunId?: string;
44
- private sessionStore?: SessionStore;
19
+ private taskRunId?: string;
20
+ private sessionLogWriter?: SessionLogWriter;
45
21
  public debug: boolean;
46
22
 
47
23
  constructor(config: AgentConfig) {
48
- this.workingDirectory = config.workingDirectory || process.cwd();
49
- this.canUseTool = config.canUseTool;
50
24
  this.debug = config.debug || false;
51
-
52
- // Build default PostHog MCP server configuration
53
- const posthogMcpUrl =
54
- config.posthogMcpUrl ||
55
- process.env.POSTHOG_MCP_URL ||
56
- "https://mcp.posthog.com/mcp";
57
-
58
- // Add auth if API key provided
59
- const headers: Record<string, string> = {};
60
- if (config.getPosthogApiKey) {
61
- headers.Authorization = `Bearer ${config.getPosthogApiKey()}`;
62
- }
63
-
64
- const defaultMcpServers = {
65
- posthog: {
66
- type: "http" as const,
67
- url: posthogMcpUrl,
68
- ...(Object.keys(headers).length > 0 ? { headers } : {}),
69
- },
70
- };
71
-
72
- // Merge default PostHog MCP with user-provided servers (user config takes precedence)
73
- this.mcpServers = {
74
- ...defaultMcpServers,
75
- ...config.mcpServers,
76
- };
77
25
  this.logger = new Logger({
78
26
  debug: this.debug,
79
27
  prefix: "[PostHog Agent]",
80
28
  onLog: config.onLog,
81
29
  });
82
- this.taskManager = new TaskManager();
83
30
 
84
- this.fileManager = new PostHogFileManager(
85
- this.workingDirectory,
86
- this.logger.child("FileManager"),
87
- );
88
- this.gitManager = new GitManager({
89
- repositoryPath: this.workingDirectory,
90
- logger: this.logger.child("GitManager"),
91
- });
92
- this.templateManager = new TemplateManager();
31
+ if (config.posthog) {
32
+ this.posthogAPI = new PostHogAPIClient(config.posthog);
33
+ }
93
34
 
94
- if (
95
- config.posthogApiUrl &&
96
- config.getPosthogApiKey &&
97
- config.posthogProjectId
98
- ) {
99
- this.posthogAPI = new PostHogAPIClient({
100
- apiUrl: config.posthogApiUrl,
101
- getApiKey: config.getPosthogApiKey,
102
- projectId: config.posthogProjectId,
35
+ if (config.otelTransport) {
36
+ // OTEL pipeline: use OtelLogWriter only (no S3 writer)
37
+ this.sessionLogWriter = new SessionLogWriter({
38
+ otelConfig: {
39
+ posthogHost: config.otelTransport.host,
40
+ apiKey: config.otelTransport.apiKey,
41
+ logsPath: config.otelTransport.logsPath,
42
+ },
43
+ logger: this.logger.child("SessionLogWriter"),
44
+ });
45
+ } else if (config.posthog) {
46
+ // Legacy: use S3 writer via PostHog API
47
+ this.sessionLogWriter = new SessionLogWriter({
48
+ posthogAPI: this.posthogAPI,
49
+ logger: this.logger.child("SessionLogWriter"),
103
50
  });
104
-
105
- // Create SessionStore from the API client for ACP connection
106
- this.sessionStore = new SessionStore(
107
- this.posthogAPI,
108
- this.logger.child("SessionStore"),
109
- );
110
51
  }
111
-
112
- this.promptBuilder = new PromptBuilder({
113
- getTaskFiles: (taskId: string) => this.getTaskFiles(taskId),
114
- generatePlanTemplate: (vars) => this.templateManager.generatePlan(vars),
115
- posthogClient: this.posthogAPI,
116
- logger: this.logger.child("PromptBuilder"),
117
- });
118
52
  }
119
53
 
120
- /**
121
- * Enable or disable debug logging
122
- */
123
- setDebug(enabled: boolean) {
124
- this.debug = enabled;
125
- this.logger.setDebug(enabled);
126
- }
127
-
128
- /**
129
- * Configure LLM gateway environment variables for Claude Code CLI.
130
- */
131
- private async _configureLlmGateway(): Promise<void> {
54
+ private _configureLlmGateway(_adapter?: "claude" | "codex"): {
55
+ gatewayUrl: string;
56
+ apiKey: string;
57
+ } | null {
132
58
  if (!this.posthogAPI) {
133
- return;
59
+ return null;
134
60
  }
135
61
 
136
62
  try {
137
63
  const gatewayUrl = this.posthogAPI.getLlmGatewayUrl();
138
64
  const apiKey = this.posthogAPI.getApiKey();
65
+
66
+ process.env.OPENAI_BASE_URL = `${gatewayUrl}/v1`;
67
+ process.env.OPENAI_API_KEY = apiKey;
139
68
  process.env.ANTHROPIC_BASE_URL = gatewayUrl;
140
69
  process.env.ANTHROPIC_AUTH_TOKEN = apiKey;
141
- this.ensureOpenAIGatewayEnv(gatewayUrl, apiKey);
142
- this.ensureGeminiGatewayEnv(gatewayUrl, apiKey);
70
+
71
+ return { gatewayUrl, apiKey };
143
72
  } catch (error) {
144
73
  this.logger.error("Failed to configure LLM gateway", error);
145
74
  throw error;
146
75
  }
147
76
  }
148
77
 
149
- private getOrCreateConnection(): InProcessAcpConnection {
150
- if (!this.acpConnection) {
151
- this.acpConnection = createAcpConnection({
152
- sessionStore: this.sessionStore,
153
- });
154
- }
155
- return this.acpConnection;
156
- }
157
-
158
- // Adaptive task execution orchestrated via workflow steps
159
- async runTask(
160
- taskId: string,
161
- taskRunId: string,
162
- options: import("./types.js").TaskExecutionOptions = {},
163
- ): Promise<void> {
164
- // await this._configureLlmGateway();
165
-
166
- const task = await this.fetchTask(taskId);
167
- const cwd = options.repositoryPath || this.workingDirectory;
168
- const isCloudMode = options.isCloudMode ?? false;
169
- const taskSlug = (task as any).slug || task.id;
170
-
171
- // Use taskRunId as sessionId - they are the same identifier
172
- this.currentRunId = taskRunId;
173
-
174
- this.logger.info("Starting adaptive task execution", {
175
- taskId: task.id,
176
- taskSlug,
177
- taskRunId,
178
- isCloudMode,
179
- });
180
-
181
- const connection = this.getOrCreateConnection();
182
-
183
- // Create sendNotification using ACP connection's extNotification
184
- const sendNotification: SendNotification = async (method, params) => {
185
- this.logger.debug(`Notification: ${method}`, params);
186
- await connection.agentConnection.extNotification?.(method, params);
187
- };
188
-
189
- await sendNotification(POSTHOG_NOTIFICATIONS.RUN_STARTED, {
190
- sessionId: taskRunId,
191
- runId: taskRunId,
192
- });
193
-
194
- await this.prepareTaskBranch(taskSlug, isCloudMode, sendNotification);
195
-
196
- let taskError: Error | undefined;
197
- try {
198
- const workflowContext: WorkflowRuntime = {
199
- task,
200
- taskSlug,
201
- runId: taskRunId,
202
- cwd,
203
- isCloudMode,
204
- options,
205
- logger: this.logger,
206
- fileManager: this.fileManager,
207
- gitManager: this.gitManager,
208
- promptBuilder: this.promptBuilder,
209
- connection: connection.agentConnection,
210
- sessionId: taskRunId,
211
- sendNotification,
212
- mcpServers: this.mcpServers,
213
- posthogAPI: this.posthogAPI,
214
- stepResults: {},
215
- };
216
-
217
- for (const step of TASK_WORKFLOW) {
218
- const result = await step.run({ step, context: workflowContext });
219
- if (result.halt) {
220
- return;
221
- }
222
- }
223
-
224
- const shouldCreatePR = options.createPR ?? isCloudMode;
225
- if (shouldCreatePR) {
226
- await this.ensurePullRequest(
227
- task,
228
- workflowContext.stepResults,
229
- sendNotification,
230
- );
231
- }
232
-
233
- this.logger.info("Task execution complete", { taskId: task.id });
234
- await sendNotification(POSTHOG_NOTIFICATIONS.TASK_COMPLETE, {
235
- sessionId: taskRunId,
236
- taskId: task.id,
237
- });
238
- } catch (error) {
239
- taskError = error instanceof Error ? error : new Error(String(error));
240
- this.logger.error("Task execution failed", {
241
- taskId: task.id,
242
- error: taskError.message,
243
- });
244
- await sendNotification(POSTHOG_NOTIFICATIONS.ERROR, {
245
- sessionId: taskRunId,
246
- message: taskError.message,
247
- });
248
- throw taskError;
249
- }
250
- }
251
-
252
- /**
253
- * Creates an in-process ACP connection for client communication.
254
- * Sets up git branch for the task, configures LLM gateway.
255
- * The client handles all prompting/querying via the returned streams.
256
- *
257
- * @returns InProcessAcpConnection with clientStreams for the client to use
258
- */
259
- async runTaskV2(
78
+ async run(
260
79
  taskId: string,
261
80
  taskRunId: string,
262
- options: import("./types.js").TaskExecutionOptions = {},
81
+ options: TaskExecutionOptions = {},
263
82
  ): Promise<InProcessAcpConnection> {
264
- await this._configureLlmGateway();
265
-
266
- const task = await this.fetchTask(taskId);
267
- const taskSlug = (task as any).slug || task.id;
268
- const isCloudMode = options.isCloudMode ?? false;
269
- const _cwd = options.repositoryPath || this.workingDirectory;
270
-
271
- // Use taskRunId as sessionId - they are the same identifier
272
- this.currentRunId = taskRunId;
273
-
274
- this.acpConnection = createAcpConnection({
275
- sessionStore: this.sessionStore,
276
- sessionId: taskRunId,
277
- taskId: task.id,
278
- });
279
-
280
- const sendNotification: SendNotification = async (method, params) => {
281
- this.logger.debug(`Notification: ${method}`, params);
282
- await this.acpConnection?.agentConnection.extNotification?.(
283
- method,
284
- params,
285
- );
286
- };
83
+ const gatewayConfig = this._configureLlmGateway(options.adapter);
84
+
85
+ this.taskRunId = taskRunId;
86
+
87
+ let allowedModelIds: Set<string> | undefined;
88
+ let sanitizedModel =
89
+ options.model && !BLOCKED_MODELS.has(options.model)
90
+ ? options.model
91
+ : undefined;
92
+ if (options.adapter === "codex" && gatewayConfig) {
93
+ const models = await fetchArrayModels({
94
+ gatewayUrl: gatewayConfig.gatewayUrl,
95
+ });
96
+ const codexModelIds = models
97
+ .filter((model) => {
98
+ if (BLOCKED_MODELS.has(model.id)) return false;
99
+ if (model.owned_by) {
100
+ return model.owned_by === "openai";
101
+ }
102
+ return model.id.startsWith("gpt-") || model.id.startsWith("openai/");
103
+ })
104
+ .map((model) => model.id);
287
105
 
288
- await sendNotification(POSTHOG_NOTIFICATIONS.RUN_STARTED, {
289
- sessionId: taskRunId,
290
- runId: taskRunId,
291
- });
106
+ if (codexModelIds.length > 0) {
107
+ allowedModelIds = new Set(codexModelIds);
108
+ }
292
109
 
293
- if (!options.skipGitBranch) {
294
- try {
295
- await this.prepareTaskBranch(taskSlug, isCloudMode, sendNotification);
296
- } catch (error) {
297
- const errorMessage =
298
- error instanceof Error ? error.message : String(error);
299
- this.logger.error("Failed to prepare task branch", {
300
- error: errorMessage,
301
- });
302
- await sendNotification(POSTHOG_NOTIFICATIONS.ERROR, {
303
- sessionId: taskRunId,
304
- message: errorMessage,
305
- });
306
- throw error;
110
+ if (!sanitizedModel || !allowedModelIds?.has(sanitizedModel)) {
111
+ sanitizedModel = codexModelIds[0];
307
112
  }
308
113
  }
309
-
310
- return this.acpConnection;
311
- }
312
-
313
- // PostHog task operations
314
- async fetchTask(taskId: string): Promise<Task> {
315
- if (!this.posthogAPI) {
316
- const error = new Error(
317
- "PostHog API not configured. Provide posthogApiUrl and posthogApiKey in constructor.",
318
- );
319
- this.logger.error("PostHog API not configured", error);
320
- throw error;
114
+ if (!sanitizedModel) {
115
+ sanitizedModel = DEFAULT_GATEWAY_MODEL;
321
116
  }
322
- return this.posthogAPI.fetchTask(taskId);
323
- }
324
-
325
- getPostHogClient(): PostHogAPIClient | undefined {
326
- return this.posthogAPI;
327
- }
328
117
 
329
- /**
330
- * Send a notification to a cloud task run's S3 log.
331
- * The cloud runner will pick up new notifications via interrupt polling.
332
- */
333
- async sendNotification(
334
- taskId: string,
335
- runId: string,
336
- notification: StoredNotification,
337
- ): Promise<void> {
338
- if (!this.posthogAPI) {
339
- throw new Error(
340
- "PostHog API not configured. Cannot send notification to cloud task.",
341
- );
342
- }
343
-
344
- await this.posthogAPI.appendTaskRunLog(taskId, runId, [notification]);
345
- this.logger.debug("Notification sent to cloud task", {
346
- taskId,
347
- runId,
348
- method: notification.notification.method,
349
- });
350
- }
351
-
352
- async getTaskFiles(taskId: string): Promise<any[]> {
353
- this.logger.debug("Getting task files", { taskId });
354
- const files = await this.fileManager.getTaskFiles(taskId);
355
- this.logger.debug("Found task files", { taskId, fileCount: files.length });
356
- return files;
357
- }
358
-
359
- async createPullRequest(
360
- taskId: string,
361
- branchName: string,
362
- taskTitle: string,
363
- taskDescription: string,
364
- customBody?: string,
365
- ): Promise<string> {
366
- this.logger.info("Creating pull request", {
118
+ this.acpConnection = createAcpConnection({
119
+ adapter: options.adapter,
120
+ logWriter: this.sessionLogWriter,
121
+ taskRunId,
367
122
  taskId,
368
- branchName,
369
- taskTitle,
123
+ deviceType: "local",
124
+ logger: this.logger,
125
+ processCallbacks: options.processCallbacks,
126
+ allowedModelIds,
127
+ codexOptions:
128
+ options.adapter === "codex" && gatewayConfig
129
+ ? {
130
+ cwd: options.repositoryPath,
131
+ apiBaseUrl: `${gatewayConfig.gatewayUrl}/v1`,
132
+ apiKey: gatewayConfig.apiKey,
133
+ binaryPath: options.codexBinaryPath,
134
+ model: sanitizedModel,
135
+ }
136
+ : undefined,
370
137
  });
371
138
 
372
- const defaultBody = `## Task Details
373
- **Task ID**: ${taskId}
374
- **Description**: ${taskDescription}
375
-
376
- ## Changes
377
- This PR implements the changes described in the task.`;
378
- const prBody = customBody || defaultBody;
379
-
380
- const prUrl = await this.gitManager.createPullRequest(
381
- branchName,
382
- taskTitle,
383
- prBody,
384
- );
385
-
386
- this.logger.info("Pull request created", { taskId, prUrl });
387
- return prUrl;
139
+ return this.acpConnection;
388
140
  }
389
141
 
390
142
  async attachPullRequestToTask(
@@ -394,7 +146,7 @@ This PR implements the changes described in the task.`;
394
146
  ): Promise<void> {
395
147
  this.logger.info("Attaching PR to task run", { taskId, prUrl, branchName });
396
148
 
397
- if (!this.posthogAPI || !this.currentRunId) {
149
+ if (!this.posthogAPI || !this.taskRunId) {
398
150
  const error = new Error(
399
151
  "PostHog API not configured or no active run. Cannot attach PR to task.",
400
152
  );
@@ -409,411 +161,22 @@ This PR implements the changes described in the task.`;
409
161
  updates.branch = branchName;
410
162
  }
411
163
 
412
- await this.posthogAPI.updateTaskRun(taskId, this.currentRunId, updates);
164
+ await this.posthogAPI.updateTaskRun(taskId, this.taskRunId, updates);
413
165
  this.logger.debug("PR attached to task run", {
414
166
  taskId,
415
- runId: this.currentRunId,
167
+ taskRunId: this.taskRunId,
416
168
  prUrl,
417
169
  });
418
170
  }
419
171
 
420
- async updateTaskBranch(taskId: string, branchName: string): Promise<void> {
421
- this.logger.info("Updating task run branch", { taskId, branchName });
422
-
423
- if (!this.posthogAPI || !this.currentRunId) {
424
- const error = new Error(
425
- "PostHog API not configured or no active run. Cannot update branch.",
426
- );
427
- this.logger.error("PostHog API not configured", error);
428
- throw error;
429
- }
430
-
431
- await this.posthogAPI.updateTaskRun(taskId, this.currentRunId, {
432
- branch: branchName,
433
- });
434
- this.logger.debug("Task run branch updated", {
435
- taskId,
436
- runId: this.currentRunId,
437
- branchName,
438
- });
439
- }
440
-
441
- // Execution management
442
- cancelTask(taskId: string): void {
443
- // Find the execution for this task and cancel it
444
- for (const [executionId, execution] of this.taskManager.executionStates) {
445
- if (execution.taskId === taskId && execution.status === "running") {
446
- this.taskManager.cancelExecution(executionId);
447
- break;
448
- }
449
- }
450
- }
451
-
452
- getTaskExecutionStatus(taskId: string): string | null {
453
- // Find the execution for this task
454
- for (const execution of this.taskManager.executionStates.values()) {
455
- if (execution.taskId === taskId) {
456
- return execution.status;
457
- }
458
- }
459
- return null;
460
- }
461
-
462
- private async prepareTaskBranch(
463
- taskSlug: string,
464
- isCloudMode: boolean,
465
- sendNotification: SendNotification,
466
- ): Promise<void> {
467
- if (await this.gitManager.hasChanges()) {
468
- throw new Error(
469
- "Cannot start task with uncommitted changes. Please commit or stash your changes first.",
470
- );
471
- }
472
-
473
- // If we're running in a worktree, we're already on the correct branch
474
- // (the worktree was created with its own branch). Skip branch creation.
475
- const isWorktree = await this.gitManager.isWorktree();
476
- if (isWorktree) {
477
- const currentBranch = await this.gitManager.getCurrentBranch();
478
- this.logger.info("Running in worktree, using existing branch", {
479
- branch: currentBranch,
480
- });
481
- await sendNotification(POSTHOG_NOTIFICATIONS.BRANCH_CREATED, {
482
- branch: currentBranch,
483
- });
484
- return;
485
- }
486
-
487
- await this.gitManager.resetToDefaultBranchIfNeeded();
488
-
489
- const existingBranch = await this.gitManager.getTaskBranch(taskSlug);
490
- if (!existingBranch) {
491
- const branchName = await this.gitManager.createTaskBranch(taskSlug);
492
- await sendNotification(POSTHOG_NOTIFICATIONS.BRANCH_CREATED, {
493
- branch: branchName,
494
- });
495
-
496
- await this.gitManager.addAllPostHogFiles();
497
-
498
- // Only commit if there are changes or we're in cloud mode
499
- if (isCloudMode) {
500
- await this.gitManager.commitAndPush(`Initialize task ${taskSlug}`, {
501
- allowEmpty: true,
502
- });
503
- } else {
504
- // Check if there are any changes before committing
505
- const hasChanges = await this.gitManager.hasStagedChanges();
506
- if (hasChanges) {
507
- await this.gitManager.commitChanges(`Initialize task ${taskSlug}`);
508
- }
509
- }
510
- } else {
511
- this.logger.info("Switching to existing task branch", {
512
- branch: existingBranch,
513
- });
514
- await this.gitManager.switchToBranch(existingBranch);
515
- }
516
- }
517
-
518
- private ensureOpenAIGatewayEnv(gatewayUrl?: string, token?: string): void {
519
- const resolvedGatewayUrl = gatewayUrl || process.env.ANTHROPIC_BASE_URL;
520
- const resolvedToken = token || process.env.ANTHROPIC_AUTH_TOKEN;
521
-
522
- if (resolvedGatewayUrl) {
523
- process.env.OPENAI_BASE_URL = resolvedGatewayUrl;
524
- }
525
-
526
- if (resolvedToken) {
527
- process.env.OPENAI_API_KEY = resolvedToken;
528
- }
172
+ async flushAllLogs(): Promise<void> {
173
+ await this.sessionLogWriter?.flushAll();
529
174
  }
530
175
 
531
- private ensureGeminiGatewayEnv(gatewayUrl?: string, token?: string): void {
532
- const resolvedGatewayUrl = gatewayUrl || process.env.ANTHROPIC_BASE_URL;
533
- const resolvedToken = token || process.env.ANTHROPIC_AUTH_TOKEN;
534
-
535
- if (resolvedGatewayUrl) {
536
- process.env.GEMINI_BASE_URL = resolvedGatewayUrl;
537
- }
538
-
539
- if (resolvedToken) {
540
- process.env.GEMINI_API_KEY = resolvedToken;
541
- }
542
- }
543
-
544
- async runTaskCloud(
545
- taskId: string,
546
- taskRunId: string,
547
- options: TaskExecutionOptions = {},
548
- ): Promise<void> {
549
- await this._configureLlmGateway();
550
-
551
- const task = await this.fetchTask(taskId);
552
- const cwd = options.repositoryPath || this.workingDirectory;
553
- const taskSlug = (task as any).slug || task.id;
554
-
555
- this.currentRunId = taskRunId;
556
-
557
- this.logger.info("Starting cloud task execution", {
558
- taskId: task.id,
559
- taskSlug,
560
- taskRunId,
561
- cwd,
562
- });
563
-
564
- if (!this.sessionStore) {
565
- throw new Error(
566
- "SessionStore required for cloud mode. Ensure PostHog API credentials are configured.",
567
- );
568
- }
569
-
570
- // Start session in SessionStore (updates task run status to in_progress)
571
- const taskRun = await this.sessionStore.start(taskRunId, taskId, taskRunId);
572
- this.logger.debug("Session started", {
573
- taskRunId,
574
- logUrl: taskRun?.log_url,
575
- });
576
-
577
- // Create internal ACP connection with S3 persistence
578
- const acpConnection = createAcpConnection({
579
- sessionStore: this.sessionStore,
580
- sessionId: taskRunId,
581
- taskId: task.id,
582
- });
583
-
584
- // Create client connection using the client-side streams
585
- const clientStream = ndJsonStream(
586
- acpConnection.clientStreams.writable as WritableStream<Uint8Array>,
587
- acpConnection.clientStreams.readable as ReadableStream<Uint8Array>,
588
- );
589
-
590
- // Create auto-approving client for headless cloud mode
591
- const cloudClient: Client = {
592
- async requestPermission(params) {
593
- const allowOption = params.options.find(
594
- (o) => o.kind === "allow_once" || o.kind === "allow_always",
595
- );
596
- return {
597
- outcome: {
598
- outcome: "selected",
599
- optionId: allowOption?.optionId ?? params.options[0].optionId,
600
- },
601
- };
602
- },
603
- async sessionUpdate(_params) {
604
- // Notifications are already being persisted to S3 via tapped streams
605
- },
606
- };
607
-
608
- const clientConnection = new ClientSideConnection(
609
- (_agent) => cloudClient,
610
- clientStream,
611
- );
612
-
613
- try {
614
- // Initialize the connection
615
- await clientConnection.initialize({
616
- protocolVersion: PROTOCOL_VERSION,
617
- clientCapabilities: {},
618
- });
619
-
620
- // Create new session
621
- await clientConnection.newSession({
622
- cwd,
623
- mcpServers: [],
624
- _meta: { sessionId: taskRunId },
625
- });
626
-
627
- // Prepare git branch if not skipped
628
- if (!options.skipGitBranch) {
629
- const sendNotification: SendNotification = async (method, params) => {
630
- this.logger.debug(`Notification: ${method}`, params);
631
- await acpConnection.agentConnection.extNotification?.(method, params);
632
- };
633
- await this.prepareTaskBranch(taskSlug, true, sendNotification);
634
- }
635
-
636
- // Build initial prompt from task description
637
- const initialPrompt: ContentBlock[] = [
638
- {
639
- type: "text",
640
- text: `# Task: ${task.title}\n\n${task.description}`,
641
- },
642
- ];
643
-
644
- // Track the last known log entry count for interrupt polling
645
- let lastKnownEntryCount = 0;
646
- let isPolling = true;
647
-
648
- // Start interrupt polling in background
649
- const pollForInterrupts = async () => {
650
- while (isPolling) {
651
- await new Promise((resolve) => setTimeout(resolve, 2000)); // Poll every 2 seconds
652
- if (!isPolling) break;
653
-
654
- try {
655
- const newEntries = await this.sessionStore?.pollForNewEntries(
656
- taskRunId,
657
- lastKnownEntryCount,
658
- );
659
-
660
- for (const entry of newEntries ?? []) {
661
- lastKnownEntryCount++;
662
- // Look for user_message notifications
663
- if (
664
- entry.notification?.method === "sessionUpdate" &&
665
- (entry.notification?.params as any)?.sessionUpdate ===
666
- "user_message"
667
- ) {
668
- const content = (entry.notification?.params as any)?.content;
669
- if (content) {
670
- this.logger.info("Processing user interrupt", { content });
671
- // Send as new prompt - will be processed after current prompt completes
672
- await clientConnection.prompt({
673
- sessionId: taskRunId,
674
- prompt: Array.isArray(content) ? content : [content],
675
- });
676
- }
677
- }
678
- }
679
- } catch (err) {
680
- this.logger.warn("Interrupt polling error", { error: err });
681
- }
682
- }
683
- };
684
-
685
- // Start polling in background (don't await)
686
- const pollingPromise = pollForInterrupts();
687
-
688
- // Send initial prompt and wait for completion
689
- this.logger.info("Sending initial prompt to agent");
690
- const result = await clientConnection.prompt({
691
- sessionId: taskRunId,
692
- prompt: initialPrompt,
693
- });
694
-
695
- // Stop interrupt polling
696
- isPolling = false;
697
- await pollingPromise;
698
-
699
- this.logger.info("Task execution complete", {
700
- taskId: task.id,
701
- stopReason: result.stopReason,
702
- });
703
-
704
- const branchName = await this.gitManager.getCurrentBranch();
705
- const hasChanges = await this.gitManager.hasChanges();
706
- const shouldCreatePR = options.createPR ?? false;
707
-
708
- if (hasChanges) {
709
- this.logger.info("Committing uncommitted changes", { taskId: task.id });
710
- await this.gitManager.commitImplementation(
711
- task.id,
712
- task.title,
713
- task.description ?? undefined,
714
- );
715
- }
716
-
717
- const defaultBranch = await this.gitManager.getDefaultBranch();
718
- if (branchName !== defaultBranch) {
719
- this.logger.info("Pushing branch", { branchName, taskId: task.id });
720
- await this.gitManager.pushBranch(branchName);
721
-
722
- if (shouldCreatePR) {
723
- this.logger.info("Creating PR", { branchName, taskId: task.id });
724
-
725
- const prUrl = await this.createPullRequest(
726
- task.id,
727
- branchName,
728
- task.title,
729
- task.description ?? "",
730
- );
731
-
732
- this.logger.info("PR created", { prUrl, taskId: task.id });
733
-
734
- try {
735
- await this.attachPullRequestToTask(task.id, prUrl, branchName);
736
- } catch (err) {
737
- this.logger.warn("Could not attach PR to task", {
738
- error: err instanceof Error ? err.message : String(err),
739
- });
740
- }
741
- }
742
- }
743
-
744
- await this.sessionStore.complete(taskRunId);
745
- } catch (error) {
746
- const errorMessage =
747
- error instanceof Error ? error.message : String(error);
748
- this.logger.error("Cloud task execution failed", {
749
- taskId: task.id,
750
- error: errorMessage,
751
- });
752
- await this.sessionStore.fail(taskRunId, errorMessage);
753
- throw error;
754
- }
755
- }
756
-
757
- private async ensurePullRequest(
758
- task: Task,
759
- stepResults: Record<string, any>,
760
- sendNotification: SendNotification,
761
- ): Promise<void> {
762
- const latestRun = task.latest_run;
763
- const existingPr =
764
- latestRun?.output && typeof latestRun.output === "object"
765
- ? (latestRun.output as any).pr_url
766
- : null;
767
-
768
- if (existingPr) {
769
- this.logger.info("PR already exists, skipping creation", {
770
- taskId: task.id,
771
- prUrl: existingPr,
772
- });
773
- return;
774
- }
775
-
776
- const buildResult = stepResults.build;
777
- if (!buildResult?.commitCreated) {
778
- this.logger.warn(
779
- "Build step did not produce a commit; skipping PR creation",
780
- { taskId: task.id },
781
- );
782
- return;
783
- }
784
-
785
- const branchName = await this.gitManager.getCurrentBranch();
786
- const finalizeResult = stepResults.finalize;
787
- const prBody = finalizeResult?.prBody;
788
-
789
- const prUrl = await this.createPullRequest(
790
- task.id,
791
- branchName,
792
- task.title,
793
- task.description ?? "",
794
- prBody,
795
- );
796
-
797
- await sendNotification(POSTHOG_NOTIFICATIONS.PR_CREATED, { prUrl });
798
-
799
- try {
800
- await this.attachPullRequestToTask(task.id, prUrl, branchName);
801
- this.logger.info("PR attached to task successfully", {
802
- taskId: task.id,
803
- prUrl,
804
- });
805
- } catch (error) {
806
- this.logger.warn("Could not attach PR to task", {
807
- error: error instanceof Error ? error.message : String(error),
808
- });
176
+ async cleanup(): Promise<void> {
177
+ if (this.sessionLogWriter && this.taskRunId) {
178
+ await this.sessionLogWriter.flush(this.taskRunId);
809
179
  }
180
+ await this.acpConnection?.cleanup();
810
181
  }
811
182
  }
812
-
813
- export type {
814
- AgentConfig,
815
- ExecutionResult,
816
- SupportingFile,
817
- Task,
818
- } from "./types.js";
819
- export { PermissionMode } from "./types.js";